mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-11 10:00:08 +08:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bc52576d9 | ||
|
|
700d2c4a51 | ||
|
|
92b745e180 | ||
|
|
a2007083b8 | ||
|
|
36a5f7ff29 | ||
|
|
f727aea51d | ||
|
|
936ca24328 | ||
|
|
62f49b6087 | ||
|
|
e9ddbf9962 | ||
|
|
196cf522e6 | ||
|
|
3fce3bf4a7 | ||
|
|
1cfee25695 | ||
|
|
5711285a77 | ||
|
|
e6f537ca3a | ||
|
|
3b5220af57 | ||
|
|
fa6b4b1d2d | ||
|
|
7968e5374b | ||
|
|
64997ebe45 | ||
|
|
f8592b01e2 | ||
|
|
087474f514 | ||
|
|
1725088f05 | ||
|
|
ec1b756a3d | ||
|
|
76a06e0817 | ||
|
|
02fb608d7b | ||
|
|
e17fc2fc12 | ||
|
|
4f6c317652 | ||
|
|
46c198be26 | ||
|
|
8552203d43 | ||
|
|
139eaa7016 | ||
|
|
d81120ab8f | ||
|
|
6353d56beb | ||
|
|
aa05496b42 | ||
|
|
dc15e537d8 | ||
|
|
6fbd41f40a | ||
|
|
0181f614e1 | ||
|
|
fded7b0b28 | ||
|
|
7e637f835a | ||
|
|
deaaf1834d | ||
|
|
139c870f99 | ||
|
|
4cc2350bc6 | ||
|
|
8b31a118da | ||
|
|
cca26acb78 | ||
|
|
245edbd2f6 | ||
|
|
903d22c622 | ||
|
|
8b1805628e | ||
|
|
11c8c488da | ||
|
|
4dd4e0e148 | ||
|
|
21f352aa64 | ||
|
|
6c4beffdb7 | ||
|
|
43d3efa838 | ||
|
|
1c99839ab4 | ||
|
|
c9e05ce5b1 | ||
|
|
3fe7ed0e1d | ||
|
|
b3bff5c6f5 | ||
|
|
e357bac70f | ||
|
|
ad51d4e4f3 | ||
|
|
912d8ced93 | ||
|
|
8334999e98 | ||
|
|
5e23ea7809 | ||
|
|
b62d291aab | ||
|
|
a34dd8148f |
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -110,4 +110,4 @@
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/locales"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
components.d.ts
vendored
1
components.d.ts
vendored
@@ -8,6 +8,7 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ConfirmDialog: typeof import('./src/@core/components/ConfirmDialog.vue')['default']
|
||||
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']
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "moviepilot",
|
||||
"version": "2.4.9",
|
||||
"version": "2.5.1-1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"bin": "dist/service.js",
|
||||
@@ -62,7 +62,6 @@
|
||||
"vue3-perfect-scrollbar": "^2.0.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuetify": "3.7.3",
|
||||
"vuetify-use-dialog": "^0.6.11",
|
||||
"webfontloader": "^1.6.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
86
src/@core/components/ConfirmDialog.vue
Normal file
86
src/@core/components/ConfirmDialog.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
type?: 'info' | 'warn' | 'error'
|
||||
title?: string
|
||||
content?: string
|
||||
confirmText?: string
|
||||
cancelText?: string
|
||||
width?: string | number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 'info',
|
||||
title: '',
|
||||
content: '',
|
||||
confirmText: '',
|
||||
cancelText: '',
|
||||
width: '28rem',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'confirm'): void
|
||||
(e: 'cancel'): void
|
||||
}>()
|
||||
|
||||
// 对话框类型对应的图标和颜色
|
||||
const typeConfig = {
|
||||
info: {
|
||||
icon: 'mdi-information',
|
||||
color: 'info',
|
||||
},
|
||||
warn: {
|
||||
icon: 'mdi-alert',
|
||||
color: 'warning',
|
||||
},
|
||||
error: {
|
||||
icon: 'mdi-alert-circle',
|
||||
color: 'error',
|
||||
},
|
||||
}
|
||||
|
||||
// 获取当前类型的配置
|
||||
const currentType = computed(() => typeConfig[props.type])
|
||||
|
||||
// 确认按钮点击
|
||||
function handleConfirm() {
|
||||
emit('confirm')
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
// 取消按钮点击
|
||||
function handleCancel() {
|
||||
emit('cancel')
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog :model-value="modelValue" @update:model-value="emit('update:modelValue', $event)" :max-width="width">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<div class="d-flex align-center justify-start mt-3">
|
||||
<VAvatar :color="currentType.color" variant="text" size="x-large">
|
||||
<VIcon size="x-large" :icon="currentType.icon" />
|
||||
</VAvatar>
|
||||
<div class="mx-3">
|
||||
<p class="font-weight-bold text-xl text-high-emphasis">{{ title }}</p>
|
||||
<p>{{ content }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</VCardItem>
|
||||
<VCardActions class="mx-auto">
|
||||
<VBtn variant="tonal" color="secondary" class="px-5" @click="handleCancel">
|
||||
{{ cancelText }}
|
||||
</VBtn>
|
||||
<VBtn variant="elevated" :color="currentType.color" @click="handleConfirm" class="px-5">
|
||||
{{ confirmText }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
<VDialogCloseBtn @click="handleCancel" />
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
@@ -5,6 +5,10 @@ import filter_svg from '@images/svg/filter.svg'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { innerFilterRules } from '@/api/constants'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -106,8 +110,20 @@ function onClose() {
|
||||
<VImg :src="filter_svg" cover class="mt-7" max-width="3rem" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
<VDialog v-if="ruleInfoDialog" v-model="ruleInfoDialog" scrollable max-width="40rem">
|
||||
<VCard :title="t('customRule.title', { id: props.rule.id })">
|
||||
<VDialog
|
||||
v-if="ruleInfoDialog"
|
||||
v-model="ruleInfoDialog"
|
||||
scrollable
|
||||
max-width="40rem"
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-filter-outline" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ t('customRule.title', { id: props.rule.id }) }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn v-model="ruleInfoDialog" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
@@ -121,6 +137,7 @@ function onClose() {
|
||||
:hint="t('customRule.hint.ruleId')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-identifier"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -131,6 +148,7 @@ function onClose() {
|
||||
:hint="t('customRule.hint.ruleName')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -141,6 +159,7 @@ function onClose() {
|
||||
:hint="t('customRule.hint.include')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-plus-circle"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -151,6 +170,7 @@ function onClose() {
|
||||
:hint="t('customRule.hint.exclude')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-minus-circle"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
@@ -161,6 +181,7 @@ function onClose() {
|
||||
:hint="t('customRule.hint.sizeRange')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-harddisk"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
@@ -171,6 +192,7 @@ function onClose() {
|
||||
:hint="t('customRule.hint.seeders')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-account-group"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
@@ -181,13 +203,14 @@ function onClose() {
|
||||
:hint="t('customRule.hint.publishTime')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-calendar-clock"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn @click="saveRuleInfo" variant="elevated" prepend-icon="mdi-content-save" class="px-5">{{
|
||||
<VBtn @click="saveRuleInfo" prepend-icon="mdi-content-save" class="px-5">{{
|
||||
t('customRule.action.confirm')
|
||||
}}</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -10,6 +10,10 @@ import custom_image from '@images/logos/downloader.png'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { downloaderDict } from '@/api/constants'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 获取i18n实例
|
||||
const { t } = useI18n()
|
||||
@@ -188,8 +192,22 @@ onUnmounted(() => {
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VHover>
|
||||
<VDialog v-if="downloaderInfoDialog" v-model="downloaderInfoDialog" scrollable max-width="40rem">
|
||||
<VCard :title="`${props.downloader.name} - ${t('downloader.title')}`">
|
||||
|
||||
<VDialog
|
||||
v-if="downloaderInfoDialog"
|
||||
v-model="downloaderInfoDialog"
|
||||
scrollable
|
||||
max-width="40rem"
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem class="py-2">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-download" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ t('common.config') }}</VCardTitle>
|
||||
<VCardSubtitle>{{ props.downloader.name }}</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn v-model="downloaderInfoDialog" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
@@ -215,6 +233,7 @@ onUnmounted(() => {
|
||||
:hint="t('downloader.name')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -225,6 +244,7 @@ onUnmounted(() => {
|
||||
:hint="t('downloader.host')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-server"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -234,6 +254,7 @@ onUnmounted(() => {
|
||||
:hint="t('downloader.username')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-account"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -244,6 +265,7 @@ onUnmounted(() => {
|
||||
:hint="t('downloader.password')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-lock"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -292,6 +314,7 @@ onUnmounted(() => {
|
||||
:hint="t('downloader.name')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -302,6 +325,7 @@ onUnmounted(() => {
|
||||
:hint="t('downloader.host')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-server"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -311,6 +335,7 @@ onUnmounted(() => {
|
||||
:hint="t('downloader.username')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-account"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -321,6 +346,7 @@ onUnmounted(() => {
|
||||
:hint="t('downloader.password')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-lock"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -332,6 +358,7 @@ onUnmounted(() => {
|
||||
:hint="t('downloader.customTypeHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-cog"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -341,13 +368,14 @@ onUnmounted(() => {
|
||||
:hint="t('downloader.nameRequired')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn @click="saveDownloaderInfo" variant="elevated" prepend-icon="mdi-content-save" class="px-5">
|
||||
<VBtn @click="saveDownloaderInfo" prepend-icon="mdi-content-save" class="px-5">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -8,6 +8,10 @@ import ImportCodeDialog from '@/components/dialog/ImportCodeDialog.vue'
|
||||
import filter_group_svg from '@images/svg/filter-group.svg'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 获取i18n实例
|
||||
const { t } = useI18n()
|
||||
@@ -219,7 +223,13 @@ function onClose() {
|
||||
<VImg :src="filter_group_svg" cover class="mt-10" max-width="3rem" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
<VDialog v-if="groupInfoDialog" v-model="groupInfoDialog" scrollable max-width="80rem">
|
||||
<VDialog
|
||||
v-if="groupInfoDialog"
|
||||
v-model="groupInfoDialog"
|
||||
scrollable
|
||||
max-width="80rem"
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard :title="`${props.group.name} - ${t('filterRule.title')}`">
|
||||
<VDialogCloseBtn v-model="groupInfoDialog" />
|
||||
<VDivider />
|
||||
@@ -233,6 +243,7 @@ function onClose() {
|
||||
:hint="t('filterRule.groupName')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="6" md="3">
|
||||
@@ -243,6 +254,7 @@ function onClose() {
|
||||
:hint="t('filterRule.mediaType')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-movie-open"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="6" md="3">
|
||||
@@ -253,6 +265,7 @@ function onClose() {
|
||||
:hint="t('filterRule.category')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-folder-open"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -280,17 +293,17 @@ function onClose() {
|
||||
<div class="text-center" v-if="filterRuleCards.length == 0">{{ t('filterRule.add') }}</div>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn color="primary" variant="tonal" @click="addFilterCard">
|
||||
<VBtn color="primary" @click="addFilterCard">
|
||||
<VIcon icon="mdi-plus" />
|
||||
</VBtn>
|
||||
<VBtn color="success" variant="tonal" @click="importRules('priority')">
|
||||
<VBtn color="success" @click="importRules('priority')">
|
||||
<VIcon icon="mdi-import" />
|
||||
</VBtn>
|
||||
<VBtn color="info" variant="tonal" @click="shareRules">
|
||||
<VBtn color="info" @click="shareRules">
|
||||
<VIcon icon="mdi-share" />
|
||||
</VBtn>
|
||||
<VSpacer />
|
||||
<VBtn @click="saveGroupInfo" variant="elevated" prepend-icon="mdi-content-save" class="px-5">
|
||||
<VBtn @click="saveGroupInfo" prepend-icon="mdi-content-save" class="px-5">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -10,6 +10,10 @@ import api from '@/api'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { mediaServerDict } from '@/api/constants'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 获取i18n实例
|
||||
const { t } = useI18n()
|
||||
@@ -199,8 +203,22 @@ onMounted(() => {
|
||||
<VImg :src="getIcon" cover class="mt-7 me-3" max-width="3rem" min-width="3rem" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
<VDialog v-if="mediaServerInfoDialog" v-model="mediaServerInfoDialog" scrollable max-width="40rem">
|
||||
<VCard :title="`${props.mediaserver.name} - ${t('common.config')}`">
|
||||
|
||||
<VDialog
|
||||
v-if="mediaServerInfoDialog"
|
||||
v-model="mediaServerInfoDialog"
|
||||
scrollable
|
||||
max-width="40rem"
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem class="py-2">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-cog" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ t('common.config') }}</VCardTitle>
|
||||
<VCardSubtitle>{{ props.mediaserver.name }}</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn v-model="mediaServerInfoDialog" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
@@ -219,6 +237,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.serverAlias')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -229,6 +248,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.hostHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-server"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -239,6 +259,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.playHostHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-play-network"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -248,6 +269,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.embyApiKeyHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-key"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -262,6 +284,7 @@ onMounted(() => {
|
||||
persistent-hint
|
||||
active
|
||||
append-inner-icon="mdi-refresh"
|
||||
prepend-inner-icon="mdi-library"
|
||||
@click:append-inner="loadLibrary(mediaServerInfo.name)"
|
||||
/>
|
||||
</VCol>
|
||||
@@ -275,6 +298,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.serverAlias')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -285,6 +309,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.hostHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-server"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -295,6 +320,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.playHostHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-play-network"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -304,6 +330,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.jellyfinApiKeyHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-key"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -318,6 +345,7 @@ onMounted(() => {
|
||||
persistent-hint
|
||||
active
|
||||
append-inner-icon="mdi-refresh"
|
||||
prepend-inner-icon="mdi-library"
|
||||
@click:append-inner="loadLibrary(mediaServerInfo.name)"
|
||||
/>
|
||||
</VCol>
|
||||
@@ -331,6 +359,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.serverAlias')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -341,6 +370,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.hostHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-server"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -351,10 +381,16 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.playHostHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-play-network"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField v-model="mediaServerInfo.config.username" :label="t('mediaserver.username')" active />
|
||||
<VTextField
|
||||
v-model="mediaServerInfo.config.username"
|
||||
:label="t('mediaserver.username')"
|
||||
active
|
||||
prepend-inner-icon="mdi-account"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
@@ -362,6 +398,7 @@ onMounted(() => {
|
||||
v-model="mediaServerInfo.config.password"
|
||||
:label="t('mediaserver.password')"
|
||||
active
|
||||
prepend-inner-icon="mdi-lock"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -376,6 +413,7 @@ onMounted(() => {
|
||||
persistent-hint
|
||||
active
|
||||
append-inner-icon="mdi-refresh"
|
||||
prepend-inner-icon="mdi-library"
|
||||
@click:append-inner="loadLibrary(mediaServerInfo.name)"
|
||||
/>
|
||||
</VCol>
|
||||
@@ -389,6 +427,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.serverAlias')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -399,6 +438,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.hostHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-server"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -409,6 +449,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.playHostHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-play-network"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -418,6 +459,7 @@ onMounted(() => {
|
||||
:hint="t('mediaserver.plexTokenHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-key"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -432,21 +474,7 @@ onMounted(() => {
|
||||
persistent-hint
|
||||
active
|
||||
append-inner-icon="mdi-refresh"
|
||||
@click:append-inner="loadLibrary(mediaServerInfo.name)"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VSelect
|
||||
v-model="mediaServerInfo.sync_libraries"
|
||||
:label="t('mediaserver.syncLibraries')"
|
||||
:items="librariesOptions"
|
||||
chips
|
||||
multiple
|
||||
clearable
|
||||
:hint="t('mediaserver.syncLibrariesHint')"
|
||||
persistent-hint
|
||||
active
|
||||
append-inner-icon="mdi-refresh"
|
||||
prepend-inner-icon="mdi-library"
|
||||
@click:append-inner="loadLibrary(mediaServerInfo.name)"
|
||||
/>
|
||||
</VCol>
|
||||
@@ -458,16 +486,22 @@ onMounted(() => {
|
||||
:label="t('mediaserver.type')"
|
||||
:hint="t('mediaserver.customTypeHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-cog"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField :label="t('common.name')" :hint="t('mediaserver.nameRequired')" persistent-hint />
|
||||
<VTextField
|
||||
:label="t('common.name')"
|
||||
:hint="t('mediaserver.nameRequired')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn @click="saveMediaServerInfo" variant="elevated" prepend-icon="mdi-content-save" class="px-5">
|
||||
<VBtn @click="saveMediaServerInfo" prepend-icon="mdi-content-save" class="px-5">
|
||||
{{ t('common.confirm') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -10,6 +10,10 @@ import custom_image from '@images/logos/notification.png'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -106,8 +110,6 @@ const getIcon = computed(() => {
|
||||
return slack_image
|
||||
case 'webpush':
|
||||
return chrome_image
|
||||
case 'wechat':
|
||||
return wechat_image
|
||||
default:
|
||||
return custom_image
|
||||
}
|
||||
@@ -138,9 +140,23 @@ function onClose() {
|
||||
<VImg :src="getIcon" cover class="mt-7 me-1" max-width="3rem" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
<VDialog v-if="notificationInfoDialog" v-model="notificationInfoDialog" scrollable max-width="40rem">
|
||||
<VCard :title="`${props.notification.name} - ${t('notification.config')}`">
|
||||
<VDialogCloseBtn v-model="notificationInfoDialog" />
|
||||
|
||||
<VDialog
|
||||
v-if="notificationInfoDialog"
|
||||
v-model="notificationInfoDialog"
|
||||
scrollable
|
||||
max-width="40rem"
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem class="py-2">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-cog" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ t('common.config') }}</VCardTitle>
|
||||
<VCardSubtitle>{{ props.notification.name }}</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn @click="notificationInfoDialog = false" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VForm>
|
||||
@@ -158,6 +174,7 @@ function onClose() {
|
||||
clearable
|
||||
chips
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-bell-outline"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -169,6 +186,7 @@ function onClose() {
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -177,6 +195,7 @@ function onClose() {
|
||||
:label="t('notification.wechat.corpId')"
|
||||
:hint="t('notification.wechat.corpIdHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-domain"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -185,6 +204,7 @@ function onClose() {
|
||||
:label="t('notification.wechat.appId')"
|
||||
:hint="t('notification.wechat.appIdHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-application"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -193,6 +213,7 @@ function onClose() {
|
||||
:label="t('notification.wechat.appSecret')"
|
||||
:hint="t('notification.wechat.appSecretHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -201,6 +222,7 @@ function onClose() {
|
||||
:label="t('notification.wechat.proxy')"
|
||||
:hint="t('notification.wechat.proxyHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-server-network"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -209,6 +231,7 @@ function onClose() {
|
||||
:label="t('notification.wechat.token')"
|
||||
:hint="t('notification.wechat.tokenHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key-variant"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -217,6 +240,7 @@ function onClose() {
|
||||
:label="t('notification.wechat.encodingAesKey')"
|
||||
:hint="t('notification.wechat.encodingAesKeyHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-lock"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -226,6 +250,7 @@ function onClose() {
|
||||
:placeholder="t('notification.wechat.adminsPlaceholder')"
|
||||
:hint="t('notification.wechat.adminsHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-account-supervisor"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -237,6 +262,7 @@ function onClose() {
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -245,6 +271,7 @@ function onClose() {
|
||||
:label="t('notification.telegram.token')"
|
||||
:hint="t('notification.telegram.tokenHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -253,6 +280,7 @@ function onClose() {
|
||||
:label="t('notification.telegram.chatId')"
|
||||
:hint="t('notification.telegram.chatIdHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-chat"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -262,6 +290,7 @@ function onClose() {
|
||||
:placeholder="t('notification.telegram.usersPlaceholder')"
|
||||
:hint="t('notification.telegram.usersHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-account-group"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -271,6 +300,7 @@ function onClose() {
|
||||
:placeholder="t('notification.telegram.adminsPlaceholder')"
|
||||
:hint="t('notification.telegram.adminsHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-account-supervisor"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -282,6 +312,7 @@ function onClose() {
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -291,6 +322,7 @@ function onClose() {
|
||||
:placeholder="t('notification.slack.oauthTokenPlaceholder')"
|
||||
:hint="t('notification.slack.oauthTokenHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -300,6 +332,7 @@ function onClose() {
|
||||
:placeholder="t('notification.slack.appTokenPlaceholder')"
|
||||
:hint="t('notification.slack.appTokenHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-application"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -309,6 +342,7 @@ function onClose() {
|
||||
:placeholder="t('notification.slack.channelPlaceholder')"
|
||||
:hint="t('notification.slack.channelHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-pound"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -320,6 +354,7 @@ function onClose() {
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -328,6 +363,7 @@ function onClose() {
|
||||
:label="t('notification.synologychat.webhook')"
|
||||
:hint="t('notification.synologychat.webhookHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-webhook"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -336,6 +372,7 @@ function onClose() {
|
||||
:label="t('notification.synologychat.token')"
|
||||
:hint="t('notification.synologychat.tokenHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -347,6 +384,7 @@ function onClose() {
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -355,6 +393,7 @@ function onClose() {
|
||||
:label="t('notification.vocechat.host')"
|
||||
:hint="t('notification.vocechat.hostHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-server"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -363,6 +402,7 @@ function onClose() {
|
||||
:label="t('notification.vocechat.apiKey')"
|
||||
:hint="t('notification.vocechat.apiKeyHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -372,6 +412,7 @@ function onClose() {
|
||||
:placeholder="t('notification.vocechat.channelIdPlaceholder')"
|
||||
:hint="t('notification.vocechat.channelIdHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-pound"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -383,6 +424,7 @@ function onClose() {
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -391,6 +433,7 @@ function onClose() {
|
||||
:label="t('notification.webpush.username')"
|
||||
:hint="t('notification.webpush.usernameHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-account"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -402,6 +445,7 @@ function onClose() {
|
||||
:hint="t('notification.customTypeHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-cog"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -410,14 +454,14 @@ function onClose() {
|
||||
:label="t('notification.name')"
|
||||
:hint="t('notification.nameRequired')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn @click="saveNotificationInfo" variant="elevated" prepend-icon="mdi-content-save" class="px-5">
|
||||
<VBtn @click="saveNotificationInfo" prepend-icon="mdi-content-save" class="px-5">
|
||||
{{ t('common.confirm') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -98,10 +98,7 @@ function goPersonDetail() {
|
||||
<div class="w-full truncate text-center font-bold">
|
||||
{{ getPersonName() }}
|
||||
</div>
|
||||
<div
|
||||
class="overflow-hidden whitespace-normal text-center text-sm"
|
||||
style="display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2"
|
||||
>
|
||||
<div class="overflow-hidden whitespace-normal text-center text-sm text-ellipsis line-clamp-2">
|
||||
{{ getPersonCharacter() }}
|
||||
</div>
|
||||
<div class="absolute bottom-0 left-0 right-0 h-12 rounded-b" />
|
||||
|
||||
@@ -36,7 +36,17 @@ const $toast = useToast()
|
||||
const progressDialog = ref(false)
|
||||
|
||||
// 进度框文本
|
||||
const progressText = ref('正在安装插件...')
|
||||
const progressText = ref('')
|
||||
|
||||
// 获取当前插件的标签
|
||||
const pluginLabels = computed(() => {
|
||||
if (!props.plugin?.plugin_label) return []
|
||||
|
||||
return props.plugin.plugin_label
|
||||
.split(',')
|
||||
.map(tag => tag.trim())
|
||||
.filter(tag => tag.length > 0)
|
||||
})
|
||||
|
||||
// 图片是否加载完成
|
||||
const isImageLoaded = ref(false)
|
||||
@@ -180,9 +190,26 @@ const dropdownItems = ref([
|
||||
</VCardText>
|
||||
<div class="relative flex flex-row items-start px-2 justify-between grow">
|
||||
<div class="relative flex-1 min-w-0">
|
||||
<VCardText class="text-white text-sm px-2 py-1 text-shadow overflow-hidden line-clamp-3 ...">
|
||||
<div
|
||||
class="text-white text-sm px-2 py-1 text-shadow overflow-hidden ..."
|
||||
:class="{ 'line-clamp-3': !props.plugin?.plugin_label, 'line-clamp-2': props.plugin?.plugin_label }"
|
||||
>
|
||||
{{ props.plugin?.plugin_desc }}
|
||||
</VCardText>
|
||||
</div>
|
||||
<!-- 插件标签 -->
|
||||
<div v-if="pluginLabels.length > 0" class="plugin-app-card__tags-section px-2">
|
||||
<VChip
|
||||
v-for="tag in pluginLabels"
|
||||
:key="tag"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
color="info"
|
||||
class="me-1 mb-1"
|
||||
tile
|
||||
>
|
||||
{{ tag }}
|
||||
</VChip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative flex-shrink-0 self-center pb-3">
|
||||
<VAvatar size="48">
|
||||
@@ -198,9 +225,11 @@ const dropdownItems = ref([
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<VCardText class="flex flex-col align-self-baseline px-2 py-2 w-full overflow-hidden">
|
||||
<div class="flex flex-nowrap items-center w-full pe-7">
|
||||
<span>
|
||||
<VCardText
|
||||
class="flex flex-col align-self-baseline justify-between px-2 py-2 w-full overflow-hidden max-h-10 min-h-10"
|
||||
>
|
||||
<div class="flex flex-nowrap items-center w-full pe-10">
|
||||
<div class="flex flex-nowrap max-w-32 items-center align-middle">
|
||||
<VIcon icon="mdi-github" class="me-1" />
|
||||
<a
|
||||
class="overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
@@ -210,13 +239,13 @@ const dropdownItems = ref([
|
||||
>
|
||||
{{ props.plugin?.plugin_author }}
|
||||
</a>
|
||||
</span>
|
||||
<span v-if="props.count" class="ms-2 flex-shrink-0 download-count">
|
||||
<VIcon icon="mdi-download" />
|
||||
<span class="text-sm ms-1 mt-1">{{ props.count?.toLocaleString() }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="props.count" class="ms-2 flex-shrink-0 download-count align-middle items-center">
|
||||
<VIcon size="small" icon="mdi-download" />
|
||||
<span class="text-sm">{{ props.count?.toLocaleString() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="me-n3 absolute bottom-0 right-3">
|
||||
<div class="absolute bottom-0 right-0">
|
||||
<IconBtn>
|
||||
<VIcon size="small" icon="mdi-dots-vertical" />
|
||||
<VMenu activator="parent" close-on-content-click>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { useConfirm } from 'vuetify-use-dialog'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import api from '@/api'
|
||||
import type { Plugin } from '@/api/types'
|
||||
import { isNullOrEmptyObject } from '@core/utils'
|
||||
@@ -10,7 +10,12 @@ import VersionHistory from '@/components/misc/VersionHistory.vue'
|
||||
import ProgressDialog from '../dialog/ProgressDialog.vue'
|
||||
import PluginConfigDialog from '../dialog/PluginConfigDialog.vue'
|
||||
import PluginDataDialog from '../dialog/PluginDataDialog.vue'
|
||||
import LoggingView from '@/views/system/LoggingView.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -54,6 +59,9 @@ const progressDialog = ref(false)
|
||||
// 插件数据页面
|
||||
const pluginInfoDialog = ref(false)
|
||||
|
||||
// 实时日志弹窗
|
||||
const loggingDialog = ref(false)
|
||||
|
||||
// 进度框文本
|
||||
const progressText = ref('正在更新插件...')
|
||||
|
||||
@@ -69,6 +77,18 @@ const imageLoadError = ref(false)
|
||||
// 更新日志弹窗
|
||||
const releaseDialog = ref(false)
|
||||
|
||||
// 插件分身对话框
|
||||
const pluginCloneDialog = ref(false)
|
||||
|
||||
// 插件分身表单
|
||||
const cloneForm = ref({
|
||||
suffix: '',
|
||||
name: '',
|
||||
description: '',
|
||||
version: '',
|
||||
icon: '',
|
||||
})
|
||||
|
||||
// 监听动作标识,如为true则打开详情
|
||||
watch(
|
||||
() => props.action,
|
||||
@@ -120,7 +140,12 @@ async function uninstallPlugin() {
|
||||
// 通知父组件刷新
|
||||
emit('remove')
|
||||
} else {
|
||||
$toast.error(t('plugin.uninstallFailed', { name: props.plugin?.plugin_name, message: result.message }))
|
||||
$toast.error(
|
||||
t('plugin.uninstallFailed', {
|
||||
name: props.plugin?.plugin_name,
|
||||
message: result.message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -174,7 +199,12 @@ async function resetPlugin() {
|
||||
// 通知父组件刷新
|
||||
emit('save')
|
||||
} else {
|
||||
$toast.error(t('plugin.resetFailed', { name: props.plugin?.plugin_name, message: result.message }))
|
||||
$toast.error(
|
||||
t('plugin.resetFailed', {
|
||||
name: props.plugin?.plugin_name,
|
||||
message: result.message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -205,7 +235,12 @@ async function updatePlugin() {
|
||||
// 通知父组件刷新
|
||||
emit('save')
|
||||
} else {
|
||||
$toast.error(t('plugin.updateFailed', { name: props.plugin?.plugin_name, message: result.message }))
|
||||
$toast.error(
|
||||
t('plugin.updateFailed', {
|
||||
name: props.plugin?.plugin_name,
|
||||
message: result.message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -237,6 +272,54 @@ function configDone() {
|
||||
emit('save')
|
||||
}
|
||||
|
||||
// 显示插件分身对话框
|
||||
function showPluginClone() {
|
||||
cloneForm.value = {
|
||||
suffix: '',
|
||||
name: t('plugin.cloneDefaultName', { name: props.plugin?.plugin_name }),
|
||||
description: t('plugin.cloneDefaultDescription', { description: props.plugin?.plugin_desc }),
|
||||
version: props.plugin?.plugin_version || '1.0',
|
||||
icon: props.plugin?.plugin_icon || '',
|
||||
}
|
||||
pluginCloneDialog.value = true
|
||||
}
|
||||
|
||||
// 执行插件分身
|
||||
async function executePluginClone() {
|
||||
if (!cloneForm.value.suffix.trim()) {
|
||||
$toast.error(t('plugin.suffixRequired'))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
progressDialog.value = true
|
||||
progressText.value = t('plugin.cloning', { name: props.plugin?.plugin_name })
|
||||
|
||||
const result: { [key: string]: any } = await api.post(`plugin/clone/${props.plugin?.id}`, {
|
||||
suffix: cloneForm.value.suffix.trim(),
|
||||
name: cloneForm.value.name.trim(),
|
||||
description: cloneForm.value.description.trim(),
|
||||
version: cloneForm.value.version.trim(),
|
||||
icon: cloneForm.value.icon.trim(),
|
||||
})
|
||||
|
||||
progressDialog.value = false
|
||||
|
||||
if (result.success) {
|
||||
$toast.success(t('plugin.cloneSuccess', { name: cloneForm.value.name }))
|
||||
pluginCloneDialog.value = false
|
||||
// 通知父组件刷新
|
||||
emit('remove')
|
||||
} else {
|
||||
$toast.error(t('plugin.cloneFailed', { message: result.message }))
|
||||
}
|
||||
} catch (error) {
|
||||
progressDialog.value = false
|
||||
$toast.error(t('plugin.cloneFailedGeneral'))
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 弹出菜单
|
||||
const dropdownItems = ref([
|
||||
{
|
||||
@@ -257,6 +340,16 @@ const dropdownItems = ref([
|
||||
click: showPluginConfig,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('plugin.clone'),
|
||||
value: 8,
|
||||
show: true,
|
||||
props: {
|
||||
prependIcon: 'mdi-content-copy',
|
||||
color: 'info',
|
||||
click: showPluginClone,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('plugin.update'),
|
||||
value: 3,
|
||||
@@ -294,7 +387,7 @@ const dropdownItems = ref([
|
||||
props: {
|
||||
prependIcon: 'mdi-file-document-outline',
|
||||
click: () => {
|
||||
openLoggerWindow()
|
||||
loggingDialog.value = true
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -328,7 +421,7 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="h-full">
|
||||
<!-- 插件卡片 -->
|
||||
<VHover>
|
||||
<template #default="hover">
|
||||
@@ -358,11 +451,11 @@ watch(
|
||||
</VCardText>
|
||||
<div class="relative flex flex-row items-start px-2 justify-between grow">
|
||||
<div class="relative flex-1 min-w-0">
|
||||
<VCardText class="px-2 py-1 text-white text-sm text-shadow overflow-hidden line-clamp-3 ...">
|
||||
<div class="px-2 py-1 text-white text-sm text-shadow overflow-hidden line-clamp-3 ...">
|
||||
{{ props.plugin?.plugin_desc }}
|
||||
</VCardText>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative flex-shrink-0 self-center cursor-move pb-3">
|
||||
<div class="relative flex-shrink-0 self-center pb-3" :class="{ 'cursor-move': display.mdAndUp.value }">
|
||||
<VAvatar size="48">
|
||||
<VImg
|
||||
ref="imageRef"
|
||||
@@ -376,9 +469,11 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<VCardText class="flex flex-col align-self-baseline px-2 py-2 w-full overflow-hidden">
|
||||
<div class="flex flex-nowrap items-center w-full pe-7">
|
||||
<span class="author-info flex-shrink-1 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
<VCardText
|
||||
class="flex flex-col align-self-baseline justify-between px-2 py-2 w-full overflow-hidden max-h-10 min-h-10"
|
||||
>
|
||||
<div class="flex flex-nowrap items-center w-full pe-10">
|
||||
<div class="flex flex-nowrap max-w-32 items-center align-middle">
|
||||
<VImg :src="authorPath" class="author-avatar" @load="isAvatarLoaded = true">
|
||||
<VIcon v-if="!isAvatarLoaded" size="small" icon="mdi-github" class="me-1" />
|
||||
</VImg>
|
||||
@@ -390,15 +485,15 @@ watch(
|
||||
>
|
||||
{{ props.plugin?.plugin_author }}
|
||||
</a>
|
||||
</span>
|
||||
<span v-if="props.count" class="ms-2 flex-shrink-0 download-count">
|
||||
</div>
|
||||
<span v-if="props.count" class="ms-2 flex-shrink-0 download-count items-center align-middle">
|
||||
<VIcon size="small" icon="mdi-download" />
|
||||
<span class="text-sm ms-1 mt-1">{{ props.count?.toLocaleString() }}</span>
|
||||
<span class="text-sm">{{ props.count?.toLocaleString() }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="me-n3 absolute bottom-0 right-3">
|
||||
<div class="absolute bottom-0 right-0">
|
||||
<IconBtn>
|
||||
<VIcon size="small" icon="mdi-dots-vertical" />
|
||||
<VIcon icon="mdi-dots-vertical" />
|
||||
<VMenu v-model="menuVisible" activator="parent" close-on-content-click>
|
||||
<VList>
|
||||
<VListItem
|
||||
@@ -448,7 +543,7 @@ watch(
|
||||
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
|
||||
|
||||
<!-- 更新日志 -->
|
||||
<VDialog v-if="releaseDialog" v-model="releaseDialog" width="600" max-height="85vh" scrollable>
|
||||
<VDialog v-if="releaseDialog" v-model="releaseDialog" width="600" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
<VCard :title="t('plugin.updateHistoryTitle', { name: props.plugin?.plugin_name })">
|
||||
<VDialogCloseBtn @click="releaseDialog = false" />
|
||||
<VDivider />
|
||||
@@ -464,6 +559,144 @@ watch(
|
||||
</VCardItem>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- 实时日志弹窗 -->
|
||||
<VDialog
|
||||
v-if="loggingDialog"
|
||||
v-model="loggingDialog"
|
||||
scrollable
|
||||
max-width="60rem"
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VDialogCloseBtn @click="loggingDialog = false" />
|
||||
<VCardItem>
|
||||
<VCardTitle class="d-inline-flex">
|
||||
<VIcon icon="mdi-file-document" class="me-2" />
|
||||
{{ t('plugin.logTitle') }}
|
||||
<a class="mx-2 d-inline-flex align-center cursor-pointer" @click="openLoggerWindow">
|
||||
<VChip color="grey-darken-1" size="small" class="ml-2">
|
||||
<VIcon icon="mdi-open-in-new" size="small" start />
|
||||
{{ t('common.openInNewWindow') }}
|
||||
</VChip>
|
||||
</a>
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<LoggingView :logfile="`plugins/${props.plugin?.id?.toLowerCase()}.log`" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- 插件分身对话框 -->
|
||||
<VDialog
|
||||
v-if="pluginCloneDialog"
|
||||
v-model="pluginCloneDialog"
|
||||
width="600"
|
||||
scrollable
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem class="py-2">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-content-copy" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ t('plugin.cloneTitle') }}</VCardTitle>
|
||||
<VCardSubtitle>{{ t('plugin.cloneSubtitle', { name: props.plugin?.plugin_name }) }}</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn @click="pluginCloneDialog = false" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="cloneForm.suffix"
|
||||
:label="t('plugin.suffix') + ' *'"
|
||||
:placeholder="t('plugin.suffixPlaceholder')"
|
||||
:hint="t('plugin.suffixHint')"
|
||||
persistent-hint
|
||||
:rules="[
|
||||
v => !!v || t('plugin.suffixRequired'),
|
||||
v => /^[a-zA-Z0-9]+$/.test(v) || t('plugin.suffixFormatError'),
|
||||
v => v.length <= 20 || t('plugin.suffixLengthError'),
|
||||
]"
|
||||
required
|
||||
prepend-inner-icon="mdi-tag"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="cloneForm.name"
|
||||
:label="t('plugin.cloneName')"
|
||||
:placeholder="t('plugin.cloneNamePlaceholder')"
|
||||
:hint="t('plugin.cloneNameHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-rename-box"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="cloneForm.description"
|
||||
:label="t('plugin.cloneDescriptionLabel')"
|
||||
:placeholder="t('plugin.cloneDescriptionPlaceholder')"
|
||||
:hint="t('plugin.cloneDescriptionHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-text"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="cloneForm.version"
|
||||
:label="t('plugin.cloneVersion')"
|
||||
:placeholder="t('plugin.cloneVersionPlaceholder')"
|
||||
:hint="t('plugin.cloneVersionHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-numeric"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="cloneForm.icon"
|
||||
:label="t('plugin.cloneIcon')"
|
||||
:placeholder="t('plugin.cloneIconPlaceholder')"
|
||||
:hint="t('plugin.cloneIconHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-image"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- 重要提醒 -->
|
||||
<VCol cols="12">
|
||||
<VAlert type="warning" variant="tonal" density="compact" class="mt-2" icon="mdi-alert-circle-outline">
|
||||
<div class="text-body-2">
|
||||
<strong>{{ t('common.notice') }}</strong
|
||||
>:{{ t('plugin.cloneNotice') }}
|
||||
</div>
|
||||
</VAlert>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
color="primary"
|
||||
@click="executePluginClone"
|
||||
prepend-icon="mdi-content-copy"
|
||||
class="px-5"
|
||||
:disabled="!cloneForm.suffix.trim()"
|
||||
>
|
||||
{{ t('plugin.createClone') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -478,11 +711,6 @@ watch(
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.author-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.author-avatar {
|
||||
border-radius: 50%;
|
||||
block-size: 24px;
|
||||
|
||||
663
src/components/cards/PluginFolderCard.vue
Normal file
663
src/components/cards/PluginFolderCard.vue
Normal file
@@ -0,0 +1,663 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 文件夹配置接口
|
||||
interface FolderConfig {
|
||||
plugins?: string[]
|
||||
order?: number
|
||||
background?: string
|
||||
icon?: string
|
||||
color?: string
|
||||
gradient?: string
|
||||
showIcon?: boolean
|
||||
}
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
folderName: String,
|
||||
pluginCount: Number,
|
||||
folderConfig: {
|
||||
type: Object as PropType<FolderConfig>,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: String,
|
||||
height: String,
|
||||
})
|
||||
|
||||
// 定义触发的自定义事件
|
||||
const emit = defineEmits(['open', 'delete', 'rename', 'update-config'])
|
||||
|
||||
// 多语言
|
||||
const { t } = useI18n()
|
||||
|
||||
// 响应式显示
|
||||
const display = useDisplay()
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 确认框
|
||||
const createConfirm = useConfirm()
|
||||
|
||||
// 菜单显示状态
|
||||
const menuVisible = ref(false)
|
||||
|
||||
// 重命名对话框
|
||||
const renameDialog = ref(false)
|
||||
|
||||
// 设置对话框
|
||||
const settingDialog = ref(false)
|
||||
|
||||
// 新名称
|
||||
const newFolderName = ref('')
|
||||
|
||||
// 默认颜色
|
||||
const defaultColor = '#2196F3'
|
||||
// 默认图标
|
||||
const defaultIcon = 'mdi-folder'
|
||||
// 默认渐变
|
||||
const defaultGradient =
|
||||
'linear-gradient(rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.5) 100%), linear-gradient(135deg, rgba(33, 150, 243, 0.7) 0%, rgba(33, 150, 243, 0.8s) 100%)'
|
||||
|
||||
// 文件夹设置
|
||||
const folderSettings = ref<FolderConfig>({
|
||||
background: '',
|
||||
icon: defaultIcon,
|
||||
color: defaultColor,
|
||||
gradient: defaultGradient,
|
||||
showIcon: true,
|
||||
})
|
||||
|
||||
// 计算背景图片
|
||||
const backgroundImage = computed(() => {
|
||||
return props.folderConfig.background || folderSettings.value.background
|
||||
})
|
||||
|
||||
// 预设图标选项
|
||||
const iconOptions = [
|
||||
'mdi-folder',
|
||||
'mdi-folder-star',
|
||||
'mdi-folder-heart',
|
||||
'mdi-folder-cog',
|
||||
'mdi-folder-music',
|
||||
'mdi-folder-image',
|
||||
'mdi-folder-video',
|
||||
'mdi-folder-download',
|
||||
'mdi-folder-network',
|
||||
'mdi-folder-special',
|
||||
]
|
||||
|
||||
// 预设颜色选项
|
||||
const colorOptions = [
|
||||
'#2196F3', // 蓝色
|
||||
'#4CAF50', // 绿色
|
||||
'#FF9800', // 橙色
|
||||
'#9C27B0', // 紫色
|
||||
'#F44336', // 红色
|
||||
'#607D8B', // 蓝灰色
|
||||
'#795548', // 棕色
|
||||
'#E91E63', // 粉色
|
||||
]
|
||||
|
||||
// 预设渐变选项
|
||||
const gradientOptions = [
|
||||
'linear-gradient(rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.4) 100%), linear-gradient(135deg, rgba(33, 150, 243, 0.7) 0%, rgba(33, 150, 243, 0.8) 100%)',
|
||||
'linear-gradient(rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.4) 100%), linear-gradient(135deg, rgba(76, 175, 80, 0.7) 0%, rgba(76, 175, 80, 0.8) 100%)',
|
||||
'linear-gradient(rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.4) 100%), linear-gradient(135deg, rgba(255, 152, 0, 0.7) 0%, rgba(255, 152, 0, 0.8) 100%)',
|
||||
'linear-gradient(rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.4) 100%), linear-gradient(135deg, rgba(156, 39, 176, 0.7) 0%, rgba(156, 39, 176, 0.8) 100%)',
|
||||
'linear-gradient(rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.4) 100%), linear-gradient(135deg, rgba(244, 67, 54, 0.7) 0%, rgba(244, 67, 54, 0.8) 100%)',
|
||||
'linear-gradient(rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.4) 100%), linear-gradient(135deg, rgba(96, 125, 139, 0.7) 0%, rgba(96, 125, 139, 0.8) 100%)',
|
||||
'linear-gradient(rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.4) 100%), linear-gradient(135deg, rgba(233, 30, 99, 0.7) 0%, rgba(233, 30, 99, 0.8) 100%)',
|
||||
'linear-gradient(rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.4) 100%), linear-gradient(135deg, rgba(63, 81, 181, 0.7) 0%, rgba(156, 39, 176, 0.8) 100%)',
|
||||
]
|
||||
|
||||
// 计算背景渐变
|
||||
const backgroundGradient = computed(() => {
|
||||
const config = props.folderConfig || {}
|
||||
const settings = folderSettings.value
|
||||
|
||||
return config.gradient || settings.gradient || gradientOptions[0]
|
||||
})
|
||||
|
||||
// 计算图标
|
||||
const folderIcon = computed(() => {
|
||||
const config = props.folderConfig || {}
|
||||
const settings = folderSettings.value
|
||||
|
||||
return config.icon || settings.icon || defaultIcon
|
||||
})
|
||||
|
||||
// 计算图标颜色
|
||||
const iconColor = computed(() => {
|
||||
const config = props.folderConfig || {}
|
||||
const settings = folderSettings.value
|
||||
|
||||
return config.color || settings.color || defaultColor
|
||||
})
|
||||
|
||||
// 计算是否显示图标
|
||||
const shouldShowIcon = computed(() => {
|
||||
const config = props.folderConfig || {}
|
||||
const settings = folderSettings.value
|
||||
|
||||
return config.showIcon !== undefined ? config.showIcon : settings.showIcon !== undefined ? settings.showIcon : true
|
||||
})
|
||||
|
||||
// 监听props变化,更新本地设置
|
||||
watch(
|
||||
() => props.folderConfig,
|
||||
newConfig => {
|
||||
if (newConfig) {
|
||||
folderSettings.value = {
|
||||
...folderSettings.value,
|
||||
...newConfig,
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
|
||||
// 打开文件夹
|
||||
function openFolder() {
|
||||
emit('open', props.folderName)
|
||||
}
|
||||
|
||||
// 重命名文件夹
|
||||
function showRenameDialog() {
|
||||
newFolderName.value = props.folderName || ''
|
||||
renameDialog.value = true
|
||||
}
|
||||
|
||||
// 确认重命名
|
||||
async function confirmRename() {
|
||||
if (!newFolderName.value.trim()) {
|
||||
$toast.error(t('folder.folderNameCannotBeEmpty'))
|
||||
return
|
||||
}
|
||||
|
||||
if (newFolderName.value === props.folderName) {
|
||||
renameDialog.value = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
emit('rename', props.folderName, newFolderName.value)
|
||||
renameDialog.value = false
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文件夹
|
||||
async function deleteFolder() {
|
||||
const isConfirmed = await createConfirm({
|
||||
title: t('common.confirm'),
|
||||
content: t('folder.confirmDeleteFolder', { folderName: props.folderName }),
|
||||
})
|
||||
|
||||
if (!isConfirmed) return
|
||||
|
||||
try {
|
||||
emit('delete', props.folderName)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示设置对话框
|
||||
function showSettingDialog() {
|
||||
folderSettings.value = {
|
||||
background: props.folderConfig?.background || '',
|
||||
icon: props.folderConfig?.icon || defaultIcon,
|
||||
color: props.folderConfig?.color || defaultColor,
|
||||
gradient: props.folderConfig?.gradient || gradientOptions[0],
|
||||
showIcon: props.folderConfig?.showIcon !== undefined ? props.folderConfig.showIcon : true,
|
||||
}
|
||||
settingDialog.value = true
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
function saveSettings() {
|
||||
const config = {
|
||||
...props.folderConfig,
|
||||
...folderSettings.value,
|
||||
}
|
||||
|
||||
emit('update-config', props.folderName, config)
|
||||
settingDialog.value = false
|
||||
$toast.success(t('folder.folderSettingsSaved'))
|
||||
}
|
||||
|
||||
// 弹出菜单
|
||||
const dropdownItems = ref([
|
||||
{
|
||||
title: t('folder.settingAppearance'),
|
||||
value: 0,
|
||||
show: true,
|
||||
props: {
|
||||
prependIcon: 'mdi-palette',
|
||||
click: showSettingDialog,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('folder.rename'),
|
||||
value: 1,
|
||||
show: true,
|
||||
props: {
|
||||
prependIcon: 'mdi-pencil',
|
||||
click: showRenameDialog,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('folder.deleteFolder'),
|
||||
value: 2,
|
||||
show: true,
|
||||
props: {
|
||||
prependIcon: 'mdi-delete',
|
||||
color: 'error',
|
||||
click: deleteFolder,
|
||||
},
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<!-- 文件夹卡片 -->
|
||||
<VHover>
|
||||
<template #default="hover">
|
||||
<VCard
|
||||
v-bind="hover.props"
|
||||
:ripple="false"
|
||||
:width="props.width"
|
||||
:height="props.height"
|
||||
min-height="8.5rem"
|
||||
@click="openFolder"
|
||||
class="plugin-folder-card h-full"
|
||||
:class="{
|
||||
'plugin-folder-card--mobile': display.mobile,
|
||||
'plugin-folder-card--hover': hover.isHovering,
|
||||
}"
|
||||
>
|
||||
<template v-if="backgroundImage" #image>
|
||||
<VImg :src="backgroundImage" cover position="top"> </VImg>
|
||||
</template>
|
||||
|
||||
<!-- 背景遮罩(当有背景图片时) -->
|
||||
<div v-if="backgroundImage" class="plugin-folder-card__overlay" />
|
||||
|
||||
<!-- 背景渐变层 -->
|
||||
<div v-else class="plugin-folder-card__bg" :style="{ background: backgroundGradient }" />
|
||||
|
||||
<!-- 卡片内容 -->
|
||||
<div class="plugin-folder-card__content">
|
||||
<!-- 主体内容 -->
|
||||
<div class="plugin-folder-card__body" :class="{ 'plugin-folder-card__body--no-icon': !shouldShowIcon }">
|
||||
<!-- 文件夹图标 -->
|
||||
<div v-if="shouldShowIcon" class="plugin-folder-card__icon-container">
|
||||
<VIcon
|
||||
:icon="folderIcon"
|
||||
:size="display.mobile ? 56 : 72"
|
||||
:color="iconColor"
|
||||
:class="{ 'cursor-move': display.mdAndUp.value }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 文件夹信息 -->
|
||||
<div
|
||||
class="plugin-folder-card__info"
|
||||
:class="{ 'cursor-move': display.mdAndUp.value, 'plugin-folder-card__info--no-icon': !shouldShowIcon }"
|
||||
>
|
||||
<!-- 文件夹名称 -->
|
||||
<h3 class="plugin-folder-card__name">
|
||||
{{ props.folderName }}
|
||||
</h3>
|
||||
<!-- 插件数量 -->
|
||||
<p class="plugin-folder-card__count">{{ t('folder.pluginCount', { count: props.pluginCount }) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 更多菜单按钮 - 右下角 -->
|
||||
<div class="absolute top-0 right-0">
|
||||
<VMenu v-model="menuVisible" location="top end" :close-on-content-click="true">
|
||||
<template #activator="{ props: menuProps }">
|
||||
<IconBtn v-bind="menuProps" @click.stop>
|
||||
<VIcon size="small" icon="mdi-dots-vertical" class="text-white" />
|
||||
</IconBtn>
|
||||
</template>
|
||||
<VList>
|
||||
<VListItem
|
||||
v-for="(item, i) in dropdownItems"
|
||||
v-show="item.show"
|
||||
:key="i"
|
||||
:base-color="item.props.color"
|
||||
@click="item.props.click"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon :icon="item.props.prependIcon" size="16" />
|
||||
</template>
|
||||
<VListItemTitle class="text-body-2">{{ item.title }}</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</div>
|
||||
</div>
|
||||
</VCard>
|
||||
</template>
|
||||
</VHover>
|
||||
|
||||
<!-- 重命名对话框 -->
|
||||
<VDialog v-if="renameDialog" v-model="renameDialog" max-width="400">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-pencil" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ t('folder.renameFolder') }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn @click="renameDialog = false" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VTextField
|
||||
v-model="newFolderName"
|
||||
:label="t('folder.folderName')"
|
||||
variant="outlined"
|
||||
autofocus
|
||||
@keyup.enter="confirmRename"
|
||||
/>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn color="primary" prepend-icon="mdi-check" class="px-5" @click="confirmRename">确认</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- 设置对话框 -->
|
||||
<VDialog
|
||||
v-if="settingDialog"
|
||||
v-model="settingDialog"
|
||||
max-width="600"
|
||||
scrollable
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VDialogCloseBtn @click="settingDialog = false" />
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
<VIcon icon="mdi-palette" class="mr-2" />
|
||||
{{ t('folder.folderAppearanceSettings') }}
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<!-- 显示图标开关 -->
|
||||
<VCol cols="12">
|
||||
<VSwitch
|
||||
v-model="folderSettings.showIcon"
|
||||
:label="t('folder.showFolderIcon')"
|
||||
color="primary"
|
||||
hide-details
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- 图标选择 -->
|
||||
<VCol v-if="folderSettings.showIcon" cols="12" md="6">
|
||||
<VCardSubtitle class="pa-0 mb-2">{{ t('folder.icon') }}</VCardSubtitle>
|
||||
<div class="icon-grid">
|
||||
<VBtn
|
||||
v-for="icon in iconOptions"
|
||||
icon
|
||||
:key="icon"
|
||||
:variant="folderSettings.icon === icon ? 'tonal' : 'text'"
|
||||
:color="folderSettings.icon === icon ? 'primary' : 'default'"
|
||||
size="large"
|
||||
class="ma-1"
|
||||
@click="folderSettings.icon = icon"
|
||||
>
|
||||
<VIcon :icon="icon" size="24" />
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<!-- 颜色选择 -->
|
||||
<VCol v-if="folderSettings.showIcon" cols="12" md="6">
|
||||
<VCardSubtitle class="pa-0 mb-2">{{ t('folder.iconColor') }}</VCardSubtitle>
|
||||
<div class="color-grid">
|
||||
<VBtn
|
||||
v-for="color in colorOptions"
|
||||
:key="color"
|
||||
:variant="folderSettings.color === color ? 'tonal' : 'text'"
|
||||
:color="color"
|
||||
size="large"
|
||||
class="ma-1 color-btn"
|
||||
:style="{ backgroundColor: color }"
|
||||
@click="folderSettings.color = color"
|
||||
>
|
||||
<VIcon v-if="folderSettings.color === color" icon="mdi-check" color="white" />
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<!-- 渐变背景选择 -->
|
||||
<VCol cols="12">
|
||||
<VCardSubtitle class="pa-0 mb-2">{{ t('folder.backgroundGradient') }}</VCardSubtitle>
|
||||
<div class="gradient-grid">
|
||||
<VBtn
|
||||
v-for="(gradient, index) in gradientOptions"
|
||||
:key="index"
|
||||
:variant="folderSettings.gradient === gradient ? 'tonal' : 'text'"
|
||||
class="ma-1 gradient-btn"
|
||||
:style="{ background: gradient }"
|
||||
size="large"
|
||||
@click="folderSettings.gradient = gradient"
|
||||
>
|
||||
<VIcon v-if="folderSettings.gradient === gradient" icon="mdi-check" color="white" />
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<!-- 自定义背景图片 -->
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="folderSettings.background"
|
||||
:label="t('folder.customBackgroundImageURL')"
|
||||
placeholder="https://example.com/image.jpg"
|
||||
variant="outlined"
|
||||
:hint="t('folder.customBackgroundImageHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-image"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn color="primary" prepend-icon="mdi-content-save" class="px-5" @click="saveSettings">保存</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plugin-folder-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
&--hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
&__bg {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
inset: 0;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&__overlay {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
background: rgba(0, 0, 0, 60%);
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
block-size: 100%;
|
||||
padding-block-end: 12px;
|
||||
|
||||
.plugin-folder-card--mobile & {
|
||||
padding: 12px;
|
||||
padding-block-end: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 16px;
|
||||
padding-block: 0;
|
||||
padding-inline: 8px;
|
||||
|
||||
.plugin-folder-card--mobile & {
|
||||
gap: 12px;
|
||||
padding-block: 0;
|
||||
padding-inline: 4px;
|
||||
}
|
||||
|
||||
&--no-icon {
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
padding: 16px;
|
||||
gap: 0;
|
||||
|
||||
.plugin-folder-card--mobile & {
|
||||
padding: 12px;
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__icon-container {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__info {
|
||||
flex: 1;
|
||||
min-block-size: 0;
|
||||
text-align: start;
|
||||
|
||||
&--no-icon {
|
||||
flex: none;
|
||||
text-align: start;
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
-webkit-box-orient: vertical;
|
||||
color: white;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
line-height: 1.3;
|
||||
max-inline-size: none;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 50%);
|
||||
|
||||
.plugin-folder-card--mobile & {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.plugin-folder-card__info--no-icon & {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
margin-block-end: 4px;
|
||||
|
||||
.plugin-folder-card--mobile & {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__count {
|
||||
color: white;
|
||||
font-size: 0.85rem;
|
||||
margin-block: 2px 0;
|
||||
margin-inline: 0;
|
||||
opacity: 0.9;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 50%);
|
||||
|
||||
.plugin-folder-card--mobile & {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.plugin-folder-card__info--no-icon & {
|
||||
font-size: 0.9rem;
|
||||
margin-block-start: 0;
|
||||
|
||||
.plugin-folder-card--mobile & {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置对话框样式
|
||||
.icon-grid {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
|
||||
max-block-size: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.color-grid {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
|
||||
}
|
||||
|
||||
.gradient-grid {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
max-block-size: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.color-btn {
|
||||
border-radius: 8px !important;
|
||||
block-size: 60px !important;
|
||||
min-inline-size: 60px !important;
|
||||
}
|
||||
|
||||
.gradient-btn {
|
||||
border-radius: 8px !important;
|
||||
block-size: 60px !important;
|
||||
min-inline-size: 120px !important;
|
||||
}
|
||||
</style>
|
||||
183
src/components/cards/PluginMixedSortCard.vue
Normal file
183
src/components/cards/PluginMixedSortCard.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<script lang="ts" setup>
|
||||
import PluginCard from './PluginCard.vue'
|
||||
import PluginFolderCard from './PluginFolderCard.vue'
|
||||
|
||||
interface MixedSortItem {
|
||||
type: 'folder' | 'plugin'
|
||||
id: string
|
||||
data: any
|
||||
order: number
|
||||
}
|
||||
|
||||
interface Props {
|
||||
item: MixedSortItem
|
||||
pluginStatistics?: { [key: string]: number }
|
||||
pluginActions?: { [key: string]: boolean }
|
||||
showRemoveButton?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
pluginStatistics: () => ({}),
|
||||
pluginActions: () => ({}),
|
||||
showRemoveButton: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
openFolder: [folderName: string]
|
||||
deleteFolder: [folderName: string]
|
||||
renameFolder: [oldName: string, newName: string]
|
||||
updateFolderConfig: [folderName: string, config: any]
|
||||
refreshData: []
|
||||
actionDone: [pluginId: string]
|
||||
removeFromFolder: [pluginId: string]
|
||||
dropToFolder: [event: DragEvent, folderName: string]
|
||||
}>()
|
||||
|
||||
// 拖拽事件处理
|
||||
function handleDragOver(event: DragEvent) {
|
||||
// 只有当拖拽的是插件时才允许放入文件夹
|
||||
if (props.item.type === 'folder') {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
event.dataTransfer!.dropEffect = 'move'
|
||||
const target = event.currentTarget as HTMLElement
|
||||
target.classList.add('drag-over')
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragEnter(event: DragEvent) {
|
||||
if (props.item.type === 'folder') {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragLeave(event: DragEvent) {
|
||||
if (props.item.type === 'folder') {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const target = event.currentTarget as HTMLElement
|
||||
target.classList.remove('drag-over')
|
||||
}
|
||||
}
|
||||
|
||||
function handleDropToFolder(event: DragEvent) {
|
||||
if (props.item.type === 'folder') {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const target = event.currentTarget as HTMLElement
|
||||
target.classList.remove('drag-over')
|
||||
|
||||
emit('dropToFolder', event, props.item.id)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mixed-sort-card-wrapper h-full">
|
||||
<!-- 文件夹卡片 -->
|
||||
<div
|
||||
v-if="item.type === 'folder'"
|
||||
class="drop-zone h-full"
|
||||
:data-plugin-id="item.id"
|
||||
@dragover="handleDragOver"
|
||||
@dragenter="handleDragEnter"
|
||||
@dragleave="handleDragLeave"
|
||||
@drop="handleDropToFolder"
|
||||
>
|
||||
<PluginFolderCard
|
||||
:folder-name="item.data.name"
|
||||
:plugin-count="item.data.pluginCount"
|
||||
:folder-config="item.data.config"
|
||||
@open="$emit('openFolder', item.id)"
|
||||
@delete="$emit('deleteFolder', item.id)"
|
||||
@rename="(oldName, newName) => $emit('renameFolder', oldName, newName)"
|
||||
@update-config="(folderName, config) => $emit('updateFolderConfig', folderName, config)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 插件卡片 -->
|
||||
<div v-else-if="item.type === 'plugin'" class="plugin-item-wrapper h-full" :data-plugin-id="item.id">
|
||||
<PluginCard
|
||||
:count="pluginStatistics[item.id] || 0"
|
||||
:plugin="item.data"
|
||||
:action="pluginActions[item.id] || false"
|
||||
@remove="$emit('refreshData')"
|
||||
@save="$emit('refreshData')"
|
||||
@action-done="$emit('actionDone', item.id)"
|
||||
/>
|
||||
|
||||
<!-- 移出文件夹按钮(仅在文件夹内显示) -->
|
||||
<VBtn
|
||||
v-if="showRemoveButton"
|
||||
icon="mdi-folder-remove"
|
||||
variant="text"
|
||||
color="warning"
|
||||
size="small"
|
||||
class="remove-from-folder-btn"
|
||||
@click="$emit('removeFromFolder', item.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mixed-sort-card-wrapper {
|
||||
block-size: 100%;
|
||||
inline-size: 100%;
|
||||
|
||||
// 确保拖拽时的边界清晰
|
||||
&.sortable-chosen {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.sortable-ghost {
|
||||
border: 2px dashed #2196f3;
|
||||
border-radius: 16px;
|
||||
background: rgba(33, 150, 243, 10%);
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽相关样式
|
||||
.drop-zone {
|
||||
position: relative;
|
||||
isolation: isolate; // 创建新的层叠上下文
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.drag-over {
|
||||
border: 2px dashed #2196f3;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 0 20px rgba(33, 150, 243, 50%);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-item-wrapper {
|
||||
position: relative;
|
||||
isolation: isolate; // 创建新的层叠上下文
|
||||
|
||||
.remove-from-folder-btn {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
border-radius: 50%;
|
||||
backdrop-filter: blur(4px);
|
||||
background: rgba(255, 255, 255, 10%);
|
||||
inset-block-start: 4px;
|
||||
inset-inline-end: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover .remove-from-folder-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽时的样式优化
|
||||
.mixed-sort-card-wrapper.sortable-drag {
|
||||
.remove-from-folder-btn {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -11,7 +11,11 @@ import api from '@/api'
|
||||
import type { Site, SiteStatistic, SiteUserData } from '@/api/types'
|
||||
import { isNullOrEmptyObject } from '@/@core/utils'
|
||||
import { formatFileSize } from '@/@core/utils/formatters'
|
||||
import { useConfirm } from 'vuetify-use-dialog'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -224,7 +228,7 @@ onMounted(() => {
|
||||
<!-- 顶部:图标和站点名称 -->
|
||||
<div class="flex items-center mb-1">
|
||||
<!-- 站点图标 -->
|
||||
<VAvatar tile rounded="lg" size="32" class="me-2 cursor-move">
|
||||
<VAvatar tile rounded="lg" size="32" class="me-2" :class="{ 'cursor-move': display.mdAndUp.value }">
|
||||
<VImg :src="siteIcon" class="w-full h-full" :alt="cardProps.site?.name" cover>
|
||||
<template #placeholder>
|
||||
<div class="w-full h-full">
|
||||
|
||||
@@ -16,6 +16,10 @@ import { useToast } from 'vue-toast-notification'
|
||||
import { isNullOrEmptyObject } from '@/@core/utils'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { storageIconDict } from '@/api/constants'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -200,9 +204,18 @@ function onClose() {
|
||||
@close="aListConfigDialog = false"
|
||||
@done="handleDone"
|
||||
/>
|
||||
<VDialog v-if="customConfigDialog" v-model="customConfigDialog" scrollable max-width="30rem">
|
||||
<VDialog
|
||||
v-if="customConfigDialog"
|
||||
v-model="customConfigDialog"
|
||||
scrollable
|
||||
max-width="30rem"
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-cog" />
|
||||
</template>
|
||||
<VCardTitle>{{ t('storage.custom') }}</VCardTitle>
|
||||
<VDialogCloseBtn v-model="customConfigDialog" />
|
||||
</VCardItem>
|
||||
@@ -215,16 +228,21 @@ function onClose() {
|
||||
:label="t('storage.type')"
|
||||
:hint="t('storage.customTypeHint')"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-database"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField v-model="customName" :label="t('storage.name')" persistent-hint active />
|
||||
<VTextField
|
||||
v-model="customName"
|
||||
:label="t('storage.name')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn @click="handleDone" variant="elevated" prepend-icon="mdi-content-save" class="px-5">
|
||||
<VBtn @click="handleDone" prepend-icon="mdi-content-save" class="px-5">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { useConfirm } from 'vuetify-use-dialog'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue'
|
||||
import SubscribeFilesDialog from '../dialog/SubscribeFilesDialog.vue'
|
||||
import SubscribeShareDialog from '../dialog/SubscribeShareDialog.vue'
|
||||
@@ -9,6 +9,10 @@ import api from '@/api'
|
||||
import type { Subscribe } from '@/api/types'
|
||||
import router from '@/router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -348,7 +352,11 @@ function onSubscribeEditRemove() {
|
||||
</template>
|
||||
<div>
|
||||
<VCardText class="flex items-center pt-3 pb-2">
|
||||
<div class="h-auto w-14 flex-shrink-0 overflow-hidden rounded-md cursor-move" v-if="imageLoaded">
|
||||
<div
|
||||
class="h-auto w-14 flex-shrink-0 overflow-hidden rounded-md"
|
||||
v-if="imageLoaded"
|
||||
:class="{ 'cursor-move': display.mdAndUp.value }"
|
||||
>
|
||||
<VImg :src="posterUrl" aspect-ratio="2/3" cover>
|
||||
<template #placeholder>
|
||||
<div class="w-full h-full">
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Subscribe, User } from '@/api/types'
|
||||
import { useUserStore } from '@/stores'
|
||||
import avatar1 from '@images/avatars/avatar-1.png'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { useConfirm } from 'vuetify-use-dialog'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import UserAddEditDialog from '@/components/dialog/UserAddEditDialog.vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { Workflow } from '@/api/types'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { useConfirm } from 'vuetify-use-dialog'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import WorkflowAddEditDialog from '@/components/dialog/WorkflowAddEditDialog.vue'
|
||||
import WorkflowActionsDialog from '@/components/dialog/WorkflowActionsDialog.vue'
|
||||
import api from '@/api'
|
||||
|
||||
@@ -134,69 +134,75 @@ onMounted(() => {
|
||||
<template>
|
||||
<VDialog max-width="35rem" scrollable>
|
||||
<VCard>
|
||||
<VCardTitle class="py-4 me-12">
|
||||
<VIcon icon="mdi-download" class="me-2" />
|
||||
<span v-if="title">{{ torrent?.site_name }} - {{ title }}</span>
|
||||
<span v-else>{{ t('dialog.addDownload.confirmDownload') }}</span>
|
||||
</VCardTitle>
|
||||
<VCardItem class="py-2">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-monitor-arrow-down-variant" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ t('dialog.addDownload.confirmDownload') }}</VCardTitle>
|
||||
<VCardSubtitle>{{ torrent?.site_name }} - {{ title }}</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VDivider />
|
||||
<VList lines="one">
|
||||
<VListItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-web"></VIcon>
|
||||
</template>
|
||||
<VListItemTitle>
|
||||
<span class="whitespace-break-spaces me-2">{{ torrent?.title }}</span>
|
||||
<span class="text-green-700 ms-2 text-sm">↑{{ torrent?.seeders }}</span>
|
||||
<span class="text-orange-700 ms-2 text-sm">↓{{ torrent?.peers }}</span>
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem v-if="torrent?.description">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-subtitles-outline"></VIcon>
|
||||
</template>
|
||||
<VListItemTitle>
|
||||
<span class="text-body-2 whitespace-break-spaces">{{ torrent?.description }}</span>
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem v-if="torrent?.size">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-database"></VIcon>
|
||||
</template>
|
||||
<VListItemTitle>
|
||||
<span class="text-body-2">
|
||||
<VChip variant="tonal" label>
|
||||
{{ formatFileSize(torrent?.size || 0) }}
|
||||
</VChip>
|
||||
</span>
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
<VRow class="px-7">
|
||||
<VCol cols="12" md="4">
|
||||
<VSelect
|
||||
v-model="selectedDownloader"
|
||||
:items="downloaderOptions"
|
||||
size="small"
|
||||
:label="t('dialog.addDownload.downloader')"
|
||||
variant="underlined"
|
||||
:placeholder="t('dialog.addDownload.defaultPlaceholder')"
|
||||
density="compact"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="8">
|
||||
<VCombobox
|
||||
v-model="selectedDirectory"
|
||||
:items="targetDirectories"
|
||||
:label="t('dialog.addDownload.saveDirectory')"
|
||||
size="small"
|
||||
:placeholder="t('dialog.addDownload.autoPlaceholder')"
|
||||
variant="underlined"
|
||||
density="compact"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VCardText>
|
||||
<VList lines="one">
|
||||
<VListItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-web"></VIcon>
|
||||
</template>
|
||||
<VListItemTitle>
|
||||
<span class="whitespace-break-spaces me-2">{{ torrent?.title }}</span>
|
||||
<span class="text-green-700 ms-2 text-sm">↑{{ torrent?.seeders }}</span>
|
||||
<span class="text-orange-700 ms-2 text-sm">↓{{ torrent?.peers }}</span>
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem v-if="torrent?.description">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-subtitles-outline"></VIcon>
|
||||
</template>
|
||||
<VListItemTitle>
|
||||
<span class="text-body-2 whitespace-break-spaces">{{ torrent?.description }}</span>
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem v-if="torrent?.size">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-database"></VIcon>
|
||||
</template>
|
||||
<VListItemTitle>
|
||||
<span class="text-body-2">
|
||||
<VChip variant="tonal" label>
|
||||
{{ formatFileSize(torrent?.size || 0) }}
|
||||
</VChip>
|
||||
</span>
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
<VRow class="px-5">
|
||||
<VCol cols="12" md="6">
|
||||
<VSelect
|
||||
v-model="selectedDownloader"
|
||||
:items="downloaderOptions"
|
||||
size="small"
|
||||
:label="t('dialog.addDownload.downloader')"
|
||||
variant="underlined"
|
||||
:placeholder="t('dialog.addDownload.defaultPlaceholder')"
|
||||
density="comfortable"
|
||||
prepend-inner-icon="mdi-download"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="selectedDirectory"
|
||||
:items="targetDirectories"
|
||||
:label="t('dialog.addDownload.saveDirectory')"
|
||||
size="small"
|
||||
:placeholder="t('dialog.addDownload.autoPlaceholder')"
|
||||
variant="underlined"
|
||||
density="comfortable"
|
||||
prepend-inner-icon="mdi-folder"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardText class="text-center">
|
||||
<VBtn variant="elevated" :disabled="loading" @click="addDownload" :prepend-icon="icon" class="px-5">
|
||||
{{ buttonText }}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import api from '@/api'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 多语言支持
|
||||
const { t } = useI18n()
|
||||
@@ -66,9 +70,18 @@ async function savaAlistConfig() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog width="50rem" scrollable max-height="85vh">
|
||||
<VCard :title="t('dialog.alistConfig.title')">
|
||||
<VDialog width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-cog-outline" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>
|
||||
{{ t('dialog.alistConfig.title') }}
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
@@ -77,6 +90,7 @@ async function savaAlistConfig() {
|
||||
:hint="t('dialog.alistConfig.serverUrl')"
|
||||
:label="t('dialog.alistConfig.serverUrl')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-server"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
@@ -86,6 +100,7 @@ async function savaAlistConfig() {
|
||||
:label="t('dialog.alistConfig.loginType')"
|
||||
:hint="t('dialog.alistConfig.loginType')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-login"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4" v-if="loginType == 'username'">
|
||||
@@ -94,6 +109,7 @@ async function savaAlistConfig() {
|
||||
:hint="t('dialog.alistConfig.username')"
|
||||
:label="t('dialog.alistConfig.username')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-account"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4" v-if="loginType == 'username'">
|
||||
@@ -103,6 +119,7 @@ async function savaAlistConfig() {
|
||||
:hint="t('dialog.alistConfig.password')"
|
||||
:label="t('dialog.alistConfig.password')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-lock"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="8" v-if="loginType == 'token'">
|
||||
@@ -111,16 +128,17 @@ async function savaAlistConfig() {
|
||||
:hint="t('dialog.alistConfig.loginTypeOptions.token')"
|
||||
:label="t('dialog.alistConfig.loginTypeOptions.token')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn variant="tonal" color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
||||
<VBtn color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
||||
{{ t('dialog.alistConfig.reset') }}
|
||||
</VBtn>
|
||||
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
||||
<VSpacer />
|
||||
<VBtn @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
||||
{{ t('dialog.alistConfig.complete') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import api from '@/api'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 多语言支持
|
||||
const { t } = useI18n()
|
||||
@@ -106,11 +110,20 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog width="40rem" scrollable max-height="85vh">
|
||||
<VCard :title="t('dialog.aliyunAuth.loginTitle')">
|
||||
<VDialog width="40rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VCardText class="pt-2 flex flex-col items-center">
|
||||
<div class="my-6 rounded text-center p-3 border">
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-qrcode" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>
|
||||
{{ t('dialog.aliyunAuth.loginTitle') }}
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText class="pt-2 flex flex-col items-center justify-center">
|
||||
<div class="mt-6 rounded text-center p-3 border">
|
||||
<VImg class="mx-auto" :src="qrCodeUrl" width="200" height="200">
|
||||
<template #placeholder>
|
||||
<div class="w-full h-full">
|
||||
@@ -119,16 +132,18 @@ onUnmounted(() => {
|
||||
</template>
|
||||
</VImg>
|
||||
</div>
|
||||
<VAlert variant="tonal" :type="alertType" class="my-4 text-center" :text="text">
|
||||
<template #prepend />
|
||||
</VAlert>
|
||||
<div>
|
||||
<VAlert variant="tonal" :type="alertType" class="my-4 text-center" :text="text">
|
||||
<template #prepend />
|
||||
</VAlert>
|
||||
</div>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn variant="tonal" color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
||||
<VBtn color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
||||
{{ t('dialog.aliyunAuth.reset') }}
|
||||
</VBtn>
|
||||
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
||||
<VSpacer />
|
||||
<VBtn @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
||||
{{ t('dialog.aliyunAuth.complete') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -25,14 +25,20 @@ function handleImport() {
|
||||
|
||||
<template>
|
||||
<VDialog width="40rem" scrollable max-height="85vh">
|
||||
<VCard :title="props.title">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-code-json" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ props.title }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VCardText class="pt-2">
|
||||
<VTextarea v-model="codeString" />
|
||||
<VTextarea v-model="codeString" prepend-inner-icon="mdi-code-json" />
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn variant="elevated" @click="handleImport" prepend-icon="mdi-import" class="px-5 me-3">
|
||||
<VBtn @click="handleImport" prepend-icon="mdi-import" class="px-5 me-3">
|
||||
{{ t('dialog.importCode.import') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -161,18 +161,12 @@ onBeforeMount(async () => {
|
||||
</div>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn v-if="props.plugin?.has_page" @click="emit('switch')" variant="outlined" color="info">
|
||||
<VBtn v-if="props.plugin?.has_page" @click="emit('switch')" color="info">
|
||||
{{ t('dialog.pluginConfig.viewData') }}
|
||||
</VBtn>
|
||||
<VSpacer />
|
||||
<!-- 只有Vuetify模式显示默认保存按钮,Vue模式由组件内部控制 -->
|
||||
<VBtn
|
||||
v-if="renderMode === 'vuetify'"
|
||||
@click="savePluginConf"
|
||||
variant="elevated"
|
||||
prepend-icon="mdi-content-save"
|
||||
class="px-5"
|
||||
>
|
||||
<VBtn v-if="renderMode === 'vuetify'" @click="savePluginConf" prepend-icon="mdi-content-save" class="px-5">
|
||||
保存
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
import api from '@/api'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed } from 'vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -9,6 +14,16 @@ const $toast = useToast()
|
||||
|
||||
// 插件仓库设置字符串
|
||||
const repoString = ref('')
|
||||
// 用于显示的仓库地址数组
|
||||
const repoArray = ref<string[]>([])
|
||||
|
||||
// 计算属性:在数组和换行符分隔的字符串之间转换
|
||||
const displayRepos = computed({
|
||||
get: () => repoArray.value.join('\n'),
|
||||
set: (value: string) => {
|
||||
repoArray.value = value.split('\n').filter((repo: string) => repo.trim() !== '')
|
||||
},
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['save', 'close'])
|
||||
@@ -17,7 +32,10 @@ const emit = defineEmits(['save', 'close'])
|
||||
async function queryMarketRepoSetting() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/PLUGIN_MARKET')
|
||||
if (result && result.data && result.data.value) repoString.value = result.data.value
|
||||
if (result && result.data && result.data.value) {
|
||||
repoString.value = result.data.value
|
||||
repoArray.value = result.data.value.split(',').filter((repo: string) => repo.trim() !== '')
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
@@ -26,8 +44,9 @@ async function queryMarketRepoSetting() {
|
||||
// 保存设置
|
||||
async function saveHandle() {
|
||||
try {
|
||||
// 用户名密码
|
||||
const result: { [key: string]: any } = await api.post('system/setting/PLUGIN_MARKET', repoString.value)
|
||||
// 将数组转换为逗号分隔的字符串
|
||||
const repoStringToSave = repoArray.value.join(',')
|
||||
const result: { [key: string]: any } = await api.post('system/setting/PLUGIN_MARKET', repoStringToSave)
|
||||
|
||||
if (result.success) {
|
||||
$toast.success(t('dialog.pluginMarketSetting.saveSuccess'))
|
||||
@@ -44,7 +63,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog width="50rem" scrollable max-height="85vh">
|
||||
<VDialog width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
@@ -53,17 +72,19 @@ onMounted(() => {
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText class="pt-2">
|
||||
<VTextarea
|
||||
v-model="repoString"
|
||||
v-model="displayRepos"
|
||||
:placeholder="t('dialog.pluginMarketSetting.repoPlaceholder')"
|
||||
:hint="t('dialog.pluginMarketSetting.repoHint')"
|
||||
persistent-hint
|
||||
auto-grow
|
||||
/>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn variant="elevated" @click="saveHandle" prepend-icon="mdi-content-save-check" class="px-5 me-3">
|
||||
<VBtn @click="saveHandle" prepend-icon="mdi-content-save-check" class="px-5 me-3">
|
||||
{{ t('dialog.pluginMarketSetting.save') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import api from '@/api'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 多语言支持
|
||||
const { t } = useI18n()
|
||||
@@ -53,32 +57,44 @@ async function handleReset() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog width="50rem" scrollable max-height="85vh">
|
||||
<VCard :title="t('dialog.rcloneConfig.title')">
|
||||
<VDialog width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-cog-outline" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>
|
||||
{{ t('dialog.rcloneConfig.title') }}
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VTextField v-model="props.conf.filepath" :label="t('dialog.rcloneConfig.filePath')" />
|
||||
<VTextField
|
||||
v-model="props.conf.filepath"
|
||||
:label="t('dialog.rcloneConfig.filePath')"
|
||||
prepend-inner-icon="mdi-file-document"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VAceEditor
|
||||
v-model:value="props.conf.content"
|
||||
lang="ini"
|
||||
theme="monokai"
|
||||
style="block-size: 30rem"
|
||||
class="rounded"
|
||||
class="rounded h-full min-h-[30rem]"
|
||||
>
|
||||
</VAceEditor>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn variant="tonal" color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
||||
<VBtn color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
||||
{{ t('dialog.rcloneConfig.reset') }}
|
||||
</VBtn>
|
||||
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
||||
<VSpacer />
|
||||
<VBtn @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
||||
{{ t('dialog.rcloneConfig.complete') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -82,15 +82,18 @@ const storageOptions = computed(() => {
|
||||
|
||||
// 标题
|
||||
const dialogTitle = computed(() => {
|
||||
return t('dialog.reorganize.manualTitle')
|
||||
})
|
||||
|
||||
// 副标题
|
||||
const dialogSubtitle = computed(() => {
|
||||
if (props.items) {
|
||||
if (props.items.length > 1) return t('dialog.reorganize.multipleItemsTitle', { count: props.items.length })
|
||||
return t('dialog.reorganize.singleItemTitle', { path: props.items[0].path })
|
||||
} else if (props.logids) {
|
||||
return t('dialog.reorganize.multipleItemsTitle', { count: props.logids.length })
|
||||
}
|
||||
return t('dialog.reorganize.manualTitle')
|
||||
})
|
||||
|
||||
// 禁用指定集数
|
||||
const disableEpisodeDetail = computed(() => {
|
||||
if (props.items) {
|
||||
@@ -250,7 +253,12 @@ onUnmounted(() => {
|
||||
|
||||
<template>
|
||||
<VDialog scrollable max-width="45rem" :fullscreen="!display.mdAndUp.value">
|
||||
<VCard :title="dialogTitle">
|
||||
<VCard>
|
||||
<VCardItem class="py-2">
|
||||
<template #prepend> <VIcon icon="mdi-folder-move" class="me-2" /> </template>
|
||||
<VCardTitle>{{ dialogTitle }}</VCardTitle>
|
||||
<VCardSubtitle>{{ dialogSubtitle }}</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
@@ -264,6 +272,7 @@ onUnmounted(() => {
|
||||
:placeholder="t('dialog.reorganize.targetPathPlaceholder')"
|
||||
:hint="t('dialog.reorganize.targetStorageHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-harddisk"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -273,6 +282,7 @@ onUnmounted(() => {
|
||||
:items="transferTypeOptions"
|
||||
:hint="t('dialog.reorganize.transferTypeHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-swap-horizontal"
|
||||
>
|
||||
<template v-slot:selection="{ item }">
|
||||
{{ transferForm.transfer_type === '' ? t('dialog.reorganize.auto') : item.title }}
|
||||
@@ -287,6 +297,7 @@ onUnmounted(() => {
|
||||
:placeholder="t('dialog.reorganize.targetPathPlaceholder')"
|
||||
:hint="t('dialog.reorganize.targetPathHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-folder-outline"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -302,6 +313,7 @@ onUnmounted(() => {
|
||||
]"
|
||||
:hint="t('dialog.reorganize.mediaTypeHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-movie-open"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -315,6 +327,7 @@ onUnmounted(() => {
|
||||
append-inner-icon="mdi-magnify"
|
||||
:hint="t('dialog.reorganize.mediaIdHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-identifier"
|
||||
@click:append-inner="mediaSelectorDialog = true"
|
||||
/>
|
||||
<VTextField
|
||||
@@ -327,6 +340,7 @@ onUnmounted(() => {
|
||||
append-inner-icon="mdi-magnify"
|
||||
:hint="t('dialog.reorganize.mediaIdHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-identifier"
|
||||
@click:append-inner="mediaSelectorDialog = true"
|
||||
/>
|
||||
</VCol>
|
||||
@@ -339,6 +353,7 @@ onUnmounted(() => {
|
||||
:placeholder="t('dialog.reorganize.episodeGroupPlaceholder')"
|
||||
:hint="t('dialog.reorganize.episodeGroupHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-view-list"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
@@ -348,6 +363,7 @@ onUnmounted(() => {
|
||||
:items="seasonItems"
|
||||
:hint="t('dialog.reorganize.seasonHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-calendar"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
@@ -358,6 +374,7 @@ onUnmounted(() => {
|
||||
:placeholder="t('dialog.reorganize.episodeDetailPlaceholder')"
|
||||
:hint="t('dialog.reorganize.episodeDetailHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-playlist-play"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -367,6 +384,7 @@ onUnmounted(() => {
|
||||
:placeholder="t('dialog.reorganize.episodeFormatPlaceholder')"
|
||||
:hint="t('dialog.reorganize.episodeFormatHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-format-text"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -376,6 +394,7 @@ onUnmounted(() => {
|
||||
:placeholder="t('dialog.reorganize.episodeOffsetPlaceholder')"
|
||||
:hint="t('dialog.reorganize.episodeOffsetHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-numeric"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -387,6 +406,7 @@ onUnmounted(() => {
|
||||
:placeholder="t('dialog.reorganize.episodePartPlaceholder')"
|
||||
:hint="t('dialog.reorganize.episodePartHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-file-multiple"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -397,6 +417,7 @@ onUnmounted(() => {
|
||||
placeholder="0"
|
||||
:hint="t('dialog.reorganize.minFileSizeHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-file-document-outline"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -438,10 +459,10 @@ onUnmounted(() => {
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VSpacer />
|
||||
<VBtn variant="elevated" color="success" @click="transfer(true)" prepend-icon="mdi-plus" class="px-5">
|
||||
<VBtn color="success" @click="transfer(true)" prepend-icon="mdi-plus" class="px-5">
|
||||
{{ t('dialog.reorganize.addToQueue') }}
|
||||
</VBtn>
|
||||
<VBtn variant="elevated" @click="transfer(false)" prepend-icon="mdi-arrow-right-bold" class="px-5">
|
||||
<VBtn @click="transfer(false)" prepend-icon="mdi-arrow-right-bold" class="px-5">
|
||||
{{ t('dialog.reorganize.reorganizeNow') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -6,6 +6,10 @@ import { NavMenu } from '@/@layouts/types'
|
||||
import { useUserStore } from '@/stores'
|
||||
import SearchSiteDialog from '@/components/dialog/SearchSiteDialog.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 多语言支持
|
||||
const { t } = useI18n()
|
||||
@@ -302,29 +306,24 @@ onMounted(() => {
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<VDialog v-model="dialog" max-width="42rem" scrollable maxHeight="85vh">
|
||||
<VDialog v-model="dialog" max-width="42rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
<VCard class="search-dialog">
|
||||
<!-- 搜索输入框 -->
|
||||
<VCardItem class="pa-4 pa-sm-5 search-box-container">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-magnify" color="primary" size="x-large" />
|
||||
</template>
|
||||
<VCombobox
|
||||
ref="searchWordInput"
|
||||
v-model="searchWord"
|
||||
density="comfortable"
|
||||
variant="outlined"
|
||||
class="search-input"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
append-inner-icon="mdi-close"
|
||||
@click:append-inner="emit('close')"
|
||||
:placeholder="t('dialog.searchBar.searchPlaceholder')"
|
||||
@keydown.enter="searchMedia('media')"
|
||||
hide-details
|
||||
clearable
|
||||
/>
|
||||
<template #append>
|
||||
<IconBtn>
|
||||
<VIcon icon="mdi-close" color="primary" @click="emit('close')" size="x-large" />
|
||||
</IconBtn>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VDivider />
|
||||
|
||||
@@ -58,23 +58,16 @@ const filteredSites = computed(() => {
|
||||
<!-- Site Selection Dialog -->
|
||||
<VDialog max-width="40rem" fullscreen-mobile>
|
||||
<VCard class="site-dialog">
|
||||
<VCardTitle class="d-flex align-center pa-4">
|
||||
<span class="text-h6 font-weight-medium">{{ t('dialog.searchSite.selectSites') }}</span>
|
||||
<VSpacer />
|
||||
<VTextField
|
||||
v-model="siteFilter"
|
||||
:placeholder="t('dialog.searchSite.siteSearch')"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
class="ml-4"
|
||||
style="max-inline-size: 200px"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
clearable
|
||||
/>
|
||||
</VCardTitle>
|
||||
<VDivider class="search-divider" />
|
||||
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-web-check" />
|
||||
</template>
|
||||
<VCardTitle>
|
||||
{{ t('dialog.searchSite.selectSites') }}
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VDivider />
|
||||
<VCardText style="max-block-size: 420px" class="overflow-y-auto px-4 py-4">
|
||||
<!-- 站点列表 -->
|
||||
<div v-if="filteredSites.length > 0">
|
||||
@@ -163,27 +156,16 @@ const filteredSites = computed(() => {
|
||||
</div>
|
||||
</VCardText>
|
||||
|
||||
<VDivider class="search-divider" />
|
||||
|
||||
<VCardActions class="pa-4">
|
||||
<VCardActions class="pt-3">
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
color="grey-darken-1"
|
||||
variant="text"
|
||||
@click="emit('close')"
|
||||
class="mr-2 d-flex align-center justify-center"
|
||||
>
|
||||
{{ t('dialog.searchSite.cancel') }}
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
:disabled="selectedSites.length === 0"
|
||||
@click="emit('search', selectedSites)"
|
||||
prepend-icon="mdi-magnify"
|
||||
class="d-flex align-center justify-center px-5"
|
||||
>
|
||||
{{ t('dialog.searchSite.confirm') }}
|
||||
{{ t('common.search') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
|
||||
@@ -148,11 +148,14 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<VDialog scrollable :close-on-back="false" eager max-width="45rem" :fullscreen="!display.mdAndUp.value">
|
||||
<VCard
|
||||
:title="`${props.oper === 'add' ? t('site.actions.add') : t('site.actions.edit')}${t('site.title')}${
|
||||
props.oper !== 'add' ? ` - ${siteForm.name}` : ''
|
||||
}`"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem :class="props.oper === 'add' ? 'py-3' : 'py-2'">
|
||||
<template #prepend>
|
||||
<VIcon :icon="oper == 'add' ? 'mdi-web-plus' : 'mdi-web'" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ `${props.oper === 'add' ? t('site.actions.add') : t('site.actions.edit')}` }}</VCardTitle>
|
||||
<VCardSubtitle>{{ siteForm.name }}</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
@@ -165,6 +168,7 @@ onMounted(async () => {
|
||||
:rules="[requiredValidator]"
|
||||
:hint="t('site.hints.url')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-web"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="6" md="3">
|
||||
@@ -175,6 +179,7 @@ onMounted(async () => {
|
||||
:rules="[requiredValidator]"
|
||||
:hint="t('site.hints.priority')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-priority-high"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="6" md="3">
|
||||
@@ -184,6 +189,7 @@ onMounted(async () => {
|
||||
:label="t('site.fields.status')"
|
||||
:hint="t('site.hints.status')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-toggle-switch"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -194,6 +200,7 @@ onMounted(async () => {
|
||||
:label="t('site.fields.rss')"
|
||||
:hint="t('site.hints.rss')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-rss"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
@@ -202,6 +209,7 @@ onMounted(async () => {
|
||||
:label="t('site.fields.timeout')"
|
||||
:hint="t('site.hints.timeout')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-timer"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="6" md="3">
|
||||
@@ -211,6 +219,7 @@ onMounted(async () => {
|
||||
:items="downloaderOptions"
|
||||
:hint="t('site.hints.downloader')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-download"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -237,6 +246,7 @@ onMounted(async () => {
|
||||
:label="t('site.fields.cookie')"
|
||||
:hint="t('site.hints.cookie')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-cookie"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -245,6 +255,7 @@ onMounted(async () => {
|
||||
:label="t('site.fields.userAgent')"
|
||||
:hint="t('site.hints.userAgent')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-web-box"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -257,6 +268,7 @@ onMounted(async () => {
|
||||
:label="t('site.fields.authorization')"
|
||||
:hint="t('site.hints.authorization')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -265,6 +277,7 @@ onMounted(async () => {
|
||||
:label="t('site.fields.apiKey')"
|
||||
:hint="t('site.hints.apiKey')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-api"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -283,6 +296,7 @@ onMounted(async () => {
|
||||
:rules="[numberValidator]"
|
||||
:hint="t('site.hints.limitInterval')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-clock-outline"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
@@ -292,6 +306,7 @@ onMounted(async () => {
|
||||
:rules="[numberValidator]"
|
||||
:hint="t('site.hints.limitCount')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-counter"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
@@ -301,6 +316,7 @@ onMounted(async () => {
|
||||
:rules="[numberValidator]"
|
||||
:hint="t('site.hints.limitSeconds')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-timer-sand"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -326,24 +342,10 @@ onMounted(async () => {
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
v-if="props.oper === 'add'"
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
@click="addSite"
|
||||
prepend-icon="mdi-plus"
|
||||
class="px-5"
|
||||
>
|
||||
<VBtn v-if="props.oper === 'add'" color="primary" @click="addSite" prepend-icon="mdi-plus" class="px-5">
|
||||
{{ t('site.actions.add') }}
|
||||
</VBtn>
|
||||
<VBtn
|
||||
v-else
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
@click="updateSiteInfo"
|
||||
prepend-icon="mdi-content-save"
|
||||
class="px-5"
|
||||
>
|
||||
<VBtn v-else color="primary" @click="updateSiteInfo" prepend-icon="mdi-content-save" class="px-5">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -71,7 +71,7 @@ async function updateSiteCookie() {
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<VDialog max-width="30rem">
|
||||
<VDialog max-width="30rem" scrollable>
|
||||
<!-- Dialog Content -->
|
||||
<VCard :title="t('dialog.siteCookieUpdate.title')">
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
@@ -102,7 +102,6 @@ async function updateSiteCookie() {
|
||||
<VCardActions class="mx-auto">
|
||||
<VBtn
|
||||
size="large"
|
||||
variant="elevated"
|
||||
@click="updateSiteCookie"
|
||||
:disabled="updateButtonDisable"
|
||||
:loading="updateButtonDisable"
|
||||
|
||||
@@ -153,6 +153,7 @@ onMounted(() => {
|
||||
density="compact"
|
||||
:label="t('dialog.siteResource.searchKeyword')"
|
||||
clearable
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="6" md="5">
|
||||
@@ -165,10 +166,13 @@ onMounted(() => {
|
||||
:label="t('dialog.siteResource.resourceCategory')"
|
||||
multiple
|
||||
clearable
|
||||
prepend-inner-icon="mdi-folder"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="2" class="text-center">
|
||||
<VBtn block prepend-icon="mdi-magnify" @click="getResourceList">{{ t('dialog.siteResource.search') }}</VBtn>
|
||||
<VBtn variant="tonal" block prepend-icon="mdi-magnify" @click="getResourceList">
|
||||
{{ t('dialog.siteResource.search') }}
|
||||
</VBtn>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
@@ -186,7 +190,6 @@ onMounted(() => {
|
||||
fixed-header
|
||||
hover
|
||||
:items-per-page-text="t('dialog.siteResource.itemsPerPage')"
|
||||
:page-text="t('dialog.siteResource.pageText')"
|
||||
:loading-text="t('dialog.siteResource.loading')"
|
||||
class="h-full"
|
||||
>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { numberValidator } from '@/@validators'
|
||||
import api from '@/api'
|
||||
import type { DownloaderConf, FilterRuleGroup, Site, Subscribe, TransferDirectoryConf } from '@/api/types'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useConfirm } from 'vuetify-use-dialog'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { qualityOptions, resolutionOptions, effectOptions } from '@/api/constants'
|
||||
// i18n
|
||||
@@ -281,18 +281,24 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<VDialog scrollable max-width="45rem" :fullscreen="!display.mdAndUp.value">
|
||||
<VCard
|
||||
:title="
|
||||
props.default
|
||||
? t('dialog.subscribeEdit.titleDefault')
|
||||
: t('dialog.subscribeEdit.titleEditFormat', {
|
||||
name: subscribeForm.name,
|
||||
season: subscribeForm.season
|
||||
? t('dialog.subscribeEdit.seasonFormat', { number: subscribeForm.season })
|
||||
: '',
|
||||
})
|
||||
"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem class="py-2">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-clipboard-list-outline" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>
|
||||
{{ props.default ? t('dialog.subscribeEdit.titleDefault') : t('dialog.subscribeEdit.titleEdit') }}
|
||||
</VCardTitle>
|
||||
<VCardSubtitle v-if="!props.default">
|
||||
{{ subscribeForm.name }}
|
||||
<span v-if="subscribeForm.season">
|
||||
{{ t('dialog.subscribeEdit.seasonFormat', { number: subscribeForm.season }) }}
|
||||
</span>
|
||||
</VCardSubtitle>
|
||||
<VCardSubtitle v-else>
|
||||
{{ props.type }}
|
||||
</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VForm @submit.prevent="() => {}">
|
||||
@@ -314,6 +320,7 @@ onMounted(() => {
|
||||
:label="t('dialog.subscribeEdit.searchKeyword')"
|
||||
:hint="t('dialog.subscribeEdit.searchKeywordHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="subscribeForm.type === '电视剧'" cols="12" md="4">
|
||||
@@ -323,6 +330,7 @@ onMounted(() => {
|
||||
:rules="[numberValidator]"
|
||||
:hint="t('dialog.subscribeEdit.totalEpisodeHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-playlist-play"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="subscribeForm.type === '电视剧'" cols="12" md="4">
|
||||
@@ -332,6 +340,7 @@ onMounted(() => {
|
||||
:rules="[numberValidator]"
|
||||
:hint="t('dialog.subscribeEdit.startEpisodeHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-play-circle-outline"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -343,6 +352,7 @@ onMounted(() => {
|
||||
:items="qualityOptions"
|
||||
:hint="t('dialog.subscribeEdit.qualityHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-quality-high"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
@@ -352,6 +362,7 @@ onMounted(() => {
|
||||
:items="resolutionOptions"
|
||||
:hint="t('dialog.subscribeEdit.resolutionHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-monitor"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
@@ -361,6 +372,7 @@ onMounted(() => {
|
||||
:items="effectOptions"
|
||||
:hint="t('dialog.subscribeEdit.effectHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-auto-fix"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -375,6 +387,7 @@ onMounted(() => {
|
||||
clearable
|
||||
:hint="t('dialog.subscribeEdit.subscribeSitesHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-web"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -386,6 +399,7 @@ onMounted(() => {
|
||||
:label="t('dialog.subscribeEdit.downloader')"
|
||||
:hint="t('dialog.subscribeEdit.downloaderHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-download"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -395,6 +409,7 @@ onMounted(() => {
|
||||
:label="t('dialog.subscribeEdit.savePath')"
|
||||
:hint="t('dialog.subscribeEdit.savePathHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-folder"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -435,6 +450,7 @@ onMounted(() => {
|
||||
:label="t('dialog.subscribeEdit.include')"
|
||||
:hint="t('dialog.subscribeEdit.includeHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-plus-circle-outline"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -443,6 +459,7 @@ onMounted(() => {
|
||||
:label="t('dialog.subscribeEdit.exclude')"
|
||||
:hint="t('dialog.subscribeEdit.excludeHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-minus-circle-outline"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -457,6 +474,7 @@ onMounted(() => {
|
||||
:label="t('dialog.subscribeEdit.filterGroups')"
|
||||
:hint="t('dialog.subscribeEdit.filterGroupsHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-filter"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="!props.default && subscribeForm.type === '电视剧'" cols="12" md="6">
|
||||
@@ -467,6 +485,7 @@ onMounted(() => {
|
||||
:label="t('dialog.subscribeEdit.episodeGroup')"
|
||||
:hint="t('dialog.subscribeEdit.episodeGroupHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-view-list"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="!props.default && subscribeForm.type === '电视剧'" cols="12" md="6">
|
||||
@@ -476,6 +495,7 @@ onMounted(() => {
|
||||
:label="t('dialog.subscribeEdit.season')"
|
||||
:hint="t('dialog.subscribeEdit.seasonHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-calendar"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" v-if="!props.default">
|
||||
@@ -484,6 +504,7 @@ onMounted(() => {
|
||||
:label="t('dialog.subscribeEdit.mediaCategory')"
|
||||
:hint="t('dialog.subscribeEdit.mediaCategoryHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-tag"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -495,6 +516,7 @@ onMounted(() => {
|
||||
:hint="t('dialog.subscribeEdit.customWordsHint')"
|
||||
persistent-hint
|
||||
:placeholder="t('dialog.subscribeEdit.customWordsPlaceholder')"
|
||||
prepend-inner-icon="mdi-text"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -504,12 +526,11 @@ onMounted(() => {
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn v-if="!props.default" color="error" @click="removeSubscribe" variant="outlined" class="me-3">
|
||||
<VBtn v-if="!props.default" color="error" @click="removeSubscribe" class="me-3">
|
||||
{{ t('dialog.subscribeEdit.cancelSubscribe') }}
|
||||
</VBtn>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="elevated"
|
||||
@click=";`${props.default ? saveDefaultSubscribeConfig() : updateSubscribeInfo()}`"
|
||||
prepend-icon="mdi-content-save"
|
||||
class="px-5"
|
||||
|
||||
@@ -56,11 +56,17 @@ const $toast = useToast()
|
||||
|
||||
<template>
|
||||
<VDialog scrollable max-width="30rem" :fullscreen="!display.mdAndUp.value">
|
||||
<VCard
|
||||
:title="`${t('dialog.subscribeShare.shareSubscription')} - ${props.sub?.name} ${
|
||||
props.sub?.season ? t('dialog.subscribeShare.season', { number: props.sub?.season }) : ''
|
||||
}`"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem class="py-2">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-share-outline" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ t('dialog.subscribeShare.shareSubscription') }}</VCardTitle>
|
||||
<VCardSubtitle>
|
||||
{{ props.sub?.name }}
|
||||
{{ props.sub?.season ? t('dialog.subscribeShare.season', { number: props.sub?.season }) : '' }}
|
||||
</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VForm @submit.prevent="() => {}" class="pt-2">
|
||||
@@ -72,6 +78,7 @@ const $toast = useToast()
|
||||
:label="t('dialog.subscribeShare.title')"
|
||||
:rules="[requiredValidator]"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-format-title"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -81,6 +88,7 @@ const $toast = useToast()
|
||||
:rules="[requiredValidator]"
|
||||
:hint="t('dialog.subscribeShare.descriptionHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-comment-text-outline"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -90,6 +98,7 @@ const $toast = useToast()
|
||||
:rules="[requiredValidator]"
|
||||
:hint="t('dialog.subscribeShare.shareUserHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-account-outline"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -97,14 +106,7 @@ const $toast = useToast()
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="elevated"
|
||||
:disabled="shareDoing"
|
||||
@click="doShare"
|
||||
prepend-icon="mdi-share"
|
||||
class="px-5"
|
||||
:loading="shareDoing"
|
||||
>
|
||||
<VBtn :disabled="shareDoing" @click="doShare" prepend-icon="mdi-share" class="px-5" :loading="shareDoing">
|
||||
{{ t('dialog.subscribeShare.confirmShare') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
import api from '@/api'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 多语言支持
|
||||
const { t } = useI18n()
|
||||
@@ -111,23 +115,34 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog width="40rem" scrollable max-height="85vh">
|
||||
<VCard :title="t('dialog.u115Auth.loginTitle')">
|
||||
<VDialog width="40rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VCardText class="pt-2 flex flex-col items-center">
|
||||
<div class="my-6 rounded text-center p-3 border">
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-qrcode" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>
|
||||
{{ t('dialog.u115Auth.loginTitle') }}
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText class="pt-2 flex flex-col items-center justify-center">
|
||||
<div class="mt-6 rounded text-center p-3 border">
|
||||
<QrcodeVue class="mx-auto" :value="qrCodeContent" :size="200" />
|
||||
</div>
|
||||
<VAlert variant="tonal" :type="alertType" class="my-4 text-center" :text="text">
|
||||
<template #prepend />
|
||||
</VAlert>
|
||||
<div>
|
||||
<VAlert variant="tonal" :type="alertType" class="my-4 text-center" :text="text">
|
||||
<template #prepend />
|
||||
</VAlert>
|
||||
</div>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn variant="tonal" color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
||||
<VBtn color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
||||
{{ t('dialog.u115Auth.reset') }}
|
||||
</VBtn>
|
||||
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
||||
<VSpacer />
|
||||
<VBtn @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
||||
{{ t('dialog.u115Auth.complete') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -290,12 +290,15 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog scrollable :close-on-back="false" eager max-width="40rem" :fullscreen="!display.mdAndUp.value">
|
||||
<VCard
|
||||
:title="`${props.oper === 'add' ? t('dialog.userAddEdit.add') : t('dialog.userAddEdit.edit')}${
|
||||
props.oper !== 'add' ? ` - ${userName}` : ''
|
||||
}`"
|
||||
>
|
||||
<VDialog scrollable max-width="40rem" :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
<VCardItem :class="props.oper === 'add' ? 'py-3' : 'py-2'">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-account" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ props.oper === 'add' ? t('dialog.userAddEdit.add') : t('dialog.userAddEdit.edit') }}</VCardTitle>
|
||||
<VCardSubtitle>{{ userName }}</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VDivider />
|
||||
<VCardItem>
|
||||
@@ -350,6 +353,7 @@ onMounted(() => {
|
||||
density="comfortable"
|
||||
:readonly="props.oper !== 'add'"
|
||||
:label="t('dialog.userAddEdit.username')"
|
||||
prepend-inner-icon="mdi-account"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -359,6 +363,7 @@ onMounted(() => {
|
||||
clearable
|
||||
:label="t('dialog.userAddEdit.email')"
|
||||
type="email"
|
||||
prepend-inner-icon="mdi-email"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -370,6 +375,7 @@ onMounted(() => {
|
||||
clearable
|
||||
:label="t('dialog.userAddEdit.password')"
|
||||
autocomplete=""
|
||||
prepend-inner-icon="mdi-lock"
|
||||
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
|
||||
/>
|
||||
</VCol>
|
||||
@@ -382,6 +388,7 @@ onMounted(() => {
|
||||
:append-inner-icon="isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||
clearable
|
||||
:label="t('dialog.userAddEdit.confirmPassword')"
|
||||
prepend-inner-icon="mdi-lock-check"
|
||||
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
|
||||
/>
|
||||
</VCol>
|
||||
@@ -392,6 +399,7 @@ onMounted(() => {
|
||||
clearable
|
||||
:label="t('dialog.userAddEdit.nickname')"
|
||||
placeholder="显示昵称,优先于用户名显示"
|
||||
prepend-inner-icon="mdi-card-account-details"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6" v-if="canControl">
|
||||
@@ -402,6 +410,7 @@ onMounted(() => {
|
||||
item-value="value"
|
||||
:label="t('dialog.userAddEdit.status')"
|
||||
dense
|
||||
prepend-inner-icon="mdi-toggle-switch"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -415,6 +424,7 @@ onMounted(() => {
|
||||
density="comfortable"
|
||||
clearable
|
||||
:label="t('dialog.userAddEdit.wechat')"
|
||||
prepend-inner-icon="mdi-wechat"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -423,6 +433,7 @@ onMounted(() => {
|
||||
density="comfortable"
|
||||
clearable
|
||||
:label="t('dialog.userAddEdit.telegram')"
|
||||
prepend-inner-icon="mdi-send"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -431,6 +442,7 @@ onMounted(() => {
|
||||
density="comfortable"
|
||||
clearable
|
||||
:label="t('dialog.userAddEdit.slack')"
|
||||
prepend-inner-icon="mdi-slack"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -439,6 +451,7 @@ onMounted(() => {
|
||||
density="comfortable"
|
||||
clearable
|
||||
:label="t('dialog.userAddEdit.vocechat')"
|
||||
prepend-inner-icon="mdi-chat"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -447,10 +460,17 @@ onMounted(() => {
|
||||
density="comfortable"
|
||||
clearable
|
||||
:label="t('dialog.userAddEdit.synologyChat')"
|
||||
prepend-inner-icon="mdi-message"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField v-model="userForm.settings.douban_userid" density="comfortable" clearable label="豆瓣用户" />
|
||||
<VTextField
|
||||
v-model="userForm.settings.douban_userid"
|
||||
density="comfortable"
|
||||
clearable
|
||||
label="豆瓣用户"
|
||||
prepend-inner-icon="mdi-movie"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
@@ -461,7 +481,6 @@ onMounted(() => {
|
||||
v-if="props.oper === 'add'"
|
||||
:disabled="isAdding"
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
@click="addUser"
|
||||
prepend-icon="mdi-plus"
|
||||
class="px-5"
|
||||
@@ -473,7 +492,6 @@ onMounted(() => {
|
||||
v-else
|
||||
:disabled="isUpdating"
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
@click="updateUser"
|
||||
prepend-icon="mdi-content-save"
|
||||
class="px-5"
|
||||
|
||||
@@ -3,6 +3,10 @@ import { isNullOrEmptyObject } from '@/@core/utils'
|
||||
import api from '@/api'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 多语言支持
|
||||
const { t } = useI18n()
|
||||
@@ -133,9 +137,16 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog width="40rem" max-height="85vh">
|
||||
<VCard :title="t('dialog.userAuth.title')">
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VDialog width="40rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
<VIcon icon="mdi-user-check" class="me-2" />
|
||||
{{ t('dialog.userAuth.title') }}
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
@@ -146,6 +157,7 @@ onMounted(async () => {
|
||||
item-title="name"
|
||||
:label="t('dialog.userAuth.selectSite')"
|
||||
item-props
|
||||
prepend-inner-icon="mdi-web"
|
||||
>
|
||||
</VSelect>
|
||||
</VCol>
|
||||
@@ -165,14 +177,7 @@ onMounted(async () => {
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardText class="text-center">
|
||||
<VBtn
|
||||
variant="elevated"
|
||||
@click="handleDone"
|
||||
prepend-icon="mdi-check"
|
||||
class="px-5"
|
||||
size="large"
|
||||
:disabled="loading"
|
||||
>
|
||||
<VBtn @click="handleDone" prepend-icon="mdi-check" class="px-5" size="large" :disabled="loading">
|
||||
{{ t('dialog.userAuth.authBtn') }}
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
|
||||
@@ -86,7 +86,13 @@ async function editWorkflow() {
|
||||
|
||||
<template>
|
||||
<VDialog scrollable :close-on-back="false" eager max-width="30rem" :fullscreen="!display.mdAndUp.value">
|
||||
<VCard :title="title">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-clock-outline" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ title }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn @click="emit('close')" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
@@ -99,6 +105,7 @@ async function editWorkflow() {
|
||||
:rules="[requiredValidator]"
|
||||
persistent-hint
|
||||
:hint="t('dialog.workflowAddEdit.namePlaceholder')"
|
||||
prepend-inner-icon="mdi-workflow"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -109,6 +116,7 @@ async function editWorkflow() {
|
||||
placeholder="5位cron表达式"
|
||||
persistent-hint
|
||||
:hint="t('dialog.workflowAddEdit.cronExprDesc')"
|
||||
prepend-inner-icon="mdi-clock-outline"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -116,6 +124,7 @@ async function editWorkflow() {
|
||||
v-model="workflowForm.description"
|
||||
:label="t('dialog.workflowAddEdit.desc')"
|
||||
:placeholder="t('dialog.workflowAddEdit.descPlaceholder')"
|
||||
prepend-inner-icon="mdi-text-box-outline"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -123,18 +132,10 @@ async function editWorkflow() {
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
v-if="workflow"
|
||||
block
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
@click="editWorkflow"
|
||||
prepend-icon="mdi-content-save"
|
||||
class="px-5"
|
||||
>
|
||||
<VBtn v-if="workflow" color="primary" @click="editWorkflow" prepend-icon="mdi-content-save" class="px-5">
|
||||
{{ t('dialog.workflowAddEdit.confirm') }}
|
||||
</VBtn>
|
||||
<VBtn v-else block color="primary" variant="elevated" @click="addWorkflow" prepend-icon="mdi-plus" class="px-5">
|
||||
<VBtn v-else color="primary" @click="addWorkflow" prepend-icon="mdi-plus" class="px-5">
|
||||
{{ t('dialog.workflowAddEdit.confirm') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AxiosRequestConfig } from 'axios'
|
||||
import type { PropType } from 'vue'
|
||||
import { useConfirm } from 'vuetify-use-dialog'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import ReorganizeDialog from '../dialog/ReorganizeDialog.vue'
|
||||
import { formatBytes } from '@core/utils/formatters'
|
||||
@@ -696,13 +696,24 @@ onMounted(() => {
|
||||
</VCard>
|
||||
<!-- 重命名弹窗 -->
|
||||
<VDialog v-if="renamePopper" v-model="renamePopper" max-width="35rem">
|
||||
<VCard :title="t('file.rename')">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-pencil" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ t('file.rename') }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn @click="renamePopper = false" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VTextField v-model="newName" :label="t('file.newName')" :loading="renameLoading" />
|
||||
<VTextField
|
||||
v-model="newName"
|
||||
:label="t('file.newName')"
|
||||
:loading="renameLoading"
|
||||
prepend-inner-icon="mdi-format-text"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" v-if="currentItem && currentItem.type == 'dir'">
|
||||
<VSwitch v-model="renameAll" :label="t('file.includeSubfolders')" />
|
||||
@@ -710,10 +721,10 @@ onMounted(() => {
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VBtn color="success" variant="elevated" @click="get_recommend_name" prepend-icon="mdi-magic" class="px-5 me-3">
|
||||
<VBtn color="success" @click="get_recommend_name" prepend-icon="mdi-magic" class="px-5 me-3">
|
||||
{{ t('file.autoRecognizeName') }}
|
||||
</VBtn>
|
||||
<VBtn :disabled="!newName" variant="elevated" @click="rename" prepend-icon="mdi-check" class="px-5 me-3">
|
||||
<VBtn :disabled="!newName" @click="rename" prepend-icon="mdi-check" class="px-5 me-3">
|
||||
{{ t('common.confirm') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -165,21 +165,28 @@ const sortIcon = computed(() => {
|
||||
<IconBtn v-if="pathSegments.length > 0" @click="goUp">
|
||||
<VIcon icon="mdi-arrow-up-bold-outline" />
|
||||
</IconBtn>
|
||||
<!-- 新建文件夹 -->
|
||||
<VDialog v-model="newFolderPopper" max-width="35rem">
|
||||
<template #activator="{ props }">
|
||||
<IconBtn>
|
||||
<VIcon v-bind="props" icon="mdi-folder-plus-outline" />
|
||||
</IconBtn>
|
||||
</template>
|
||||
<VCard :title="t('file.newFolder')">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-folder-plus-outline" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>{{ t('file.newFolder') }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<VDialogCloseBtn @click="newFolderPopper = false" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VTextField v-model="newFolderName" :label="t('common.name')" />
|
||||
<VTextField v-model="newFolderName" :label="t('common.name')" prepend-inner-icon="mdi-format-text" />
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<div class="flex-grow-1" />
|
||||
<VBtn :disabled="!newFolderName" variant="elevated" @click="mkdir" prepend-icon="mdi-check" class="px-5 me-3">
|
||||
<VBtn :disabled="!newFolderName" @click="mkdir" prepend-icon="mdi-folder-plus" class="px-5 me-3">
|
||||
{{ t('common.create') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
88
src/composables/useConfirm.ts
Normal file
88
src/composables/useConfirm.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { ref } from 'vue'
|
||||
import { createApp } from 'vue'
|
||||
import i18n from '@/plugins/i18n'
|
||||
import vuetify from '@/plugins/vuetify'
|
||||
import ConfirmDialog from '@/@core/components/ConfirmDialog.vue'
|
||||
import DialogCloseBtn from '@/@core/components/DialogCloseBtn.vue'
|
||||
|
||||
interface ConfirmOptions {
|
||||
type?: 'info' | 'warn' | 'error'
|
||||
title?: string
|
||||
content?: string
|
||||
confirmText?: string
|
||||
cancelText?: string
|
||||
width?: string | number
|
||||
}
|
||||
|
||||
let resolvePromise: ((value: boolean) => void) | null = null
|
||||
|
||||
// 创建确认对话框实例
|
||||
async function createConfirmDialog(options: ConfirmOptions = {}) {
|
||||
return new Promise<boolean>(resolve => {
|
||||
resolvePromise = resolve
|
||||
|
||||
// 创建容器
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
|
||||
// 处理国际化
|
||||
const i18nOptions = {
|
||||
...options,
|
||||
title: options.title || i18n.global.t('common.confirm'),
|
||||
confirmText: options.confirmText || i18n.global.t('common.confirm'),
|
||||
cancelText: options.cancelText || i18n.global.t('common.cancel'),
|
||||
}
|
||||
|
||||
// 创建应用实例
|
||||
const app = createApp(ConfirmDialog, {
|
||||
modelValue: true,
|
||||
...i18nOptions,
|
||||
'onUpdate:modelValue': (val: boolean) => {
|
||||
if (!val) {
|
||||
cleanup()
|
||||
}
|
||||
},
|
||||
onConfirm: () => {
|
||||
resolvePromise?.(true)
|
||||
cleanup()
|
||||
},
|
||||
onCancel: () => {
|
||||
resolvePromise?.(false)
|
||||
cleanup()
|
||||
},
|
||||
})
|
||||
|
||||
// 注册必要的组件
|
||||
app.component('VDialogCloseBtn', DialogCloseBtn)
|
||||
|
||||
// 使用插件
|
||||
app.use(vuetify)
|
||||
app.use(i18n)
|
||||
|
||||
// 挂载应用
|
||||
app.mount(container)
|
||||
|
||||
// 清理函数
|
||||
const cleanup = () => {
|
||||
app.unmount()
|
||||
document.body.removeChild(container)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 创建一个函数对象,同时支持直接调用和解构
|
||||
const confirmFunction = Object.assign(createConfirmDialog, {
|
||||
createConfirm: createConfirmDialog,
|
||||
})
|
||||
|
||||
// 导出 useConfirm 函数
|
||||
export function useConfirm() {
|
||||
return confirmFunction
|
||||
}
|
||||
|
||||
// 插件
|
||||
export default {
|
||||
install: (app: any) => {
|
||||
app.provide('confirm', { createConfirm: createConfirmDialog })
|
||||
},
|
||||
}
|
||||
@@ -203,7 +203,13 @@ onMounted(() => {
|
||||
</VCard>
|
||||
</VMenu>
|
||||
<!-- 名称测试弹窗 -->
|
||||
<VDialog v-if="nameTestDialog" v-model="nameTestDialog" max-width="45rem" scrollable>
|
||||
<VDialog
|
||||
v-if="nameTestDialog"
|
||||
v-model="nameTestDialog"
|
||||
max-width="45rem"
|
||||
scrollable
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
@@ -219,7 +225,13 @@ onMounted(() => {
|
||||
</VCard>
|
||||
</VDialog>
|
||||
<!-- 网络测试弹窗 -->
|
||||
<VDialog v-if="netTestDialog" v-model="netTestDialog" max-width="35rem" max-height="85vh" scrollable>
|
||||
<VDialog
|
||||
v-if="netTestDialog"
|
||||
v-model="netTestDialog"
|
||||
max-width="35rem"
|
||||
scrollable
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
@@ -258,12 +270,18 @@ onMounted(() => {
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<LoggingView />
|
||||
<LoggingView logfile="moviepilot.log" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
<!-- 过滤规则弹窗 -->
|
||||
<VDialog v-if="ruleTestDialog" v-model="ruleTestDialog" max-width="35rem" scrollable>
|
||||
<VDialog
|
||||
v-if="ruleTestDialog"
|
||||
v-model="ruleTestDialog"
|
||||
max-width="35rem"
|
||||
scrollable
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
@@ -279,7 +297,13 @@ onMounted(() => {
|
||||
</VCard>
|
||||
</VDialog>
|
||||
<!-- 系统健康检查弹窗 -->
|
||||
<VDialog v-if="systemTestDialog" v-model="systemTestDialog" max-width="35rem" scrollable>
|
||||
<VDialog
|
||||
v-if="systemTestDialog"
|
||||
v-model="systemTestDialog"
|
||||
max-width="35rem"
|
||||
scrollable
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { checkPrefersColorSchemeIsDark } from '@/@core/utils'
|
||||
import { getCurrentLocale, setI18nLanguage } from '@/plugins/i18n'
|
||||
import { saveLocalTheme } from '@/@core/utils/theme'
|
||||
import type { ThemeSwitcherTheme } from '@layouts/types'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
|
||||
// 认证 Store
|
||||
const authStore = useAuthStore()
|
||||
@@ -32,9 +33,6 @@ const progressDialog = ref(false)
|
||||
// 站点认证对话框
|
||||
const siteAuthDialog = ref(false)
|
||||
|
||||
// 重启确认对话框
|
||||
const restartDialog = ref(false)
|
||||
|
||||
// 自定义CSS弹窗
|
||||
const cssDialog = ref(false)
|
||||
|
||||
@@ -47,6 +45,9 @@ const showLanguageMenu = ref(false)
|
||||
// 自定义CSS
|
||||
const customCSS = ref('')
|
||||
|
||||
// 确认框
|
||||
const { createConfirm } = useConfirm()
|
||||
|
||||
// 执行注销操作
|
||||
function logout() {
|
||||
// 清除登录状态信息
|
||||
@@ -57,7 +58,6 @@ function logout() {
|
||||
|
||||
// 执行重启操作
|
||||
async function restart() {
|
||||
restartDialog.value = false
|
||||
// 调用API重启
|
||||
try {
|
||||
// 显示等待框
|
||||
@@ -79,7 +79,15 @@ async function restart() {
|
||||
|
||||
// 显示重启确认对话框
|
||||
async function showRestartDialog() {
|
||||
restartDialog.value = true
|
||||
const isConfirmed = await createConfirm({
|
||||
type: 'warn',
|
||||
title: t('app.confirmRestart'),
|
||||
content: t('app.restartTip'),
|
||||
})
|
||||
|
||||
if (!isConfirmed) return
|
||||
|
||||
await restart()
|
||||
}
|
||||
|
||||
// 显示站点认证对话框
|
||||
@@ -417,32 +425,6 @@ onMounted(() => {
|
||||
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="t('app.restarting')" />
|
||||
<!-- 用户认证对话框 -->
|
||||
<UserAuthDialog v-if="siteAuthDialog" v-model="siteAuthDialog" @done="siteAuthDone" @close="siteAuthDialog = false" />
|
||||
<!-- 重启确认对话框 -->
|
||||
<VDialog v-if="restartDialog" v-model="restartDialog" max-width="25rem">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<div class="d-flex align-center justify-center mt-3">
|
||||
<VAvatar color="warning" variant="text" size="x-large">
|
||||
<VIcon size="x-large" icon="mdi-alert" />
|
||||
</VAvatar>
|
||||
<div class="ms-3">
|
||||
<p class="font-weight-bold text-xl text-high-emphasis">{{ t('app.confirmRestart') }}</p>
|
||||
<p>{{ t('app.restartTip') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</VCardItem>
|
||||
<VCardActions class="mx-auto">
|
||||
<VBtn variant="tonal" color="secondary" class="px-5" @click="restartDialog = false">{{
|
||||
t('common.cancel')
|
||||
}}</VBtn>
|
||||
<VBtn variant="elevated" color="error" @click="restart" prepend-icon="mdi-restart" class="px-5">{{
|
||||
t('common.confirm')
|
||||
}}</VBtn>
|
||||
</VCardActions>
|
||||
<VDialogCloseBtn @click="restartDialog = false" />
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- 自定义 CSS -->
|
||||
<VDialog v-if="cssDialog" v-model="cssDialog" max-width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
|
||||
@@ -39,6 +39,7 @@ export default {
|
||||
unsubscribe: 'Unsubscribe',
|
||||
media: 'Media',
|
||||
unknown: 'Unknown',
|
||||
notice: 'Notice',
|
||||
},
|
||||
mediaType: {
|
||||
movie: 'Movie',
|
||||
@@ -852,8 +853,8 @@ export default {
|
||||
browserSimulation: 'Use browser simulation for authentic site access',
|
||||
},
|
||||
actions: {
|
||||
add: 'Add',
|
||||
edit: 'Edit',
|
||||
add: 'Add Site',
|
||||
edit: 'Edit Site',
|
||||
},
|
||||
messages: {
|
||||
addSuccess: 'Site added successfully',
|
||||
@@ -1095,6 +1096,12 @@ export default {
|
||||
securityImageDomainsHint: 'Allowed image domains whitelist for caching, used to control trusted image sources',
|
||||
noSecurityImageDomains: 'No security domains',
|
||||
securityImageDomainAdd: 'Add domain, e.g.: image.tmdb.org',
|
||||
proxyHost: 'Proxy Server',
|
||||
proxyHostHint: 'Set proxy server address, support: http(s), socks5, socks5h, etc.',
|
||||
moviePilotAutoUpdate: 'Auto Update MoviePilot',
|
||||
moviePilotAutoUpdateHint: 'Automatically update MoviePilot to the latest release version when restarting',
|
||||
autoUpdateResource: 'Auto Update Resource',
|
||||
autoUpdateResourceHint: 'Automatically detect and update site resource package when restarting',
|
||||
},
|
||||
site: {
|
||||
siteSync: 'Site Synchronization',
|
||||
@@ -1637,7 +1644,7 @@ export default {
|
||||
title: 'Plugin Market Settings',
|
||||
repoUrl: 'Plugin Repository URL',
|
||||
repoPlaceholder: 'Format: https://github.com/jxxghp/MoviePilot-Plugins/,https://github.com/xxxx/xxxxxx/',
|
||||
repoHint: 'Multiple URLs separated by commas, only Github repositories are supported',
|
||||
repoHint: 'Multiple URLs separated by lines, only Github repositories are supported',
|
||||
close: 'Close',
|
||||
save: 'Save',
|
||||
saveSuccess: 'Plugin repository saved successfully',
|
||||
@@ -1703,8 +1710,8 @@ export default {
|
||||
previous: 'Previous',
|
||||
confirm: 'Confirm',
|
||||
manualTitle: 'Manual Organization',
|
||||
multipleItemsTitle: 'Organize - {count} Items',
|
||||
singleItemTitle: 'Organize - {path}',
|
||||
multipleItemsTitle: '{count} Items',
|
||||
singleItemTitle: '{path}',
|
||||
targetStorage: 'Target Storage',
|
||||
targetStorageHint: 'Organization target storage',
|
||||
transferType: 'Organization Method',
|
||||
@@ -1753,7 +1760,7 @@ export default {
|
||||
},
|
||||
subscribeEdit: {
|
||||
titleDefault: 'Default Subscription Rules',
|
||||
titleEditFormat: 'Edit Subscription - {name} {season}',
|
||||
titleEdit: 'Edit Subscription',
|
||||
seasonFormat: 'Season {number}',
|
||||
tabs: {
|
||||
basic: 'Basic',
|
||||
@@ -1866,6 +1873,7 @@ export default {
|
||||
peersColumn: 'Peers',
|
||||
viewDetails: 'View Details',
|
||||
downloadTorrent: 'Download Torrent',
|
||||
pageText: '{0}-{1} of {2} items',
|
||||
},
|
||||
forkSubscribe: {
|
||||
title: 'Copy Subscription',
|
||||
@@ -1998,6 +2006,54 @@ export default {
|
||||
updateHistoryTitle: '{name} Update History',
|
||||
updateToLatest: 'Update to Latest Version',
|
||||
updatingTo: 'Updating {name} to v{version} ...',
|
||||
folderNameEmpty: 'Folder name cannot be empty',
|
||||
folderExists: 'Folder already exists',
|
||||
folderCreateSuccess: 'Folder created successfully',
|
||||
folderRenameSuccess: 'Folder renamed successfully',
|
||||
folderRenameFailed: 'Failed to rename folder',
|
||||
folderDeleteSuccess: 'Folder deleted successfully',
|
||||
folderDeleteFailed: 'Failed to delete folder',
|
||||
removeFromFolderSuccess: 'Plugin removed from folder',
|
||||
operationFailed: 'Operation failed',
|
||||
saveFolderConfigFailed: 'Failed to save folder config',
|
||||
newFolder: 'New Folder',
|
||||
folderName: 'Folder Name',
|
||||
cancel: 'Cancel',
|
||||
create: 'Create',
|
||||
clone: 'Clone',
|
||||
cloneTitle: 'Create Plugin Clone',
|
||||
cloneSubtitle: 'Create an independent clone instance for {name}',
|
||||
cloneFeature: 'Plugin Clone Feature',
|
||||
cloneDescription:
|
||||
'Create an independent copy of the plugin with separate configuration and data, suitable for multi-account, testing environments, etc.',
|
||||
suffix: 'Clone Suffix',
|
||||
suffixPlaceholder: 'e.g.: Test, Backup, Site1',
|
||||
suffixHint: 'Unique identifier to distinguish clones, only letters and numbers allowed',
|
||||
suffixRequired: 'Clone suffix cannot be empty',
|
||||
suffixFormatError: 'Only letters and numbers allowed',
|
||||
suffixLengthError: 'Length cannot exceed 20 characters',
|
||||
cloneName: 'Clone Name',
|
||||
cloneNamePlaceholder: 'e.g.: Auto Backup Test Version',
|
||||
cloneNameHint: 'Display name for the clone plugin (optional)',
|
||||
cloneDefaultName: '{name} Clone',
|
||||
cloneDescriptionLabel: 'Clone Description',
|
||||
cloneDescriptionPlaceholder: 'Describe the purpose and features of this clone...',
|
||||
cloneDescriptionHint: 'Detailed description of the clone plugin purpose (optional)',
|
||||
cloneDefaultDescription: '{description} (Clone Version)',
|
||||
cloneVersion: 'Version',
|
||||
cloneVersionPlaceholder: 'e.g.: 1.0, 2.1.0',
|
||||
cloneVersionHint: 'Custom version number for the clone plugin (optional)',
|
||||
cloneIcon: 'Icon URL',
|
||||
cloneIconPlaceholder: 'https://example.com/icon.png',
|
||||
cloneIconHint: 'Custom icon for the clone plugin (optional)',
|
||||
cloneNotice:
|
||||
'Clone plugins are disabled by default after creation and need to be manually configured and enabled. The clone suffix cannot be modified once set.',
|
||||
createClone: 'Create Clone',
|
||||
cloning: 'Creating clone for {name}...',
|
||||
cloneSuccess: 'Plugin clone {name} created successfully!',
|
||||
cloneFailed: 'Plugin clone creation failed: {message}',
|
||||
cloneFailedGeneral: 'Plugin clone creation failed',
|
||||
logTitle: 'Plugin Logging',
|
||||
},
|
||||
profile: {
|
||||
personalInfo: 'Personal Information',
|
||||
@@ -2380,4 +2436,23 @@ export default {
|
||||
required: 'This field is required',
|
||||
number: 'Please enter a number',
|
||||
},
|
||||
folder: {
|
||||
settingAppearance: 'Appearance Settings',
|
||||
rename: 'Rename',
|
||||
deleteFolder: 'Delete Folder',
|
||||
folderNameCannotBeEmpty: 'Folder name cannot be empty',
|
||||
confirmDeleteFolder:
|
||||
'Are you sure you want to delete folder "{folderName}"? Plugins in this folder will be moved back to the main list.',
|
||||
folderSettingsSaved: 'Folder settings saved',
|
||||
renameFolder: 'Rename Folder',
|
||||
folderName: 'Folder Name',
|
||||
folderAppearanceSettings: 'Folder Appearance Settings',
|
||||
showFolderIcon: 'Show Folder Icon',
|
||||
icon: 'Icon',
|
||||
iconColor: 'Icon Color',
|
||||
backgroundGradient: 'Background Gradient',
|
||||
customBackgroundImageURL: 'Custom Background Image URL (Optional)',
|
||||
customBackgroundImageHint: 'Supports web image URLs, leave blank for gradient background',
|
||||
pluginCount: '{count} Plugins',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ export default {
|
||||
unsubscribe: '取消订阅',
|
||||
media: '媒体',
|
||||
unknown: '未知',
|
||||
notice: '注意',
|
||||
},
|
||||
mediaType: {
|
||||
movie: '电影',
|
||||
@@ -849,8 +850,8 @@ export default {
|
||||
browserSimulation: '使用浏览器模拟真实访问该站点',
|
||||
},
|
||||
actions: {
|
||||
add: '新增',
|
||||
edit: '编辑',
|
||||
add: '新增站点',
|
||||
edit: '编辑站点',
|
||||
},
|
||||
messages: {
|
||||
addSuccess: '新增站点成功',
|
||||
@@ -1085,6 +1086,12 @@ export default {
|
||||
securityImageDomainsHint: '允许缓存的图片域名白名单,用于控制可信任的图片来源',
|
||||
noSecurityImageDomains: '暂无安全域名',
|
||||
securityImageDomainAdd: '添加域名,如:image.tmdb.org',
|
||||
proxyHost: '代理服务器',
|
||||
proxyHostHint: '设置代理服务器地址,支持:http(s)、socks5、socks5h 等协议',
|
||||
moviePilotAutoUpdate: '自动更新MoviePilot',
|
||||
moviePilotAutoUpdateHint: '重启时自动更新MoviePilot到最新发行版本',
|
||||
autoUpdateResource: '自动更新站点资源',
|
||||
autoUpdateResourceHint: '重启时自动检测和更新站点资源包',
|
||||
},
|
||||
site: {
|
||||
siteSync: '站点同步',
|
||||
@@ -1614,7 +1621,7 @@ export default {
|
||||
title: '插件市场设置',
|
||||
repoUrl: '插件仓库地址',
|
||||
repoPlaceholder: '格式:https://github.com/jxxghp/MoviePilot-Plugins/,https://github.com/xxxx/xxxxxx/',
|
||||
repoHint: '多个地址使用逗号分隔,仅支持Github仓库',
|
||||
repoHint: '多个地址使用换行分隔,仅支持Github仓库',
|
||||
close: '关闭',
|
||||
save: '保存',
|
||||
saveSuccess: '插件仓库保存成功',
|
||||
@@ -1680,8 +1687,8 @@ export default {
|
||||
previous: '上一步',
|
||||
confirm: '确认',
|
||||
manualTitle: '手动整理',
|
||||
multipleItemsTitle: '整理 - 共 {count} 项',
|
||||
singleItemTitle: '整理 - {path}',
|
||||
multipleItemsTitle: '共 {count} 项',
|
||||
singleItemTitle: '{path}',
|
||||
targetStorage: '目的存储',
|
||||
targetStorageHint: '整理目的存储',
|
||||
transferType: '整理方式',
|
||||
@@ -1730,7 +1737,7 @@ export default {
|
||||
},
|
||||
subscribeEdit: {
|
||||
titleDefault: '默认订阅规则',
|
||||
titleEditFormat: '编辑订阅 - {name} {season}',
|
||||
titleEdit: '编辑订阅',
|
||||
seasonFormat: '第 {number} 季',
|
||||
tabs: {
|
||||
basic: '基础',
|
||||
@@ -1843,6 +1850,7 @@ export default {
|
||||
peersColumn: '下载',
|
||||
viewDetails: '查看详情',
|
||||
downloadTorrent: '下载种子文件',
|
||||
pageText: '{0}-{1} 共 {2} 条',
|
||||
},
|
||||
forkSubscribe: {
|
||||
title: '复制订阅',
|
||||
@@ -1974,6 +1982,52 @@ export default {
|
||||
updateHistoryTitle: '{name} 更新说明',
|
||||
updateToLatest: '更新到最新版本',
|
||||
updatingTo: '更新 {name} 到 {version} 版本...',
|
||||
folderNameEmpty: '文件夹名称不能为空',
|
||||
folderExists: '文件夹已存在',
|
||||
folderCreateSuccess: '文件夹创建成功',
|
||||
folderRenameSuccess: '文件夹重命名成功',
|
||||
folderRenameFailed: '重命名文件夹失败',
|
||||
folderDeleteSuccess: '文件夹删除成功',
|
||||
folderDeleteFailed: '删除文件夹失败',
|
||||
removeFromFolderSuccess: '插件已移出文件夹',
|
||||
operationFailed: '操作失败',
|
||||
saveFolderConfigFailed: '保存文件夹配置失败',
|
||||
newFolder: '新建文件夹',
|
||||
folderName: '文件夹名称',
|
||||
cancel: '取消',
|
||||
create: '创建',
|
||||
clone: '分身',
|
||||
cloneTitle: '创建插件分身',
|
||||
cloneSubtitle: '为 {name} 创建独立的分身实例',
|
||||
cloneFeature: '插件分身功能',
|
||||
cloneDescription: '创建插件的独立副本,拥有独立的配置和数据,适用于多账号、测试环境等场景',
|
||||
suffix: '分身后缀',
|
||||
suffixPlaceholder: '例如:Test、Backup、Site1',
|
||||
suffixHint: '用于区分分身的唯一标识,只能包含英文字母和数字',
|
||||
suffixRequired: '分身后缀不能为空',
|
||||
suffixFormatError: '只能包含英文字母和数字',
|
||||
suffixLengthError: '长度不能超过20个字符',
|
||||
cloneName: '分身名称',
|
||||
cloneNamePlaceholder: '例如:自动备份 测试版',
|
||||
cloneNameHint: '分身插件的显示名称(可选)',
|
||||
cloneDefaultName: '{name} 分身',
|
||||
cloneDescriptionLabel: '分身描述',
|
||||
cloneDescriptionPlaceholder: '描述这个分身的用途和特点...',
|
||||
cloneDescriptionHint: '详细描述分身插件的用途(可选)',
|
||||
cloneDefaultDescription: '{description} (分身版本)',
|
||||
cloneVersion: '版本号',
|
||||
cloneVersionPlaceholder: '例如:1.0、2.1.0',
|
||||
cloneVersionHint: '自定义分身插件的版本号(可选)',
|
||||
cloneIcon: '图标URL',
|
||||
cloneIconPlaceholder: 'https://example.com/icon.png',
|
||||
cloneIconHint: '自定义分身插件的图标(可选)',
|
||||
cloneNotice: '分身插件创建后默认为禁用状态,需要手动配置启用。分身后缀一旦确定无法修改。',
|
||||
createClone: '创建分身',
|
||||
cloning: '正在创建 {name} 的分身...',
|
||||
cloneSuccess: '插件分身 {name} 创建成功!',
|
||||
cloneFailed: '插件分身创建失败:{message}',
|
||||
cloneFailedGeneral: '插件分身创建失败',
|
||||
logTitle: '插件日志',
|
||||
},
|
||||
profile: {
|
||||
personalInfo: '个人信息',
|
||||
@@ -2355,4 +2409,22 @@ export default {
|
||||
required: '此项为必填项',
|
||||
number: '请输入数字',
|
||||
},
|
||||
folder: {
|
||||
settingAppearance: '设置外观',
|
||||
rename: '重命名',
|
||||
deleteFolder: '删除文件夹',
|
||||
folderNameCannotBeEmpty: '文件夹名称不能为空',
|
||||
confirmDeleteFolder: '确定要删除文件夹 "{folderName}" 吗?文件夹中的插件将移回主列表。',
|
||||
folderSettingsSaved: '文件夹设置已保存',
|
||||
renameFolder: '重命名文件夹',
|
||||
folderName: '文件夹名称',
|
||||
folderAppearanceSettings: '文件夹外观设置',
|
||||
showFolderIcon: '显示文件夹图标',
|
||||
icon: '图标',
|
||||
iconColor: '图标颜色',
|
||||
backgroundGradient: '背景渐变',
|
||||
customBackgroundImageURL: '自定义背景图片URL(可选)',
|
||||
customBackgroundImageHint: '支持网络图片URL,留空则使用渐变背景',
|
||||
pluginCount: '{count} 个插件',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ export default {
|
||||
unsubscribe: '取消訂閱',
|
||||
media: '媒體',
|
||||
unknown: '未知',
|
||||
notice: '注意',
|
||||
},
|
||||
mediaType: {
|
||||
movie: '電影',
|
||||
@@ -851,8 +852,8 @@ export default {
|
||||
browserSimulation: '使用瀏覽器模擬真實訪問該站點',
|
||||
},
|
||||
actions: {
|
||||
add: '新增',
|
||||
edit: '編輯',
|
||||
add: '新增站點',
|
||||
edit: '編輯站點',
|
||||
},
|
||||
messages: {
|
||||
addSuccess: '新增站點成功',
|
||||
@@ -1087,6 +1088,12 @@ export default {
|
||||
securityImageDomainsHint: '允許緩存的圖片域名白名單,用於控制可信任的圖片來源',
|
||||
noSecurityImageDomains: '暫無安全域名',
|
||||
securityImageDomainAdd: '添加域名,如:image.tmdb.org',
|
||||
proxyHost: '代理服務器',
|
||||
proxyHostHint: '設置代理服務器地址,支持:http(s)、socks5、socks5h 等協議',
|
||||
moviePilotAutoUpdate: '自動更新MoviePilot',
|
||||
moviePilotAutoUpdateHint: '重啟時自動更新MoviePilot到最新發行版本',
|
||||
autoUpdateResource: '自動更新站點資源',
|
||||
autoUpdateResourceHint: '重啟時自動檢測和更新站點資源包',
|
||||
},
|
||||
site: {
|
||||
siteSync: '站點同步',
|
||||
@@ -1615,7 +1622,7 @@ export default {
|
||||
title: '插件市場設置',
|
||||
repoUrl: '插件倉庫地址',
|
||||
repoPlaceholder: '格式:https://github.com/jxxghp/MoviePilot-Plugins/,https://github.com/xxxx/xxxxxx/',
|
||||
repoHint: '多個地址使用逗號分隔,僅支援Github倉庫',
|
||||
repoHint: '多個地址使用换行分隔,僅支援Github倉庫',
|
||||
close: '關閉',
|
||||
save: '儲存',
|
||||
saveSuccess: '插件倉庫儲存成功',
|
||||
@@ -1681,8 +1688,8 @@ export default {
|
||||
previous: '上一步',
|
||||
confirm: '確認',
|
||||
manualTitle: '手動整理',
|
||||
multipleItemsTitle: '整理 - 共 {count} 項',
|
||||
singleItemTitle: '整理 - {path}',
|
||||
multipleItemsTitle: '共 {count} 項',
|
||||
singleItemTitle: '{path}',
|
||||
targetStorage: '目的存儲',
|
||||
targetStorageHint: '整理目的存儲',
|
||||
transferType: '整理方式',
|
||||
@@ -1731,7 +1738,7 @@ export default {
|
||||
},
|
||||
subscribeEdit: {
|
||||
titleDefault: '默認訂閱規則',
|
||||
titleEditFormat: '編輯訂閱 - {name} {season}',
|
||||
titleEdit: '編輯訂閱',
|
||||
seasonFormat: '第 {number} 季',
|
||||
tabs: {
|
||||
basic: '基礎',
|
||||
@@ -1976,6 +1983,52 @@ export default {
|
||||
updateHistoryTitle: '{name} 更新說明',
|
||||
updateToLatest: '更新到最新版本',
|
||||
updatingTo: '正在更新 {name} 至 v{version} ...',
|
||||
folderNameEmpty: '文件夾名稱不能為空',
|
||||
folderExists: '文件夾已存在',
|
||||
folderCreateSuccess: '文件夾創建成功',
|
||||
folderRenameSuccess: '文件夾重命名成功',
|
||||
folderRenameFailed: '重命名文件夾失敗',
|
||||
folderDeleteSuccess: '文件夾刪除成功',
|
||||
folderDeleteFailed: '刪除文件夾失敗',
|
||||
removeFromFolderSuccess: '插件已移出文件夾',
|
||||
operationFailed: '操作失敗',
|
||||
saveFolderConfigFailed: '保存文件夾配置失敗',
|
||||
newFolder: '新建文件夾',
|
||||
folderName: '文件夾名稱',
|
||||
cancel: '取消',
|
||||
create: '創建',
|
||||
clone: '分身',
|
||||
cloneTitle: '創建插件分身',
|
||||
cloneSubtitle: '為 {name} 創建獨立的分身實例',
|
||||
cloneFeature: '插件分身功能',
|
||||
cloneDescription: '創建插件的獨立副本,擁有獨立的配置和數據,適用於多賬號、測試環境等場景',
|
||||
suffix: '分身後綴',
|
||||
suffixPlaceholder: '例如:Test、Backup、Site1',
|
||||
suffixHint: '用於區分分身的唯一標識,只能包含英文字母和數字',
|
||||
suffixRequired: '分身後綴不能為空',
|
||||
suffixFormatError: '只能包含英文字母和數字',
|
||||
suffixLengthError: '長度不能超過20個字符',
|
||||
cloneName: '分身名稱',
|
||||
cloneNamePlaceholder: '例如:自動備份 測試版',
|
||||
cloneNameHint: '分身插件的顯示名稱(可選)',
|
||||
cloneDefaultName: '{name} 分身',
|
||||
cloneDescriptionLabel: '分身描述',
|
||||
cloneDescriptionPlaceholder: '描述這個分身的用途和特點...',
|
||||
cloneDescriptionHint: '詳細描述分身插件的用途(可選)',
|
||||
cloneDefaultDescription: '{description} (分身版本)',
|
||||
cloneVersion: '版本號',
|
||||
cloneVersionPlaceholder: '例如:1.0、2.1.0',
|
||||
cloneVersionHint: '自定義分身插件的版本號(可選)',
|
||||
cloneIcon: '圖標URL',
|
||||
cloneIconPlaceholder: 'https://example.com/icon.png',
|
||||
cloneIconHint: '自定義分身插件的圖標(可選)',
|
||||
cloneNotice: '分身插件創建後默認為禁用狀態,需要手動配置啟用。分身後綴一旦確定無法修改。',
|
||||
createClone: '創建分身',
|
||||
cloning: '正在創建 {name} 的分身...',
|
||||
cloneSuccess: '插件分身 {name} 創建成功!',
|
||||
cloneFailed: '插件分身創建失敗:{message}',
|
||||
cloneFailedGeneral: '插件分身創建失敗',
|
||||
logTitle: '插件日誌',
|
||||
},
|
||||
profile: {
|
||||
personalInfo: '個人信息',
|
||||
@@ -2357,4 +2410,22 @@ export default {
|
||||
required: '此項為必填項',
|
||||
number: '請輸入數字',
|
||||
},
|
||||
folder: {
|
||||
settingAppearance: '設定外觀',
|
||||
rename: '重新命名',
|
||||
deleteFolder: '刪除資料夾',
|
||||
folderNameCannotBeEmpty: '資料夾名稱不能為空',
|
||||
confirmDeleteFolder: '確定要刪除資料夾 "{folderName}" 嗎?資料夾中的插件將移回主列表。',
|
||||
folderSettingsSaved: '資料夾設定已儲存',
|
||||
renameFolder: '重新命名資料夾',
|
||||
folderName: '資料夾名稱',
|
||||
folderAppearanceSettings: '資料夾外觀設定',
|
||||
showFolderIcon: '顯示資料夾圖示',
|
||||
icon: '圖示',
|
||||
iconColor: '圖示顏色',
|
||||
backgroundGradient: '背景漸變',
|
||||
customBackgroundImageURL: '自定義背景圖片URL(可選)',
|
||||
customBackgroundImageHint: '支援網路圖片URL,留空則使用漸變背景',
|
||||
pluginCount: '{count} 個插件',
|
||||
},
|
||||
}
|
||||
|
||||
23
src/main.ts
23
src/main.ts
@@ -24,7 +24,7 @@ import { fetchGlobalSettings } from './utils/globalSetting'
|
||||
|
||||
// 5. 其他插件和功能模块
|
||||
import ToastPlugin from 'vue-toast-notification'
|
||||
import VuetifyUseDialog from 'vuetify-use-dialog'
|
||||
import ConfirmDialog from '@/composables/useConfirm'
|
||||
import VueApexCharts from 'vue3-apexcharts'
|
||||
|
||||
// 6. 注册自定义组件
|
||||
@@ -102,26 +102,7 @@ initializeApp().then(() => {
|
||||
.use(ToastPlugin, {
|
||||
position: 'bottom-right',
|
||||
})
|
||||
.use(VuetifyUseDialog, {
|
||||
confirmDialog: {
|
||||
dialogProps: {
|
||||
maxWidth: '30rem',
|
||||
},
|
||||
confirmationButtonProps: {
|
||||
variant: 'elevated',
|
||||
color: 'primary',
|
||||
class: 'me-3 px-5',
|
||||
'prepend-icon': 'mdi-check',
|
||||
},
|
||||
cancellationButtonProps: {
|
||||
variant: 'outlined',
|
||||
color: 'secondary',
|
||||
class: 'me-3',
|
||||
},
|
||||
confirmationText: i18n.global.t('common.confirm'),
|
||||
cancellationText: i18n.global.t('common.cancel'),
|
||||
},
|
||||
})
|
||||
.use(ConfirmDialog)
|
||||
.use(i18n)
|
||||
.mount('#app')
|
||||
})
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import PersonCardListView from '@/views/discover/PersonCardListView.vue'
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
// API路径
|
||||
paths: Array as PropType<string[]> | PropType<string>,
|
||||
})
|
||||
|
||||
// 路由参数
|
||||
const route = useRoute()
|
||||
const id = route.query?.id?.toString()
|
||||
const title = route.query?.title?.toString()
|
||||
const source = route.query?.source?.toString()
|
||||
const type = route.query?.type?.toString()
|
||||
const apipath = route.query?.apipath?.toString()
|
||||
|
||||
// 标题
|
||||
let title = route.query?.title?.toString()
|
||||
|
||||
// 计算API路径
|
||||
function getApiPath(paths: string[] | string) {
|
||||
if (Array.isArray(paths)) return paths.join('/')
|
||||
else return paths
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VPageContentTitle :title="title" />
|
||||
<PersonCardListView
|
||||
:credits-id="id"
|
||||
:credits-name="title"
|
||||
:credits-source="source"
|
||||
:credits-type="type"
|
||||
:credits-apipath="apipath"
|
||||
/>
|
||||
<PersonCardListView :apipath="getApiPath(props.paths || '')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -8,6 +8,7 @@ import DashboardElement from '@/components/misc/DashboardElement.vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { VCardActions } from 'vuetify/components'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -353,7 +354,7 @@ onDeactivated(() => {
|
||||
/>
|
||||
|
||||
<!-- 弹窗,根据配置生成选项 -->
|
||||
<VDialog v-if="dialog" v-model="dialog" max-width="35rem" max-height="85vh" scrollable>
|
||||
<VDialog v-if="dialog" v-model="dialog" max-width="35rem" :fullscreen="!display.mdAndUp.value" scrollable>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
@@ -396,8 +397,7 @@ onDeactivated(() => {
|
||||
<VSwitch v-model="isElevated" :label="t('dashboard.adaptiveHeight')" />
|
||||
</p>
|
||||
</VCardText>
|
||||
<VDivider />
|
||||
<VCardText class="pt-5 text-end">
|
||||
<VCardActions class="pt-3">
|
||||
<VSpacer />
|
||||
<VBtn @click="saveDashboardConfig">
|
||||
<template #prepend>
|
||||
@@ -405,7 +405,7 @@ onDeactivated(() => {
|
||||
</template>
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
|
||||
@@ -8,6 +8,9 @@ import ExtraSourceView from '@/views/discover/ExtraSourceView.vue'
|
||||
import { DiscoverSource } from '@/api/types'
|
||||
import api from '@/api'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
const display = useDisplay()
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -179,7 +182,13 @@ onActivated(async () => {
|
||||
</VWindowItem>
|
||||
</VWindow>
|
||||
<!-- 弹窗,根据配置生成选项 -->
|
||||
<VDialog v-if="orderConfigDialog" v-model="orderConfigDialog" max-width="35rem" scrollable>
|
||||
<VDialog
|
||||
v-if="orderConfigDialog"
|
||||
v-model="orderConfigDialog"
|
||||
max-width="35rem"
|
||||
scrollable
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
@@ -199,16 +208,15 @@ onActivated(async () => {
|
||||
:component-data="{ 'class': 'settings-grid' }"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="setting-item enabled">
|
||||
<VCard variant="text" class="setting-item enabled">
|
||||
<div class="setting-item-inner cursor-move text-center">
|
||||
<span class="setting-label">{{ element.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</VCard>
|
||||
</template>
|
||||
</draggable>
|
||||
</VCardText>
|
||||
<VDivider />
|
||||
<VCardText class="pt-5 text-end">
|
||||
<VCardActions class="pt-3">
|
||||
<VSpacer />
|
||||
<VBtn @click="saveTabOrder">
|
||||
<template #prepend>
|
||||
@@ -216,7 +224,7 @@ onActivated(async () => {
|
||||
</template>
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
<!-- 快速滚动到顶部按钮 -->
|
||||
@@ -261,6 +269,7 @@ onActivated(async () => {
|
||||
&::before {
|
||||
position: absolute;
|
||||
background-color: transparent;
|
||||
background-color: rgb(var(--v-theme-primary));
|
||||
block-size: 100%;
|
||||
content: '';
|
||||
inline-size: 4px;
|
||||
|
||||
@@ -14,12 +14,6 @@ const mediaid = route.query?.mediaid?.toString()
|
||||
// 类型:电影、电视剧
|
||||
const type = route.query?.type?.toString()
|
||||
|
||||
// 媒体信息来源:TMDB、豆瓣
|
||||
const source = route.query?.source?.toString() || 'themoviedb'
|
||||
|
||||
// TMDB ID
|
||||
const page = route.query?.page?.toString() || '1'
|
||||
|
||||
// 标题
|
||||
const title = route.query?.title?.toString()
|
||||
|
||||
@@ -29,6 +23,6 @@ const year = route.query?.year?.toString()
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<MediaDetailView :mediaid="mediaid" :type="type" :source="source" :page="page" :title="title" :year="year" />
|
||||
<MediaDetailView :mediaid="mediaid" :type="type" :title="title" :year="year" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,6 +3,9 @@ import api from '@/api'
|
||||
import { RecommendSource } from '@/api/types'
|
||||
import MediaCardSlideView from '@/views/discover/MediaCardSlideView.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
const display = useDisplay()
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -235,7 +238,7 @@ onActivated(async () => {
|
||||
</div>
|
||||
|
||||
<!-- 设置面板 -->
|
||||
<VDialog v-model="dialog" width="35rem" class="settings-dialog" scrollable>
|
||||
<VDialog v-model="dialog" width="35rem" class="settings-dialog" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
<VCard class="settings-card">
|
||||
<VCardItem class="settings-card-header">
|
||||
<VCardTitle>
|
||||
@@ -248,7 +251,7 @@ onActivated(async () => {
|
||||
<VCardText>
|
||||
<p class="settings-hint">{{ t('recommend.selectContentToDisplay') }}</p>
|
||||
<div class="settings-grid">
|
||||
<div
|
||||
<VCard
|
||||
v-for="item in viewList"
|
||||
:key="item.title"
|
||||
class="setting-item"
|
||||
@@ -268,11 +271,10 @@ onActivated(async () => {
|
||||
</div>
|
||||
<span class="setting-label">{{ item.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</VCard>
|
||||
</div>
|
||||
</VCardText>
|
||||
<VDivider />
|
||||
<VCardActions class="pt-5">
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn variant="text" @click="Object.keys(enableConfig).forEach(key => (enableConfig[key] = true))">
|
||||
{{ t('recommend.selectAll') }}
|
||||
</VBtn>
|
||||
@@ -280,7 +282,7 @@ onActivated(async () => {
|
||||
{{ t('recommend.selectNone') }}
|
||||
</VBtn>
|
||||
<VSpacer />
|
||||
<VBtn @click="saveConfig" variant="elevated" color="primary" class="px-5">
|
||||
<VBtn @click="saveConfig" color="primary" class="px-5">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-content-save" />
|
||||
</template>
|
||||
|
||||
@@ -39,7 +39,7 @@ export function getBrowserLocale(): SupportedLocale | null {
|
||||
return navigatorLocale.includes(locale.split('-')[0])
|
||||
})
|
||||
|
||||
return (locale as SupportedLocale) || zh-CN
|
||||
return (locale as SupportedLocale) || 'zh-CN'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,6 +58,10 @@ html.v-overlay-scroll-blocked {
|
||||
margin-block-start: env(safe-area-inset-top);
|
||||
}
|
||||
|
||||
.v-dialog > .v-overlay__content > .v-card > .v-card-item {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* router view transition fade-slide */
|
||||
.fade-slide-leave-active,
|
||||
.fade-slide-enter-active {
|
||||
@@ -221,7 +225,7 @@ html.v-overlay-scroll-blocked {
|
||||
}
|
||||
|
||||
.grid-workflow-card {
|
||||
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
|
||||
}
|
||||
|
||||
.v-tabs:not(.v-tabs-pill).v-tabs--horizontal {
|
||||
|
||||
@@ -69,7 +69,7 @@ onActivated(() => {
|
||||
</template>
|
||||
<VCardTitle>{{ t('dashboard.library') }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<div class="grid gap-4 grid-backdrop-card mx-3" tabindex="0">
|
||||
<div class="grid gap-4 grid-backdrop-card mx-3 mb-3" tabindex="0">
|
||||
<LibraryCard v-for="item in libraryList" :key="item.id" :media="item" height="10rem" />
|
||||
</div>
|
||||
</VCard>
|
||||
|
||||
@@ -70,7 +70,7 @@ onActivated(() => {
|
||||
<VCardTitle>{{ t('dashboard.playing') }}</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<div class="grid gap-4 grid-backdrop-card mx-3" tabindex="0">
|
||||
<div class="grid gap-4 grid-backdrop-card mx-3 mb-3" tabindex="0">
|
||||
<BackdropCard v-for="item in playingList" :key="item.id" :media="item" height="10rem" />
|
||||
</div>
|
||||
</VCard>
|
||||
|
||||
@@ -113,7 +113,7 @@ async function fetchData({ done }: { done: any }) {
|
||||
|
||||
<template>
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||
<VInfiniteScroll mode="intersect" side="end" :items="dataList" class="overflow-visible" @load="fetchData">
|
||||
<VInfiniteScroll mode="intersect" side="end" :items="dataList" class="overflow-visible px-3" @load="fetchData">
|
||||
<template #loading />
|
||||
<template #empty />
|
||||
<div v-if="dataList.length > 0" class="grid gap-4 grid-media-card" tabindex="0">
|
||||
|
||||
@@ -114,7 +114,7 @@ onBeforeMount(() => {
|
||||
'ring-1 ring-gray-700': isImageLoaded,
|
||||
}"
|
||||
>
|
||||
<VImg v-img :src="getPersonImage()" cover @load="isImageLoaded = true" />
|
||||
<VImg :src="getPersonImage()" cover @load="isImageLoaded = true" />
|
||||
</VAvatar>
|
||||
<div class="ms-3">
|
||||
<h1 class="text-3xl lg:text-4xl text-center text-lg-left">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -239,7 +239,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" class="me-2" @click="saveStorages"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" class="me-2" @click="saveStorages" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
<VBtn color="success" variant="tonal" @click="addStorage">
|
||||
<VIcon icon="mdi-plus" />
|
||||
</VBtn>
|
||||
@@ -279,7 +281,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveDirectories"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveDirectories" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
<VBtn color="success" variant="tonal" @click="addDirectory">
|
||||
<VIcon icon="mdi-plus" />
|
||||
</VBtn>
|
||||
@@ -305,6 +309,7 @@ onMounted(() => {
|
||||
:label="t('setting.directory.scrapSource')"
|
||||
:hint="t('setting.directory.scrapSourceHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-database"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -315,6 +320,7 @@ onMounted(() => {
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
prepend-inner-icon="mdi-movie-open"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -325,6 +331,7 @@ onMounted(() => {
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
prepend-inner-icon="mdi-television"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -332,7 +339,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSystemSettings(SystemSettings.Basic)"> {{ t('common.save') }}</VBtn>
|
||||
<VBtn type="submit" @click="saveSystemSettings(SystemSettings.Basic)" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
|
||||
@@ -7,7 +7,10 @@ import NotificationChannelCard from '@/components/cards/NotificationChannelCard.
|
||||
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { notificationSwitchDict } from '@/api/constants'
|
||||
import { useTheme } from 'vuetify'
|
||||
import { useTheme, useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -290,7 +293,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn mtype="submit" @click="saveNotificationSetting"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn mtype="submit" @click="saveNotificationSetting" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
<VBtn color="success" variant="tonal">
|
||||
<VIcon icon="mdi-plus" />
|
||||
<VMenu :activator="'parent'" :close-on-content-click="true">
|
||||
@@ -395,11 +400,12 @@ onMounted(() => {
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveNotificationSwitchs"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveNotificationSwitchs" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
@@ -416,17 +422,29 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="6">
|
||||
<VTextField v-model="notificationTime.start" :label="t('setting.notification.startTime')" type="time" />
|
||||
<VTextField
|
||||
v-model="notificationTime.start"
|
||||
:label="t('setting.notification.startTime')"
|
||||
type="time"
|
||||
prepend-inner-icon="mdi-clock-start"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
<VTextField v-model="notificationTime.end" :label="t('setting.notification.endTime')" type="time" />
|
||||
<VTextField
|
||||
v-model="notificationTime.end"
|
||||
:label="t('setting.notification.endTime')"
|
||||
type="time"
|
||||
prepend-inner-icon="mdi-clock-end"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveNotificationTime"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveNotificationTime" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
@@ -441,13 +459,18 @@ onMounted(() => {
|
||||
:indeterminate="true"
|
||||
/>
|
||||
<!-- 模板编辑器对话框 -->
|
||||
<VDialog v-model="editorVisible" v-if="editorVisible" max-width="50rem">
|
||||
<VDialog v-model="editorVisible" v-if="editorVisible" max-width="50rem" :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardItem class="py-2">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-code-json" class="me-2" />
|
||||
</template>
|
||||
<VCardTitle>
|
||||
{{ templateTypes.find(t => t.type === currentTemplate)?.label }}
|
||||
{{ t('setting.notification.templateConfigTitle') }}
|
||||
</VCardTitle>
|
||||
<VCardSubtitle>
|
||||
{{ templateTypes.find(t => t.type === currentTemplate)?.label }}
|
||||
</VCardSubtitle>
|
||||
<VDialogCloseBtn @click="editorVisible = false" />
|
||||
</VCardItem>
|
||||
<VCardText class="py-0">
|
||||
@@ -455,11 +478,11 @@ onMounted(() => {
|
||||
v-model:value="editorContent"
|
||||
lang="json"
|
||||
:theme="editorTheme"
|
||||
class="w-full min-h-[30rem] rounded"
|
||||
class="w-full h-full min-h-[30rem] rounded"
|
||||
/>
|
||||
</VCardText>
|
||||
<VCardActions class="mx-auto pt-3">
|
||||
<VBtn variant="elevated" color="primary" @click="saveTemplate" prepend-icon="mdi-content-save" class="px-5">
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn color="primary" @click="saveTemplate" prepend-icon="mdi-content-save" class="px-5">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -401,7 +401,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" class="me-2" @click="saveCustomRules"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" class="me-2" @click="saveCustomRules" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
<VBtnGroup density="comfortable">
|
||||
<VBtn color="success" variant="tonal" @click="addCustomRule">
|
||||
<VIcon icon="mdi-plus" />
|
||||
@@ -452,7 +454,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" class="me-2" @click="saveFilterRuleGroups"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" class="me-2" @click="saveFilterRuleGroups" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
<VBtnGroup density="comfortable">
|
||||
<VBtn color="success" variant="tonal" @click="addFilterRuleGroup">
|
||||
<VIcon icon="mdi-plus" />
|
||||
@@ -501,6 +505,7 @@ onMounted(() => {
|
||||
:label="t('setting.rule.currentPriorityRules')"
|
||||
:hint="t('setting.rule.currentPriorityRulesHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-priority-high"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -509,7 +514,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveTorrentPriority"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveTorrentPriority" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
|
||||
@@ -205,6 +205,7 @@ onMounted(() => {
|
||||
:label="t('setting.search.mediaSource')"
|
||||
:hint="t('setting.search.mediaSourceHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-database-search"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -217,6 +218,7 @@ onMounted(() => {
|
||||
:label="t('setting.search.filterRuleGroup')"
|
||||
:hint="t('setting.search.filterRuleGroupHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-filter"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -228,6 +230,7 @@ onMounted(() => {
|
||||
placeholder="MOVIEPILOT"
|
||||
:hint="t('setting.search.downloadLabelHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-tag"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -237,6 +240,7 @@ onMounted(() => {
|
||||
:placeholder="t('setting.search.downloadUserPlaceholder')"
|
||||
:hint="t('setting.search.downloadUserHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-account"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -260,7 +264,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSearchSetting"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveSearchSetting" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
@@ -291,7 +297,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSelectedSites"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveSelectedSites" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
|
||||
@@ -161,6 +161,7 @@ onMounted(() => {
|
||||
:disabled="siteSetting.CookieCloud.COOKIECLOUD_ENABLE_LOCAL"
|
||||
:hint="t('setting.site.serviceAddressHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-server"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -169,6 +170,7 @@ onMounted(() => {
|
||||
:label="t('setting.site.userKey')"
|
||||
:hint="t('setting.site.userKeyHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -180,6 +182,7 @@ onMounted(() => {
|
||||
:label="t('setting.site.e2ePassword')"
|
||||
:hint="t('setting.site.e2ePasswordHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-lock"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -189,6 +192,7 @@ onMounted(() => {
|
||||
:items="CookieCloudIntervalItems"
|
||||
:hint="t('setting.site.autoSyncIntervalHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-timer"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -198,6 +202,7 @@ onMounted(() => {
|
||||
:placeholder="t('setting.site.syncBlacklistPlaceholder')"
|
||||
:hint="t('setting.site.syncBlacklistHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-block-helper"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -206,6 +211,7 @@ onMounted(() => {
|
||||
:label="t('setting.site.userAgent')"
|
||||
:hint="t('setting.site.userAgentHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-web"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -214,7 +220,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSiteSetting(siteSetting.CookieCloud)"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveSiteSetting(siteSetting.CookieCloud)" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
@@ -234,6 +242,7 @@ onMounted(() => {
|
||||
:items="SiteDataRefreshIntervalItems"
|
||||
:hint="t('setting.site.siteDataRefreshIntervalHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-refresh"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -252,7 +261,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSiteSetting(siteSetting.Site)"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveSiteSetting(siteSetting.Site)" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
|
||||
@@ -217,6 +217,7 @@ onMounted(() => {
|
||||
:label="t('setting.subscribe.mode')"
|
||||
:hint="t('setting.subscribe.modeHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-cog"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -226,6 +227,7 @@ onMounted(() => {
|
||||
:label="t('setting.subscribe.rssInterval')"
|
||||
:hint="t('setting.subscribe.rssIntervalHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-timer"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -238,6 +240,7 @@ onMounted(() => {
|
||||
:label="t('setting.subscribe.filterRuleGroup')"
|
||||
:hint="t('setting.subscribe.filterRuleGroupHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-filter"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -250,6 +253,7 @@ onMounted(() => {
|
||||
:label="t('setting.subscribe.bestVersionRuleGroup')"
|
||||
:hint="t('setting.subscribe.bestVersionRuleGroupHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-star"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -276,7 +280,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSubscribeSetting"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveSubscribeSetting" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
@@ -307,7 +313,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSelectedRssSites"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveSelectedRssSites" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
|
||||
@@ -11,6 +11,9 @@ import { copyToClipboard } from '@/@core/utils/navigator'
|
||||
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { downloaderOptions, mediaServerOptions } from '@/api/constants'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
const display = useDisplay()
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -37,6 +40,8 @@ const SystemSettings = ref<any>({
|
||||
PLUGIN_STATISTIC_SHARE: true,
|
||||
BIG_MEMORY_MODE: false,
|
||||
DB_WAL_ENABLE: false,
|
||||
AUTO_UPDATE_RESOURCE: true,
|
||||
MOVIEPILOT_AUTO_UPDATE: false,
|
||||
// 媒体
|
||||
TMDB_API_DOMAIN: null,
|
||||
TMDB_IMAGE_DOMAIN: null,
|
||||
@@ -382,6 +387,16 @@ function onMediaServerChange(mediaserver: MediaServerConf, name: string) {
|
||||
if (index !== -1) mediaServers.value[index] = mediaserver
|
||||
}
|
||||
|
||||
// 添加计算属性
|
||||
const moviePilotAutoUpdate = computed({
|
||||
get: () => {
|
||||
return ['release', 'dev'].includes(SystemSettings.value.Advanced.MOVIEPILOT_AUTO_UPDATE)
|
||||
},
|
||||
set: val => {
|
||||
SystemSettings.value.Advanced.MOVIEPILOT_AUTO_UPDATE = val ? 'release' : 'false'
|
||||
},
|
||||
})
|
||||
|
||||
// 加载数据
|
||||
onMounted(() => {
|
||||
loadDownloaderSetting()
|
||||
@@ -423,6 +438,7 @@ onDeactivated(() => {
|
||||
:hint="t('setting.system.appDomainHint')"
|
||||
placeholder="http://localhost:3000"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-web"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
@@ -435,6 +451,7 @@ onDeactivated(() => {
|
||||
:hint="t('setting.system.wallpaperHint')"
|
||||
persistent-hint
|
||||
:items="wallpaperItems"
|
||||
prepend-inner-icon="mdi-image"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
@@ -446,6 +463,7 @@ onDeactivated(() => {
|
||||
:placeholder="t('setting.system.customizeWallpaperApi')"
|
||||
persistent-hint
|
||||
:rules="[v => !!v || t('setting.system.customizeWallpaperApiRequired')]"
|
||||
prepend-inner-icon="mdi-api"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -460,6 +478,7 @@ onDeactivated(() => {
|
||||
{ title: 'TheMovieDb', value: 'themoviedb' },
|
||||
{ title: '豆瓣', value: 'douban' },
|
||||
]"
|
||||
prepend-inner-icon="mdi-database"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -476,6 +495,7 @@ onDeactivated(() => {
|
||||
(v: any) => !isNaN(v) || t('setting.system.numbersOnly'),
|
||||
(v: any) => v >= 1 || t('setting.system.minInterval'),
|
||||
]"
|
||||
prepend-inner-icon="mdi-sync"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -485,10 +505,11 @@ onDeactivated(() => {
|
||||
:hint="t('setting.system.apiTokenHint')"
|
||||
:placeholder="t('setting.system.apiTokenMinChars')"
|
||||
persistent-hint
|
||||
prependInnerIcon="mdi-reload"
|
||||
:appendInnerIcon="SystemSettings.Basic.API_TOKEN ? 'mdi-content-copy' : ''"
|
||||
@click:prependInner="createRandomString"
|
||||
@click:appendInner="copyValue(SystemSettings.Basic.API_TOKEN)"
|
||||
prepend-inner-icon="mdi-key"
|
||||
:append-inner-icon="SystemSettings.Basic.API_TOKEN ? 'mdi-content-copy' : 'mdi-reload'"
|
||||
@click:append-inner="
|
||||
SystemSettings.Basic.API_TOKEN ? copyValue(SystemSettings.Basic.API_TOKEN) : createRandomString()
|
||||
"
|
||||
:rules="[
|
||||
(v: string) => !!v || t('setting.system.apiTokenRequired'),
|
||||
(v: string) => v.length >= 16 || t('setting.system.apiTokenLength'),
|
||||
@@ -502,6 +523,7 @@ onDeactivated(() => {
|
||||
:placeholder="t('setting.system.githubTokenFormat')"
|
||||
:hint="t('setting.system.githubTokenHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-github"
|
||||
>
|
||||
</VTextField>
|
||||
</VCol>
|
||||
@@ -512,6 +534,7 @@ onDeactivated(() => {
|
||||
placeholder="https://movie-pilot.org"
|
||||
:hint="t('setting.system.ocrHostHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-text-recognition"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -520,7 +543,9 @@ onDeactivated(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveBasicSettings"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveBasicSettings" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
color="error"
|
||||
@@ -565,7 +590,9 @@ onDeactivated(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveDownloaderSetting"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveDownloaderSetting" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
<VBtn color="success" variant="tonal">
|
||||
<VIcon icon="mdi-plus" />
|
||||
<VMenu activator="parent" close-on-content-click>
|
||||
@@ -613,7 +640,9 @@ onDeactivated(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveMediaServerSetting"> {{ t('common.save') }} </VBtn>
|
||||
<VBtn type="submit" @click="saveMediaServerSetting" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
<VBtn color="success" variant="tonal">
|
||||
<VIcon icon="mdi-plus" />
|
||||
<VMenu activator="parent" close-on-content-click>
|
||||
@@ -633,8 +662,15 @@ onDeactivated(() => {
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<!-- 高级系统设置 -->
|
||||
<VDialog v-if="advancedDialog" v-model="advancedDialog" scrollable max-width="60rem">
|
||||
<VDialog
|
||||
v-if="advancedDialog"
|
||||
v-model="advancedDialog"
|
||||
scrollable
|
||||
max-width="60rem"
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VDialogCloseBtn @click="advancedDialog = false" />
|
||||
@@ -711,6 +747,22 @@ onDeactivated(() => {
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="moviePilotAutoUpdate"
|
||||
:label="t('setting.system.moviePilotAutoUpdate')"
|
||||
:hint="t('setting.system.moviePilotAutoUpdateHint')"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Advanced.AUTO_UPDATE_RESOURCE"
|
||||
:label="t('setting.system.autoUpdateResource')"
|
||||
:hint="t('setting.system.autoUpdateResourceHint')"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</VWindowItem>
|
||||
@@ -726,6 +778,7 @@ onDeactivated(() => {
|
||||
persistent-hint
|
||||
:items="['api.themoviedb.org', 'api.tmdb.org']"
|
||||
:rules="[(v: string) => !!v || t('setting.system.tmdbApiDomainRequired')]"
|
||||
prepend-inner-icon="mdi-api"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -737,6 +790,7 @@ onDeactivated(() => {
|
||||
persistent-hint
|
||||
:items="['image.tmdb.org', 'static-mdb.v.geilijiasu.com']"
|
||||
:rules="[(v: string) => !!v || t('setting.system.tmdbImageDomainRequired')]"
|
||||
prepend-inner-icon="mdi-image"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -747,6 +801,7 @@ onDeactivated(() => {
|
||||
:hint="t('setting.system.tmdbLocaleHint')"
|
||||
persistent-hint
|
||||
:items="tmdbLanguageItems"
|
||||
prepend-inner-icon="mdi-translate"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -762,6 +817,7 @@ onDeactivated(() => {
|
||||
(v: any) => v === 0 || !!v || t('setting.system.metaCacheExpireRequired'),
|
||||
(v: any) => v >= 0 || t('setting.system.metaCacheExpireMin'),
|
||||
]"
|
||||
prepend-inner-icon="mdi-timer"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -796,6 +852,16 @@ onDeactivated(() => {
|
||||
<VWindowItem value="network">
|
||||
<div>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.Advanced.PROXY_HOST"
|
||||
:label="t('setting.system.proxyHost')"
|
||||
placeholder="http://127.0.0.1:7890"
|
||||
:hint="t('setting.system.proxyHostHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-server-network"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="githubProxyDisplay"
|
||||
@@ -805,9 +871,10 @@ onDeactivated(() => {
|
||||
persistent-hint
|
||||
:items="githubMirrorsItems"
|
||||
clearable
|
||||
prepend-inner-icon="mdi-github"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCol cols="12">
|
||||
<VCombobox
|
||||
v-model="pipProxyDisplay"
|
||||
:label="t('setting.system.pipProxy')"
|
||||
@@ -816,6 +883,7 @@ onDeactivated(() => {
|
||||
persistent-hint
|
||||
:items="pipMirrorsItems"
|
||||
clearable
|
||||
prepend-inner-icon="mdi-package"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -835,6 +903,7 @@ onDeactivated(() => {
|
||||
:placeholder="t('setting.system.dohResolversPlaceholder')"
|
||||
:hint="t('setting.system.dohResolversHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-dns"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" v-show="SystemSettings.Advanced.DOH_ENABLE">
|
||||
@@ -844,6 +913,7 @@ onDeactivated(() => {
|
||||
:placeholder="t('setting.system.dohDomainsPlaceholder')"
|
||||
:hint="t('setting.system.dohDomainsHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-domain"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -875,6 +945,7 @@ onDeactivated(() => {
|
||||
:placeholder="t('setting.system.securityImageDomainAdd')"
|
||||
hide-details
|
||||
density="compact"
|
||||
prepend-inner-icon="mdi-shield-check"
|
||||
>
|
||||
<template #append>
|
||||
<VBtn icon color="primary" @click="addSecurityDomain" :disabled="!newSecurityDomain">
|
||||
@@ -908,6 +979,7 @@ onDeactivated(() => {
|
||||
:hint="t('setting.system.logLevelHint')"
|
||||
persistent-hint
|
||||
:items="logLevelItems"
|
||||
prepend-inner-icon="mdi-format-list-bulleted"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -920,6 +992,7 @@ onDeactivated(() => {
|
||||
type="number"
|
||||
:suffix="t('setting.system.mb')"
|
||||
:rules="[(v: any) => v === 0 || !!v || t('setting.system.logMaxFileSizeRequired'), (v: any) => v >= 1 || t('setting.system.logMaxFileSizeMin')]"
|
||||
prepend-inner-icon="mdi-file-document"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -931,6 +1004,7 @@ onDeactivated(() => {
|
||||
min="1"
|
||||
type="number"
|
||||
:rules="[(v: any) => v === 0 || !!v || t('setting.system.logBackupCountRequired'), (v: any) => v >= 1 || t('setting.system.logBackupCountMin')]"
|
||||
prepend-inner-icon="mdi-backup-restore"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -939,6 +1013,7 @@ onDeactivated(() => {
|
||||
:label="t('setting.system.logFileFormat')"
|
||||
:hint="t('setting.system.logFileFormatHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-format-text"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -979,13 +1054,7 @@ onDeactivated(() => {
|
||||
<VCardActions class="pt-3">
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
prepend-icon="mdi-content-save"
|
||||
@click="saveAdvancedSettings"
|
||||
class="px-5"
|
||||
>
|
||||
<VBtn color="primary" prepend-icon="mdi-content-save" @click="saveAdvancedSettings" class="px-5">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
@@ -143,6 +143,7 @@ onMounted(() => {
|
||||
:placeholder="t('setting.words.identifiersPlaceholder')"
|
||||
:hint="t('setting.words.identifiersHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-tag-text"
|
||||
/>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
@@ -153,7 +154,9 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveCustomIdentifiers">{{ t('common.save') }}</VBtn>
|
||||
<VBtn type="submit" @click="saveCustomIdentifiers" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
@@ -173,12 +176,15 @@ onMounted(() => {
|
||||
:placeholder="t('setting.words.releaseGroupsPlaceholder')"
|
||||
:hint="t('setting.words.releaseGroupsHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-account-group"
|
||||
/>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveCustomReleaseGroups">{{ t('common.save') }}</VBtn>
|
||||
<VBtn type="submit" @click="saveCustomReleaseGroups" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
@@ -198,12 +204,15 @@ onMounted(() => {
|
||||
:placeholder="t('setting.words.customizationPlaceholder')"
|
||||
:hint="t('setting.words.customizationHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-code-braces"
|
||||
/>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveCustomization">{{ t('common.save') }}</VBtn>
|
||||
<VBtn type="submit" @click="saveCustomization" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
@@ -223,12 +232,15 @@ onMounted(() => {
|
||||
:placeholder="t('setting.words.excludeWordsPlaceholder')"
|
||||
:hint="t('setting.words.excludeWordsHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-block-helper"
|
||||
/>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveTransferExcludeWords">{{ t('common.save') }}</VBtn>
|
||||
<VBtn type="submit" @click="saveTransferExcludeWords" prepend-icon="mdi-content-save">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 定义输入变量
|
||||
const props = defineProps<{
|
||||
logfile: string
|
||||
}>()
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -33,7 +38,12 @@ function getLogColor(level: string): string {
|
||||
|
||||
// SSE持续获取日志
|
||||
function startSSELogging() {
|
||||
eventSource = new EventSource(`${import.meta.env.VITE_API_BASE_URL}system/logging`)
|
||||
console.log(props.logfile)
|
||||
eventSource = new EventSource(
|
||||
`${import.meta.env.VITE_API_BASE_URL}system/logging?logfile=${
|
||||
encodeURIComponent(props.logfile) ?? 'moviepilot.log'
|
||||
}`,
|
||||
)
|
||||
const buffer: string[] = []
|
||||
let timeoutId: number | null = null
|
||||
|
||||
|
||||
@@ -54,10 +54,21 @@ async function nameTest() {
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow class="pt-2">
|
||||
<VCol cols="12">
|
||||
<VTextField v-model="nameTestForm.title" :label="t('nameTest.title')" :rules="[requiredValidator]" />
|
||||
<VTextField
|
||||
v-model="nameTestForm.title"
|
||||
:label="t('nameTest.title')"
|
||||
:rules="[requiredValidator]"
|
||||
prepend-inner-icon="mdi-movie-open"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea v-model="nameTestForm.subtitle" :label="t('nameTest.subtitle')" rows="2" auto-grow />
|
||||
<VTextarea
|
||||
v-model="nameTestForm.subtitle"
|
||||
:label="t('nameTest.subtitle')"
|
||||
rows="2"
|
||||
auto-grow
|
||||
prepend-inner-icon="mdi-subtitles"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
|
||||
@@ -80,13 +80,29 @@ onMounted(() => {
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow class="pt-2">
|
||||
<VCol cols="12" md="8">
|
||||
<VTextField v-model="ruleTestForm.title" :label="t('ruleTest.title')" :rules="[requiredValidator]" />
|
||||
<VTextField
|
||||
v-model="ruleTestForm.title"
|
||||
:label="t('ruleTest.title')"
|
||||
:rules="[requiredValidator]"
|
||||
prepend-inner-icon="mdi-movie-open"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
<VSelect v-model="ruleTestForm.rulegroup" :label="t('ruleTest.ruleGroup')" :items="filterRuleGroupItems" />
|
||||
<VSelect
|
||||
v-model="ruleTestForm.rulegroup"
|
||||
:label="t('ruleTest.ruleGroup')"
|
||||
:items="filterRuleGroupItems"
|
||||
prepend-inner-icon="mdi-filter"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea v-model="ruleTestForm.subtitle" :label="t('ruleTest.subtitle')" rows="2" auto-grow />
|
||||
<VTextarea
|
||||
v-model="ruleTestForm.subtitle"
|
||||
:label="t('ruleTest.subtitle')"
|
||||
rows="2"
|
||||
auto-grow
|
||||
prepend-inner-icon="mdi-subtitles"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
|
||||
@@ -3,6 +3,10 @@ import { cloneDeepWith } from 'lodash-es'
|
||||
import type { Context } from '@/api/types'
|
||||
import TorrentCard from '@/components/cards/TorrentCard.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -606,7 +610,13 @@ const handleSortIconClick = () => {
|
||||
</VCard>
|
||||
|
||||
<!-- 全部筛选弹窗 -->
|
||||
<VDialog v-model="allFilterMenuOpen" max-width="50rem" max-height="90%" location="center" scrollable>
|
||||
<VDialog
|
||||
v-model="allFilterMenuOpen"
|
||||
max-width="50rem"
|
||||
location="center"
|
||||
scrollable
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VDialogCloseBtn @click="allFilterMenuOpen = false" />
|
||||
<VCardTitle class="py-3 d-flex align-center">
|
||||
@@ -676,7 +686,7 @@ const handleSortIconClick = () => {
|
||||
</VDialog>
|
||||
|
||||
<!-- 筛选弹窗 -->
|
||||
<VDialog v-model="filterMenuOpen" max-width="25rem" max-height="80%" location="center">
|
||||
<VDialog v-model="filterMenuOpen" max-width="25rem" location="center" max-height="85vh">
|
||||
<VCard>
|
||||
<VCardTitle class="py-3 d-flex align-center">
|
||||
<VIcon :icon="getFilterIcon(currentFilter)" class="me-2"></VIcon>
|
||||
@@ -713,7 +723,7 @@ const handleSortIconClick = () => {
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn variant="elevated" color="primary" @click="filterMenuOpen = false">
|
||||
<VBtn color="primary" prepend-icon="mdi-check" class="px-5" @click="filterMenuOpen = false">
|
||||
{{ t('torrent.confirm') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
import type { Context } from '@/api/types'
|
||||
import TorrentItem from '@/components/cards/TorrentItem.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -582,7 +586,13 @@ onMounted(() => {
|
||||
</VCard>
|
||||
|
||||
<!-- 全部筛选弹窗 -->
|
||||
<VDialog v-model="allFilterMenuOpen" max-width="50rem" max-height="90%" location="center" scrollable>
|
||||
<VDialog
|
||||
v-model="allFilterMenuOpen"
|
||||
max-width="50rem"
|
||||
location="center"
|
||||
scrollable
|
||||
:fullscreen="!display.mdAndUp.value"
|
||||
>
|
||||
<VCard>
|
||||
<VDialogCloseBtn @click="allFilterMenuOpen = false" />
|
||||
<VCardTitle class="py-3 d-flex align-center">
|
||||
@@ -652,7 +662,7 @@ onMounted(() => {
|
||||
</VDialog>
|
||||
|
||||
<!-- 筛选弹窗 -->
|
||||
<VDialog v-model="filterMenuOpen" max-width="25rem" max-height="80%" location="center">
|
||||
<VDialog v-model="filterMenuOpen" max-width="25rem" max-height="85vh" location="center">
|
||||
<VCard>
|
||||
<VCardTitle class="py-3 d-flex align-center">
|
||||
<VIcon :icon="getFilterIcon(currentFilter)" class="me-2"></VIcon>
|
||||
@@ -689,7 +699,7 @@ onMounted(() => {
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn variant="elevated" color="primary" @click="filterMenuOpen = false">
|
||||
<VBtn color="primary" prepend-icon="mdi-check" class="px-5" @click="filterMenuOpen = false">
|
||||
{{ t('torrent.confirm') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
|
||||
@@ -114,7 +114,6 @@ useDynamicButton({
|
||||
v-model="addUserDialog"
|
||||
oper="add"
|
||||
max-width="45rem"
|
||||
persistent
|
||||
@save="onUserAdd"
|
||||
@close="addUserDialog = false"
|
||||
/>
|
||||
|
||||
@@ -322,7 +322,13 @@ watch(
|
||||
<VForm class="mt-6">
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField v-model="currentUserName" density="comfortable" readonly :label="t('user.username')" />
|
||||
<VTextField
|
||||
v-model="currentUserName"
|
||||
density="comfortable"
|
||||
readonly
|
||||
:label="t('user.username')"
|
||||
prepend-inner-icon="mdi-account"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
@@ -331,6 +337,7 @@ watch(
|
||||
clearable
|
||||
:label="t('user.email')"
|
||||
type="email"
|
||||
prepend-inner-icon="mdi-email"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -342,6 +349,7 @@ watch(
|
||||
clearable
|
||||
:label="t('user.password')"
|
||||
autocomplete=""
|
||||
prepend-inner-icon="mdi-lock"
|
||||
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
|
||||
/>
|
||||
</VCol>
|
||||
@@ -354,6 +362,7 @@ watch(
|
||||
:append-inner-icon="isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||
clearable
|
||||
:label="t('user.confirmPassword')"
|
||||
prepend-inner-icon="mdi-lock-check"
|
||||
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
|
||||
/>
|
||||
</VCol>
|
||||
@@ -364,6 +373,7 @@ watch(
|
||||
clearable
|
||||
:label="t('profile.nickname')"
|
||||
:placeholder="t('profile.nicknamePlaceholder')"
|
||||
prepend-inner-icon="mdi-card-account-details"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -379,6 +389,7 @@ watch(
|
||||
density="comfortable"
|
||||
clearable
|
||||
:label="t('profile.wechatUser')"
|
||||
prepend-inner-icon="mdi-wechat"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -387,6 +398,7 @@ watch(
|
||||
density="comfortable"
|
||||
clearable
|
||||
:label="t('profile.telegramUser')"
|
||||
prepend-inner-icon="mdi-send"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -395,6 +407,7 @@ watch(
|
||||
density="comfortable"
|
||||
clearable
|
||||
:label="t('profile.slackUser')"
|
||||
prepend-inner-icon="mdi-slack"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -403,6 +416,7 @@ watch(
|
||||
density="comfortable"
|
||||
clearable
|
||||
:label="t('profile.vocechatUser')"
|
||||
prepend-inner-icon="mdi-chat"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -411,6 +425,7 @@ watch(
|
||||
density="comfortable"
|
||||
clearable
|
||||
:label="t('profile.synologychatUser')"
|
||||
prepend-inner-icon="mdi-message"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -419,13 +434,14 @@ watch(
|
||||
density="comfortable"
|
||||
clearable
|
||||
:label="t('profile.doubanUser')"
|
||||
prepend-inner-icon="mdi-movie"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<!-- 👉 Form Actions -->
|
||||
<VCol cols="12" class="d-flex flex-wrap gap-4">
|
||||
<VBtn @click="saveAccountInfo" :disabled="isSaving">
|
||||
<VBtn @click="saveAccountInfo" :disabled="isSaving" prepend-icon="mdi-content-save">
|
||||
<span v-if="isSaving">{{ t('common.saving') }}...</span>
|
||||
<span v-else>{{ t('common.save') }}</span>
|
||||
</VBtn>
|
||||
@@ -462,6 +478,7 @@ watch(
|
||||
autocomplete=""
|
||||
class="mb-8"
|
||||
variant="outlined"
|
||||
prepend-inner-icon="mdi-shield-key"
|
||||
/>
|
||||
<div class="d-flex justify-end flex-wrap gap-4">
|
||||
<VBtn variant="outlined" color="secondary" @click="otpDialog = false"> {{ t('common.cancel') }} </VBtn>
|
||||
|
||||
@@ -7704,13 +7704,6 @@ vuedraggable@^4.1.0:
|
||||
dependencies:
|
||||
sortablejs "1.14.0"
|
||||
|
||||
vuetify-use-dialog@^0.6.11:
|
||||
version "0.6.11"
|
||||
resolved "https://registry.yarnpkg.com/vuetify-use-dialog/-/vuetify-use-dialog-0.6.11.tgz#8800cc56b234dae1dfa44a7f06a6bb1a33ad4b39"
|
||||
integrity sha512-iPAu6MsN8suuNAS1M6JN2CaOXRgr7LZ2u+UNtAw0Fi3AjianzVIrnRNhQcAZjmE8Hu6ZwAbgte1p47qU6OazLw==
|
||||
dependencies:
|
||||
defu "^6.1.4"
|
||||
|
||||
vuetify@3.7.3:
|
||||
version "3.7.3"
|
||||
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.7.3.tgz#0e89f7f0298d452510bcbc01b0e9b53a5ce6e883"
|
||||
|
||||
Reference in New Issue
Block a user