Merge pull request #1 from jxxghp/v2

pr
This commit is contained in:
boeto
2024-10-25 17:36:10 +08:00
committed by GitHub
23 changed files with 174 additions and 123 deletions

View File

@@ -1,10 +1,12 @@
name: Build Moviepilot-Frontend name: Build Moviepilot-Frontend v2
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: branches:
- dev - v2
paths:
- 'package.json'
jobs: jobs:
build: build:
@@ -43,14 +45,14 @@ jobs:
- name: Delete Release - name: Delete Release
uses: dev-drprasad/delete-tag-and-release@v1.1 uses: dev-drprasad/delete-tag-and-release@v1.1
with: with:
tag_name: dev_${{ env.frontend_version }} tag_name: ${{ env.frontend_version }}
delete_release: true delete_release: true
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Generate Release - name: Generate Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
tag_name: dev_${{ env.frontend_version }} tag_name: ${{ env.frontend_version }}
name: ${{ env.frontend_version }} name: ${{ env.frontend_version }}
draft: false draft: false
prerelease: false prerelease: false

View File

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

View File

@@ -123,7 +123,8 @@ export interface NavLink extends NavLinkProps, Partial<AclProperties> {
export interface NavMenu extends NavLink { export interface NavMenu extends NavLink {
header: string header: string
description?: string description?: string
permission?: string admin?: boolean
footer?: boolean
} }
// 👉 Vertical nav group // 👉 Vertical nav group

View File

@@ -1042,6 +1042,8 @@ export interface TransferDirectoryConf {
download_category_folder?: boolean download_category_folder?: boolean
// 监控方式 downloader/monitorNone为不监控 // 监控方式 downloader/monitorNone为不监控
monitor_type?: string monitor_type?: string
// 监控模式 fast/compatibility
monitor_mode?: string
// 整理方式 move/copy/link/softlink // 整理方式 move/copy/link/softlink
transfer_type?: string transfer_type?: string
// 文件覆盖模式 always/size/never/latest // 文件覆盖模式 always/size/never/latest
@@ -1058,6 +1060,8 @@ export interface TransferDirectoryConf {
library_type_folder?: boolean library_type_folder?: boolean
// 媒体库类别子目录 // 媒体库类别子目录
library_category_folder?: boolean library_category_folder?: boolean
// 是否发送通知
notify?: boolean
} }
// 自定义规则项 // 自定义规则项

View File

@@ -93,6 +93,11 @@ function onClose() {
<template> <template>
<div> <div>
<VCard variant="tonal" @click="openRuleInfoDialog"> <VCard variant="tonal" @click="openRuleInfoDialog">
<span class="absolute top-3 right-12">
<IconBtn>
<VIcon class="cursor-move" icon="mdi-drag" />
</IconBtn>
</span>
<DialogCloseBtn @click="onClose" /> <DialogCloseBtn @click="onClose" />
<VCardText class="flex justify-space-between align-center gap-3"> <VCardText class="flex justify-space-between align-center gap-3">
<div class="align-self-start"> <div class="align-self-start">

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { TransferDirectoryConf } from '@/api/types' import type { TransferDirectoryConf } from '@/api/types'
import { VTextField } from 'vuetify/lib/components/index.mjs' import { VDivider, VSpacer, VTextField } from 'vuetify/lib/components/index.mjs'
import { useToast } from 'vue-toast-notification' import { useToast } from 'vue-toast-notification'
import api from '@/api' import api from '@/api'
import { nextTick } from 'vue' import { nextTick } from 'vue'
@@ -26,6 +26,9 @@ const downloadPath = ref<string>('')
// 媒体库路径 // 媒体库路径
const libraryPath = ref<string>('') const libraryPath = ref<string>('')
// 卡版是否折叠状态
const isCollapsed = ref(true)
// 类型下拉字典 // 类型下拉字典
const typeItems = [ const typeItems = [
{ title: '全部', value: '' }, { title: '全部', value: '' },
@@ -48,6 +51,12 @@ const transferSourceItems = [
{ title: '目录监控', value: 'monitor' }, { title: '目录监控', value: 'monitor' },
] ]
// 监控模式下拉字典
const MonitorModeItems = [
{ title: '性能模式', value: 'fast' },
{ title: '兼容模式', value: 'compatibility' },
]
// 整理方式下拉字典 // 整理方式下拉字典
const transferTypeItems = ref<{ title: string; value: string }[]>([]) const transferTypeItems = ref<{ title: string; value: string }[]>([])
@@ -182,7 +191,7 @@ watch(
</IconBtn> </IconBtn>
</span> </span>
</VCardItem> </VCardItem>
<VCardText> <VCardText v-if="!isCollapsed">
<VForm> <VForm>
<VRow> <VRow>
<VCol cols="6"> <VCol cols="6">
@@ -236,6 +245,14 @@ watch(
</VCol> </VCol>
</VRow> </VRow>
<VRow v-if="$props.directory.monitor_type"> <VRow v-if="$props.directory.monitor_type">
<VCol cols="12" v-if="$props.directory.monitor_type == 'monitor'">
<VSelect
v-model="props.directory.monitor_mode"
variant="underlined"
:items="MonitorModeItems"
label="监控模式"
/>
</VCol>
<VCol cols="4"> <VCol cols="4">
<VSelect <VSelect
v-model="props.directory.library_storage" v-model="props.directory.library_storage"
@@ -285,8 +302,16 @@ watch(
<VCol cols="6"> <VCol cols="6">
<VSwitch v-model="props.directory.scraping" label="刮削元数据"></VSwitch> <VSwitch v-model="props.directory.scraping" label="刮削元数据"></VSwitch>
</VCol> </VCol>
<VCol cols="6">
<VSwitch v-model="props.directory.notify" label="发送通知"></VSwitch>
</VCol>
</VRow> </VRow>
</VForm> </VForm>
</VCardText> </VCardText>
<VCardActions class="text-center py-0">
<VSpacer />
<VBtn :icon="isCollapsed ? 'mdi-chevron-down' : 'mdi-chevron-up'" @click.stop="isCollapsed = !isCollapsed" />
<VSpacer />
</VCardActions>
</VCard> </VCard>
</template> </template>

View File

@@ -209,6 +209,11 @@ function onClose() {
<template> <template>
<div> <div>
<VCard variant="tonal" @click="opengroupInfoDialog"> <VCard variant="tonal" @click="opengroupInfoDialog">
<span class="absolute top-3 right-12">
<IconBtn>
<VIcon class="cursor-move" icon="mdi-drag" />
</IconBtn>
</span>
<DialogCloseBtn @click="onClose" /> <DialogCloseBtn @click="onClose" />
<VCardText class="flex justify-space-between align-center gap-3"> <VCardText class="flex justify-space-between align-center gap-3">
<div class="align-self-start"> <div class="align-self-start">

View File

@@ -117,6 +117,11 @@ function onClose() {
<template> <template>
<div> <div>
<VCard variant="tonal" @click="openNotificationInfoDialog"> <VCard variant="tonal" @click="openNotificationInfoDialog">
<span class="absolute top-3 right-12">
<IconBtn>
<VIcon class="cursor-move" icon="mdi-drag" />
</IconBtn>
</span>
<DialogCloseBtn @click="onClose" /> <DialogCloseBtn @click="onClose" />
<VCardText class="flex justify-space-between align-center gap-3"> <VCardText class="flex justify-space-between align-center gap-3">
<div class="align-self-start"> <div class="align-self-start">

View File

@@ -134,8 +134,8 @@ async function forkSubscribe() {
</template> </template>
</VImg> </VImg>
</div> </div>
<div class="flex flex-col justify-center pl-2 xl:pl-4 line-clamp-2 overflow-hidden text-ellipsis ..."> <div class="flex flex-col justify-center pl-2 xl:pl-4">
<div class="mr-2 min-w-0 text-lg font-bold text-white"> <div class="mr-2 min-w-0 text-lg font-bold text-white line-clamp-2 overflow-hidden text-ellipsis ...">
{{ props.media?.share_title }} {{ props.media?.share_title }}
</div> </div>
<div class="text-sm font-medium text-gray-200 sm:pt-1 line-clamp-3 overflow-hidden text-ellipsis ..."> <div class="text-sm font-medium text-gray-200 sm:pt-1 line-clamp-3 overflow-hidden text-ellipsis ...">

View File

@@ -21,8 +21,11 @@ const props = defineProps({
}, },
}) })
// 当前用户名称 // 当前用户的ID
const currentLoginUser = store.state.auth.userName const currentLoginUserId = computed(() => store.state.auth.userID)
// 当前用户是否是管理员
const currentUserIsSuperuser = computed(() => store.state.auth.superUser)
// 定义触发的自定义事件 // 定义触发的自定义事件
const emit = defineEmits(['remove', 'save']) const emit = defineEmits(['remove', 'save'])
@@ -57,7 +60,7 @@ async function fetchSubscriptions() {
// 删除用户 // 删除用户
async function removeUser() { async function removeUser() {
if (props.user.name == currentLoginUser) { if (props.user.id === currentLoginUserId.value) {
$toast.error('不能删除当前登录用户!') $toast.error('不能删除当前登录用户!')
return return
} }
@@ -84,19 +87,6 @@ function editUser() {
userEditDialog.value = true userEditDialog.value = true
} }
// 计算是否有用户编辑权限
const canEditUser = computed(() => {
if (store.state.auth.superUser && props.user.name !== currentLoginUser) return true
return false
})
// 计算是否有用户管理权限
const canManageUser = computed(() => {
if (props.user.name == currentLoginUser) return false
if (store.state.auth.superUser) return true
return false
})
// 用户重新完成时 // 用户重新完成时
function onUserUpdate() { function onUserUpdate() {
userEditDialog.value = false userEditDialog.value = false
@@ -139,8 +129,9 @@ onMounted(() => {
</div> </div>
</VCardText> </VCardText>
<VCardText class="pb-6"> <VCardText class="pb-6">
<h5 class="text-h6">详情</h5> <VDivider class="my-2">
<VDivider class="my-2" /> <h5 class="text-h6">详情</h5>
</VDivider>
<VList lines="one"> <VList lines="one">
<VListItem> <VListItem>
<VListItemTitle class="text-sm"> <VListItemTitle class="text-sm">
@@ -170,8 +161,22 @@ onMounted(() => {
</VList> </VList>
</VCardText> </VCardText>
<VCardText class="flex flex-row justify-center"> <VCardText class="flex flex-row justify-center">
<VBtn v-if="canEditUser" color="primary" class="me-4" @click="editUser">编辑</VBtn> <VBtn
<VBtn v-if="canManageUser" color="error" variant="outlined" @click="removeUser"> 删除 </VBtn> v-if="currentUserIsSuperuser"
color="primary"
class="me-4"
@click="editUser"
>
编辑
</VBtn>
<VBtn
v-if="currentUserIsSuperuser && props.user.id != currentLoginUserId"
color="error"
variant="outlined"
@click="removeUser"
>
删除
</VBtn>
</VCardText> </VCardText>
</VCard> </VCard>
<!-- 用户编辑弹窗 --> <!-- 用户编辑弹窗 -->

View File

@@ -36,7 +36,7 @@ const systemMenus = ref<NavMenu[]>([])
// 根据分类获取菜单列表 // 根据分类获取菜单列表
const getMenuList = (header: string) => { const getMenuList = (header: string) => {
return SystemNavMenus.filter((item: NavMenu) => item.header === header && superUser) return SystemNavMenus.filter((item: NavMenu) => item.header === header && (superUser || !item.admin))
} }
// 返回上一页 // 返回上一页

View File

@@ -143,8 +143,8 @@ onMounted(() => {
<VAvatar size="48" variant="tonal"> <VAvatar size="48" variant="tonal">
<VIcon icon="mdi-filter-cog-outline" /> <VIcon icon="mdi-filter-cog-outline" />
</VAvatar> </VAvatar>
<h6 class="text-base font-weight-medium mt-2 mb-0">优先级</h6> <h6 class="text-base font-weight-medium mt-2 mb-0">规则</h6>
<span class="text-sm">优先级规则测试</span> <span class="text-sm">规则测试</span>
</VListItem> </VListItem>
</VCol> </VCol>
</VRow> </VRow>
@@ -241,7 +241,7 @@ onMounted(() => {
</VDialog> </VDialog>
<!-- 规则测试弹窗 --> <!-- 规则测试弹窗 -->
<VDialog v-if="ruleTestDialog" v-model="ruleTestDialog" max-width="50rem" scrollable> <VDialog v-if="ruleTestDialog" v-model="ruleTestDialog" max-width="50rem" scrollable>
<VCard title="优先级测试"> <VCard title="规则测试">
<DialogCloseBtn @click="ruleTestDialog = false" /> <DialogCloseBtn @click="ruleTestDialog = false" />
<VCardText> <VCardText>
<RuleTestView /> <RuleTestView />

View File

@@ -6,9 +6,6 @@ import router from '@/router'
import avatar1 from '@images/avatars/avatar-1.png' import avatar1 from '@images/avatars/avatar-1.png'
import api from '@/api' import api from '@/api'
import ProgressDialog from '@/components/dialog/ProgressDialog.vue' import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
import { useDisplay } from 'vuetify'
const display = useDisplay()
// Vuex Store // Vuex Store
const store = useStore() const store = useStore()

View File

@@ -1,14 +1,16 @@
import '@/@core/utils/compatibility' import '@/@core/utils/compatibility'
import './ace-config'
import '@/@iconify/icons-bundle' import '@/@iconify/icons-bundle'
import '@/plugins/webfontloader' import '@/plugins/webfontloader'
import App from '@/App.vue' import App from '@/App.vue'
import vuetify from '@/plugins/vuetify' import vuetify from '@/plugins/vuetify'
import router from '@/router' import router from '@/router'
import store from '@/store' import store from '@/store'
import { VAceEditor } from 'vue3-ace-editor'
import { createApp } from 'vue' import { createApp } from 'vue'
import { removeEl } from './@core/utils/dom' import { removeEl } from './@core/utils/dom'
import { fetchGlobalSettings } from './api'
import { isPWA } from './@core/utils/navigator'
import './ace-config'
import { VAceEditor } from 'vue3-ace-editor'
import { PerfectScrollbarPlugin } from 'vue3-perfect-scrollbar' import { PerfectScrollbarPlugin } from 'vue3-perfect-scrollbar'
import { VTreeview } from 'vuetify/labs/VTreeview' import { VTreeview } from 'vuetify/labs/VTreeview'
import ToastPlugin from 'vue-toast-notification' import ToastPlugin from 'vue-toast-notification'
@@ -23,8 +25,6 @@ import MediaInfoCard from './components/cards/MediaInfoCard.vue'
import TorrentCard from './components/cards/TorrentCard.vue' import TorrentCard from './components/cards/TorrentCard.vue'
import MediaIdSelector from './components/misc/MediaIdSelector.vue' import MediaIdSelector from './components/misc/MediaIdSelector.vue'
import PathField from './components/input/PathField.vue' import PathField from './components/input/PathField.vue'
import { fetchGlobalSettings } from './api'
import { isPWA } from './@core/utils/navigator'
import '@core/scss/template/index.scss' import '@core/scss/template/index.scss'
import '@layouts/styles/index.scss' import '@layouts/styles/index.scss'
import '@styles/styles.scss' import '@styles/styles.scss'

View File

@@ -12,7 +12,7 @@ const appOrder = ref<string[]>([])
// 根据分类获取菜单列表 // 根据分类获取菜单列表
const getMenuList = () => { const getMenuList = () => {
return SystemNavMenus.filter((item: NavMenu) => !item.admin || superUser) return SystemNavMenus.filter((item: NavMenu) => (!item.admin || superUser) && !item.footer)
} }
// APP列表 // APP列表
@@ -48,7 +48,7 @@ onMounted(() => {
:component-data="{ 'class': 'ma-0 mt-n1' }" :component-data="{ 'class': 'ma-0 mt-n1' }"
> >
<template #item="{ element }"> <template #item="{ element }">
<VCol cols="6" md="4" lg="3" class="text-center cursor-pointer shortcut-icon select-none"> <VCol cols="6" md="3" lg="2" class="text-center cursor-pointer shortcut-icon select-none">
<VCard class="pa-4" :to="element.to" variant="flat"> <VCard class="pa-4" :to="element.to" variant="flat">
<VAvatar size="64" variant="text"> <VAvatar size="64" variant="text">
<VIcon size="48" :icon="element.icon" color="primary" /> <VIcon size="48" :icon="element.icon" color="primary" />

View File

@@ -15,9 +15,6 @@ const { global: globalTheme } = useTheme()
// Vuex Store // Vuex Store
const store = useStore() const store = useStore()
// 从 provide 中获取全局设置
const globalSettings: any = inject('globalSettings')
// 表单 // 表单
const form = ref({ const form = ref({
username: '', username: '',
@@ -55,15 +52,8 @@ let intervalTimer: NodeJS.Timeout | null = null
// 获取背景图片 // 获取背景图片
async function fetchBackgroundImage() { async function fetchBackgroundImage() {
try { try {
const results: string[] = await api.get('/login/wallpapers') backgroundImages.value = await api.get('/login/wallpapers')
if (results && results.length > 0) { if (backgroundImages.value && backgroundImages.value.length > 0) {
results.map((url: string) => {
if (globalSettings.GLOBAL_IMAGE_CACHE)
backgroundImages.value.push(
`${import.meta.env.VITE_API_BASE_URL}system/cache/image?url=${encodeURIComponent(url)}`,
)
else backgroundImages.value.push(url)
})
// 随机打乱排序 // 随机打乱排序
backgroundImages.value.sort(() => Math.random() - 0.5) backgroundImages.value.sort(() => Math.random() - 0.5)
backgroundImageUrl.value = backgroundImages.value[0] backgroundImageUrl.value = backgroundImages.value[0]
@@ -171,6 +161,7 @@ function login() {
// 获取token // 获取token
const token = response.access_token const token = response.access_token
const superUser = response.super_user const superUser = response.super_user
const userID = response.user_id
const userName = response.user_name const userName = response.user_name
const avatar = response.avatar const avatar = response.avatar
const level = response.level const level = response.level
@@ -178,7 +169,16 @@ function login() {
const permissions = response.permissions const permissions = response.permissions
// 更新token和remember状态到Vuex Store // 更新token和remember状态到Vuex Store
store.dispatch('auth/login', { token, remember, superUser, userName, avatar, level, permissions }) store.dispatch('auth/login', {
token,
remember,
superUser,
userID,
userName,
avatar,
level,
permissions,
})
// 登录后处理 // 登录后处理
afterLogin(superUser) afterLogin(superUser)

View File

@@ -5,21 +5,23 @@ export const SystemNavMenus = [
icon: 'mdi-home-outline', icon: 'mdi-home-outline',
to: '/dashboard', to: '/dashboard',
header: '开始', header: '开始',
permission: 'dashboard', admin: false,
footer: true,
}, },
{ {
title: '推荐', title: '推荐',
icon: 'mdi-star-outline', icon: 'mdi-star-outline',
to: '/ranking', to: '/ranking',
header: '发现', header: '发现',
permission: 'ranking', admin: false,
footer: true,
}, },
{ {
title: '资源搜索', title: '资源搜索',
icon: 'mdi-magnify', icon: 'mdi-magnify',
to: '/resource', to: '/resource',
header: '发现', header: '发现',
permission: 'resource.search', admin: false,
}, },
{ {
title: '电影', title: '电影',
@@ -27,7 +29,8 @@ export const SystemNavMenus = [
icon: 'mdi-movie-open-outline', icon: 'mdi-movie-open-outline',
to: '/subscribe/movie', to: '/subscribe/movie',
header: '订阅', header: '订阅',
permission: 'subscribe.movie', admin: false,
footer: true,
}, },
{ {
title: '电视剧', title: '电视剧',
@@ -35,7 +38,8 @@ export const SystemNavMenus = [
icon: 'mdi-television', icon: 'mdi-television',
to: '/subscribe/tv', to: '/subscribe/tv',
header: '订阅', header: '订阅',
permission: 'subscribe.tv', admin: false,
footer: true,
}, },
{ {
title: '日历', title: '日历',
@@ -43,56 +47,56 @@ export const SystemNavMenus = [
icon: 'mdi-calendar', icon: 'mdi-calendar',
to: '/calendar', to: '/calendar',
header: '订阅', header: '订阅',
permission: 'subscribe.calendar', admin: false,
}, },
{ {
title: '正在下载', title: '正在下载',
icon: 'mdi-download-outline', icon: 'mdi-download-outline',
to: '/downloading', to: '/downloading',
header: '整理', header: '整理',
permission: 'downloading.view', admin: false,
}, },
{ {
title: '历史记录', title: '历史记录',
icon: 'mdi-history', icon: 'mdi-history',
to: '/history', to: '/history',
header: '整理', header: '整理',
permission: 'admin', admin: true,
}, },
{ {
title: '文件管理', title: '文件管理',
icon: 'mdi-folder-multiple-outline', icon: 'mdi-folder-multiple-outline',
to: '/filemanager', to: '/filemanager',
header: '整理', header: '整理',
permission: 'admin', admin: true,
}, },
{ {
title: '插件', title: '插件',
icon: 'mdi-apps', icon: 'mdi-apps',
to: '/plugins', to: '/plugins',
header: '系统', header: '系统',
permission: 'admin', admin: true,
}, },
{ {
title: '站点管理', title: '站点管理',
icon: 'mdi-web', icon: 'mdi-web',
to: '/site', to: '/site',
header: '系统', header: '系统',
permission: 'admin', admin: true,
}, },
{ {
title: '用户管理', title: '用户管理',
icon: 'mdi-account-group', icon: 'mdi-account-group',
to: '/user', to: '/user',
header: '系统', header: '系统',
permission: 'usermanage', admin: true,
}, },
{ {
title: '设定', title: '设定',
icon: 'mdi-cog', icon: 'mdi-cog',
to: '/setting', to: '/setting',
header: '系统', header: '系统',
permission: 'admin', admin: true,
}, },
] ]

View File

@@ -5,6 +5,7 @@ interface AuthState {
token: string | null token: string | null
remember: boolean remember: boolean
superUser: boolean superUser: boolean
userID: number
userName: string userName: string
avatar: string avatar: string
originalPath: string | null originalPath: string | null
@@ -24,6 +25,7 @@ const authModule: Module<AuthState, RootState> = {
token: null, // 用户令牌 token: null, // 用户令牌
remember: false, // 记住我 remember: false, // 记住我
superUser: false, // 超级管理员 superUser: false, // 超级管理员
userID: 999, // 用户ID
userName: '', // 用户名 userName: '', // 用户名
avatar: '', // 头像 avatar: '', // 头像
originalPath: null, // 原始路径 originalPath: null, // 原始路径
@@ -43,6 +45,9 @@ const authModule: Module<AuthState, RootState> = {
setSuperUser(state, superUser: boolean) { setSuperUser(state, superUser: boolean) {
state.superUser = superUser state.superUser = superUser
}, },
setUserID(state, userID: number) {
state.userID = userID
},
setUserName(state, userName: string) { setUserName(state, userName: string) {
state.userName = userName state.userName = userName
}, },
@@ -60,10 +65,11 @@ const authModule: Module<AuthState, RootState> = {
}, },
}, },
actions: { actions: {
login({ commit }, { token, remember, superUser, userName, avatar, level, permissions }) { login({ commit }, { token, remember, superUser, userID, userName, avatar, level, permissions }) {
commit('setToken', token) commit('setToken', token)
commit('setRemember', remember) commit('setRemember', remember)
commit('setSuperUser', superUser) commit('setSuperUser', superUser)
commit('setUserID', userID)
commit('setUserName', userName) commit('setUserName', userName)
commit('setAvatar', avatar) commit('setAvatar', avatar)
commit('setLevel', level) commit('setLevel', level)
@@ -78,6 +84,7 @@ const authModule: Module<AuthState, RootState> = {
getToken: state => state.token, getToken: state => state.token,
getRemember: state => state.remember, getRemember: state => state.remember,
getSuperUser: state => state.superUser, getSuperUser: state => state.superUser,
getUserID: state => state.userID,
getUserName: state => state.userName, getUserName: state => state.userName,
getAvatar: state => state.avatar, getAvatar: state => state.avatar,
getOriginalPath: state => state.originalPath, getOriginalPath: state => state.originalPath,

View File

@@ -166,7 +166,7 @@
} }
.grid-directory-card { .grid-directory-card {
grid-template-columns: repeat(auto-fill, minmax(24rem, 1fr)); grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
padding-block-end: 1rem; padding-block-end: 1rem;
} }

View File

@@ -35,7 +35,8 @@ async function loadLatest(server: string) {
onMounted(async () => { onMounted(async () => {
await loadMediaServerSetting() await loadMediaServerSetting()
for (const server of mediaServers.value) { const enabledServers = mediaServers.value.filter(server => server.enabled)
for (const server of enabledServers) {
loadLatest(server.name) loadLatest(server.name)
} }
}) })

View File

@@ -35,7 +35,8 @@ async function loadLibrary(server: string) {
onMounted(async () => { onMounted(async () => {
await loadMediaServerSetting() await loadMediaServerSetting()
for (const server of mediaServers.value) { const enabledServers = mediaServers.value.filter(server => server.enabled)
for (const server of enabledServers) {
loadLibrary(server.name) loadLibrary(server.name)
} }
}) })

View File

@@ -33,7 +33,8 @@ async function loadPlayingList(server: string) {
onMounted(async () => { onMounted(async () => {
await loadMediaServerSetting() await loadMediaServerSetting()
for (const server of mediaServers.value) { const enabledServers = mediaServers.value.filter(server => server.enabled)
for (const server of enabledServers) {
loadPlayingList(server.name) loadPlayingList(server.name)
} }
}) })

View File

@@ -2,15 +2,16 @@
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { requiredValidator } from '@/@validators' import { requiredValidator } from '@/@validators'
import api from '@/api' import api from '@/api'
import { FilterRuleGroup } from '@/api/types'
// 识别结果 // 识别结果
const ruleTestResult = ref('') const ruleTestResult = ref('')
// 名称识别表单 // 名称识别表单
const ruleTestForm = reactive({ const ruleTestForm = reactive({
title: '', title: null,
subtitle: '', subtitle: null,
ruletype: '1', rulegroup: null,
}) })
// 识别按钮状态 // 识别按钮状态
@@ -22,10 +23,27 @@ const ruleTestText = ref('测试')
// 是否显示结果 // 是否显示结果
const showResult = ref(false) const showResult = ref(false)
// 所有规则组列表
const filterRuleGroups = ref<FilterRuleGroup[]>([])
// 规则组选项
const filterRuleGroupItems = computed(() => {
return filterRuleGroups.value.map(item => ({ title: item.name, value: item.name }))
})
// 加载规则组
async function queryFilterRuleGroups() {
try {
const result: { [key: string]: any } = await api.get('system/setting/UserFilterRuleGroups')
filterRuleGroups.value = result.data?.value ?? []
} catch (error) {
console.log(error)
}
}
// 调用API识别 // 调用API识别
async function ruleTest() { async function ruleTest() {
if (!ruleTestForm.title) if (!ruleTestForm.title) return
return
try { try {
ruleTestLoading.value = true ruleTestLoading.value = true
@@ -35,69 +53,41 @@ async function ruleTest() {
params: { params: {
title: ruleTestForm.title, title: ruleTestForm.title,
subtitle: ruleTestForm.subtitle, subtitle: ruleTestForm.subtitle,
ruletype: ruleTestForm.ruletype, rulegroup_name: ruleTestForm.rulegroup,
}, },
}) })
if (result.success) if (result.success) ruleTestResult.value = `优先级:${result.data.priority}`
ruleTestResult.value = `优先级:${result.data.priority}` else ruleTestResult.value = '未命中任何优先级规则!'
else
ruleTestResult.value = '未命中任何优先级规则!'
ruleTestLoading.value = false ruleTestLoading.value = false
ruleTestText.value = '重新测试' ruleTestText.value = '重新测试'
showResult.value = true showResult.value = true
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
} }
} }
onMounted(() => {
queryFilterRuleGroups()
})
</script> </script>
<template> <template>
<VForm @submit.prevent="() => {}"> <VForm @submit.prevent="() => {}">
<VRow class="pt-2"> <VRow class="pt-2">
<VCol cols="12" md="8"> <VCol cols="12" md="8">
<VTextField <VTextField v-model="ruleTestForm.title" label="标题" :rules="[requiredValidator]" />
v-model="ruleTestForm.title"
label="标题"
:rules="[requiredValidator]"
/>
</VCol> </VCol>
<VCol cols="12" md="4"> <VCol cols="12" md="4">
<VSelect <VSelect v-model="ruleTestForm.rulegroup" label="规则组" :items="filterRuleGroupItems" />
v-model="ruleTestForm.ruletype"
label="规则类型"
:items="[{
title: '订阅优先级',
value: '1',
}, {
title: '洗版优先级',
value: '2',
}, {
title: '搜索优先级',
value: '3',
}]"
/>
</VCol> </VCol>
<VCol cols="12"> <VCol cols="12">
<VTextarea <VTextarea v-model="ruleTestForm.subtitle" label="副标题" rows="2" auto-grow />
v-model="ruleTestForm.subtitle"
label="副标题"
rows="2"
auto-grow
/>
</VCol> </VCol>
</VRow> </VRow>
<VRow> <VRow>
<VCol <VCol cols="12" class="text-center">
cols="12" <VBtn :disabled="ruleTestLoading" @click="ruleTest">
class="text-center"
>
<VBtn
:disabled="ruleTestLoading"
@click="ruleTest"
>
<template #prepend> <template #prepend>
<VIcon icon="mdi-filter-check-outline" /> <VIcon icon="mdi-filter-check-outline" />
</template> </template>
@@ -109,9 +99,7 @@ async function ruleTest() {
<VExpandTransition> <VExpandTransition>
<div v-show="showResult"> <div v-show="showResult">
<VCol> <VCol>
<VAlert <VAlert icon="mdi-alert-circle-outline">
icon="mdi-alert-circle-outline"
>
{{ ruleTestResult }} {{ ruleTestResult }}
</VAlert> </VAlert>
</VCol> </VCol>