Compare commits

...

23 Commits

Author SHA1 Message Date
jxxghp
37f982e0ea add icon 2023-09-08 15:08:32 +08:00
jxxghp
139646369f fix icon 2023-09-08 10:02:43 +08:00
jxxghp
3d4b84dc09 更新 package.json 2023-09-07 17:52:20 +08:00
jxxghp
02866754e0 add icon 2023-09-07 15:50:08 +08:00
jxxghp
e4e1a75d44 fix ui 2023-09-06 21:19:30 +08:00
jxxghp
4e5dd03456 fix ui 2023-09-06 20:22:32 +08:00
jxxghp
506b6eea09 fix rss ui 2023-09-06 20:18:45 +08:00
jxxghp
403ee4b925 fix 2023-09-06 17:26:20 +08:00
jxxghp
193d1f550f fix ui 2023-09-06 15:50:05 +08:00
jxxghp
d2a02a830c fix restart ui 2023-09-06 15:30:10 +08:00
jxxghp
6cc9c1ac57 feat 内建重启 2023-09-06 12:56:49 +08:00
jxxghp
fffb1c6c02 v1.1.4 2023-09-05 19:52:01 +08:00
jxxghp
985d1baff5 feat 新增紫色主题 2023-09-05 10:15:20 +08:00
jxxghp
a70e467b69 fix ui 2023-09-05 09:53:02 +08:00
jxxghp
b07010bebd Merge pull request #35 from amtoaer/memory_percent 2023-09-04 23:18:20 +08:00
amtoaer
353bdc5989 feat: 内存占用图使用百分比 2023-09-04 23:05:16 +08:00
jxxghp
f135804c4b fix ui 2023-09-04 21:30:03 +08:00
jxxghp
ae6d0ead2c v1.1.3 2023-09-04 21:13:27 +08:00
jxxghp
6db3ad4e0d fix ui 2023-09-04 20:07:59 +08:00
jxxghp
09e42d5a08 build 2023-09-04 17:56:32 +08:00
jxxghp
b9a09fd1be fix 用户权限控制 2023-09-04 17:49:17 +08:00
jxxghp
b08235b9f6 fix ui 2023-09-04 12:37:34 +08:00
jxxghp
43e67893b4 fix ui 2023-09-04 12:36:05 +08:00
23 changed files with 227 additions and 77 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "moviepilot",
"version": "1.1.3-3",
"version": "1.1.6-1",
"private": true,
"scripts": {
"dev": "vite --host",

BIN
public/plugin/brush.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
public/plugin/delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -111,7 +111,7 @@ export default defineComponent({
.layout-navbar {
position: fixed;
width: calc(100vw - variables.$layout-vertical-nav-width - 1rem);
width: calc(100vw - variables.$layout-vertical-nav-width - 0.5rem);
z-index: variables.$layout-vertical-nav-layout-navbar-z-index;
inset-block-start: 0;

View File

@@ -38,7 +38,7 @@ body,
overflow: hidden;
// TODO: Use grid gutter variable here
padding-block: 1.5rem;
padding-top: calc(env(safe-area-inset-top) + 65px);
padding-top: calc(env(safe-area-inset-top) + 4.25rem);
// display: flex;
@@ -49,7 +49,7 @@ body,
& > div:first-child {
flex: auto;
position: relative;
width: calc(100vw - variables.$layout-vertical-nav-width - 1rem);
width: calc(100vw - variables.$layout-vertical-nav-width - 0.5rem);
}
}
}

View File

@@ -3,7 +3,7 @@
// 👉 Vertical nav
$layout-vertical-nav-z-index: 12 !default;
$layout-vertical-nav-width: 16.25rem !default;
$layout-vertical-nav-collapsed-width: 80px !default;
$layout-vertical-nav-collapsed-width: 5rem !default;
// 👉 Horizontal nav
$layout-horizontal-nav-z-index: 11 !default;
@@ -16,7 +16,7 @@ $layout-vertical-nav-layout-navbar-z-index: 11 !default;
$layout-horizontal-nav-layout-navbar-z-index: 11 !default;
// 👉 Main content
$layout-boxed-content-width: 1440px !default;
$layout-boxed-content-width: 90rem !default;
// 👉Footer
$layout-vertical-nav-footer-height: 3.5rem !default;

View File

@@ -1,10 +1,7 @@
<script lang="ts" setup>
import { useToast } from 'vue-toast-notification'
import { useTheme } from 'vuetify'
import api from './api'
import type { User } from './api/types'
import store from './store'
import avatar1 from '@images/avatars/avatar-1.png'
// 第一时间应用主题
const { global: globalTheme } = useTheme()
@@ -36,40 +33,10 @@ function startSSEMessager() {
}
}
// 当前用户信息
const accountInfo = ref<User>({
id: 0,
name: '',
password: '',
email: '',
is_active: false,
is_superuser: false,
avatar: avatar1,
})
// 调用API加载当前用户数据
async function loadAccountInfo() {
try {
const user: User = await api.get('user/current')
accountInfo.value = user
if (!accountInfo.value.avatar)
accountInfo.value.avatar = avatar1
}
catch (error) {
console.log(error)
}
}
// 页面加载时,加载当前用户数据
onBeforeMount(async () => {
await loadAccountInfo()
console.log('accountInfo', accountInfo.value)
startSSEMessager()
})
// 提供给所有元素复用
provide('accountInfo', accountInfo)
</script>
<template>

View File

@@ -52,23 +52,23 @@ function openTmdbPage(type: string, tmdbId: number) {
<div>
<VCardItem class="pb-1">
<VCardTitle>
<VCardTitle class="text-center text-md-left">
{{ context?.media_info?.title || context?.meta_info?.name }}
{{ context?.meta_info?.season_episode }}
</VCardTitle>
<VCardSubtitle>
<VCardSubtitle class="text-center text-md-left">
{{ context?.media_info?.year || context?.meta_info?.year }}
</VCardSubtitle>
</VCardItem>
<VCardText
v-if="context?.media_info?.overview"
class="line-clamp-4 overflow-hidden text-ellipsis ..."
class="line-clamp-4 overflow-hidden text-ellipsis text-center text-md-left ..."
>
{{ context?.media_info?.overview }}
</VCardText>
<VCardItem>
<VCardItem class="text-center text-md-left">
<!-- 类型 -->
<VChip
v-if="context?.media_info?.type || context?.meta_info?.type"

View File

@@ -368,6 +368,7 @@ onMounted(() => {
>
<!-- Dialog Content -->
<VCard :title="`订阅 - ${props.media?.name}`">
<DialogCloseBtn @click="rssInfoDialog = false" />
<VCardText class="pt-2">
<VForm @submit.prevent="() => {}">
<VRow>
@@ -425,6 +426,7 @@ onMounted(() => {
</VCol>
<VCol
cols="12"
md="6"
>
<VTextField
v-model="rssForm.include"
@@ -433,6 +435,7 @@ onMounted(() => {
</VCol>
<VCol
cols="12"
md="6"
>
<VTextField
v-model="rssForm.exclude"
@@ -441,6 +444,7 @@ onMounted(() => {
</VCol>
<VCol
cols="12"
md="6"
>
<VTextField
v-model="rssForm.save_path"

View File

@@ -749,7 +749,6 @@ onMounted(() => {
<!-- 识别结果对话框 -->
<vDialog
v-model="nameTestDialog"
:scrim="false"
width="800"
>
<vCard>

View File

@@ -1,5 +1,4 @@
<script lang="ts" setup>
import type { User } from '@/api/types'
import VerticalNavSectionTitle from '@/@layouts/components/VerticalNavSectionTitle.vue'
import VerticalNavLayout from '@layouts/components/VerticalNavLayout.vue'
import VerticalNavLink from '@layouts/components/VerticalNavLink.vue'
@@ -10,9 +9,10 @@ import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
import SearchBar from '@/layouts/components/SearchBar.vue'
import ShortcutBar from '@/layouts/components/ShortcutBar.vue'
import UserProfile from '@/layouts/components/UserProfile.vue'
import store from '@/store'
// 获取当前用户信息
const accountInfo: User = inject('accountInfo') as User
// 从Vuex Store中获取superuser信息
const superUser = store.state.auth.superUser
</script>
<template>
@@ -91,7 +91,7 @@ const accountInfo: User = inject('accountInfo') as User
}"
/>
<VerticalNavLink
v-if="accountInfo.is_superuser"
v-if="superUser"
:item="{
title: '电影',
icon: 'mdi-movie-check-outline',
@@ -99,7 +99,7 @@ const accountInfo: User = inject('accountInfo') as User
}"
/>
<VerticalNavLink
v-if="accountInfo.is_superuser"
v-if="superUser"
:item="{
title: '电视剧',
icon: 'mdi-television-classic',
@@ -107,7 +107,7 @@ const accountInfo: User = inject('accountInfo') as User
}"
/>
<VerticalNavLink
v-if="accountInfo.is_superuser"
v-if="superUser"
:item="{
title: '自定义',
icon: 'mdi-rss',
@@ -135,7 +135,7 @@ const accountInfo: User = inject('accountInfo') as User
}"
/>
<VerticalNavLink
v-if="accountInfo.is_superuser"
v-if="superUser"
:item="{
title: '历史记录',
icon: 'mdi-history',
@@ -143,7 +143,7 @@ const accountInfo: User = inject('accountInfo') as User
}"
/>
<VerticalNavLink
v-if="accountInfo.is_superuser"
v-if="superUser"
:item="{
title: '文件管理',
icon: 'mdi-folder-multiple-outline',
@@ -153,13 +153,13 @@ const accountInfo: User = inject('accountInfo') as User
<!-- 👉 系统 -->
<VerticalNavSectionTitle
v-if="accountInfo.is_superuser"
v-if="superUser"
:item="{
heading: '系统',
}"
/>
<VerticalNavLink
v-if="accountInfo.is_superuser"
v-if="superUser"
:item="{
title: '插件',
icon: 'mdi-apps',
@@ -167,7 +167,7 @@ const accountInfo: User = inject('accountInfo') as User
}"
/>
<VerticalNavLink
v-if="accountInfo.is_superuser"
v-if="superUser"
:item="{
title: '站点管理',
icon: 'mdi-web',
@@ -175,7 +175,7 @@ const accountInfo: User = inject('accountInfo') as User
}"
/>
<VerticalNavLink
v-if="accountInfo.is_superuser"
v-if="superUser"
:item="{
title: '设定',
icon: 'mdi-cog',

View File

@@ -10,6 +10,10 @@ const themes: ThemeSwitcherTheme[] = [
name: 'dark',
icon: 'mdi-weather-night',
},
{
name: 'purple',
icon: 'mdi-brightness-4',
},
]
</script>

View File

@@ -1,11 +1,23 @@
<script setup lang="ts">
import { useStore } from 'vuex'
import { useConfirm } from 'vuetify-use-dialog'
import { useToast } from 'vue-toast-notification'
import router from '@/router'
import type { User } from '@/api/types'
import avatar1 from '@images/avatars/avatar-1.png'
import api from '@/api'
// Vuex Store
const store = useStore()
// 确认框
const createConfirm = useConfirm()
// 提示框
const $toast = useToast()
// 进度框
const progressDialog = ref(false)
// 执行注销操作
function logout() {
// 清除登录状态信息
@@ -15,8 +27,45 @@ function logout() {
router.push('/login')
}
// 获取当前用户信息
const accountInfo: User = inject('accountInfo') as User
// 执行重启操作
async function restart() {
// 弹出提示
const confirmed = await createConfirm({
title: '确认',
content: '确认重启系统吗?',
confirmationText: '确认',
cancellationText: '取消',
dialogProps: {
maxWidth: '30rem',
},
})
if (confirmed) {
// 调用API重启
try {
// 显示等待框
progressDialog.value = true
const result: { [key: string]: any } = await api.get('system/restart')
if (!result?.success) {
// 隐藏等待框
progressDialog.value = false
// 重启不成功
$toast.error(result.message)
return
}
}
catch (error) {
console.error(error)
}
// 注销
logout()
}
}
// 从Vuex Store中获取信息
const superUser = store.state.auth.superUser
const userName = store.state.auth.userName
const avatar = store.state.auth.avatar
</script>
<template>
@@ -25,7 +74,7 @@ const accountInfo: User = inject('accountInfo') as User
color="primary"
variant="tonal"
>
<VImg :src="accountInfo.avatar" />
<VImg :src="avatar ?? avatar1" />
<!-- SECTION Menu -->
<VMenu
@@ -43,21 +92,21 @@ const accountInfo: User = inject('accountInfo') as User
color="primary"
variant="tonal"
>
<VImg :src="accountInfo.avatar" />
<VImg :src="avatar ?? avatar1" />
</VAvatar>
</VListItemAction>
</template>
<VListItemTitle class="font-weight-semibold">
{{ accountInfo.is_superuser ? "管理员" : "普通用户" }}
{{ superUser ? "管理员" : "普通用户" }}
</VListItemTitle>
<VListItemSubtitle>{{ accountInfo.name }}</VListItemSubtitle>
<VListItemSubtitle>{{ userName }}</VListItemSubtitle>
</VListItem>
<VDivider class="my-2" />
<!-- 👉 Profile -->
<VListItem
v-if="accountInfo.is_superuser"
v-if="superUser"
link
to="setting"
>
@@ -91,6 +140,19 @@ const accountInfo: User = inject('accountInfo') as User
<!-- Divider -->
<VDivider class="my-2" />
<!-- 👉 restart -->
<VListItem @click="restart">
<template #prepend>
<VIcon
class="me-2"
icon="mdi-restart"
size="22"
/>
</template>
<VListItemTitle>重启</VListItemTitle>
</VListItem>
<!-- 👉 Logout -->
<VListItem @click="logout">
<template #prepend>
@@ -107,4 +169,22 @@ const accountInfo: User = inject('accountInfo') as User
</VMenu>
<!-- !SECTION -->
</VAvatar>
<!-- 重启进度框 -->
<vDialog
v-model="progressDialog"
width="400"
>
<vCard
color="primary"
>
<vCardText class="text-center">
正在重启 ...
<vProgressLinear
indeterminate
color="white"
class="mb-0 mt-1"
/>
</vCardText>
</vCard>
</vDialog>
</template>

View File

@@ -66,10 +66,16 @@ function login() {
.then((response: any) => {
// 获取token
const token = response.access_token
const superuser = response.super_user
const username = response.user_name
const avatar = response.avatar
// 更新token和remember状态到Vuex Store
store.dispatch('auth/updateToken', token)
store.dispatch('auth/updateRemember', form.value.remember)
store.dispatch('auth/updateSuperUser', superuser)
store.dispatch('auth/updateUserName', username)
store.dispatch('auth/updateAvatar', avatar)
// 跳转到首页
router.push('/')

View File

@@ -57,7 +57,7 @@ const theme: VuetifyOptions['theme'] = {
dark: {
dark: true,
colors: {
'primary': '#9155FD',
'primary': '#6E66ED',
'secondary': '#8A8D93',
'on-secondary': '#fff',
'success': '#56CA00',
@@ -104,6 +104,57 @@ const theme: VuetifyOptions['theme'] = {
'shadow-key-ambient-opacity': 'rgba(20, 18, 33, 0.04)',
},
},
purple: {
dark: true,
colors: {
'primary': '#9155FD',
'secondary': '#8A8D93',
'on-secondary': '#fff',
'success': '#56CA00',
'info': '#16B1FF',
'warning': '#FFB400',
'error': '#FF4C51',
'on-primary': '#FFFFFF',
'on-success': '#FFFFFF',
'on-warning': '#FFFFFF',
'background': '#28243D',
'on-background': '#E7E3FC',
'surface': '#312D4B',
'on-surface': '#E7E3FC',
'grey-50': '#2A2E42',
'grey-100': '#474360',
'grey-200': '#4A5072',
'grey-300': '#5E6692',
'grey-400': '#7983BB',
'grey-500': '#8692D0',
'grey-600': '#AAB3DE',
'grey-700': '#B6BEE3',
'grey-800': '#CFD3EC',
'grey-900': '#E7E9F6',
'perfect-scrollbar-thumb': '#4A5072',
'skin-bordered-background': '#312d4b',
'skin-bordered-surface': '#312d4b',
},
variables: {
'code-color': '#d400ff',
'overlay-scrim-background': '#2C2942',
'overlay-scrim-opacity': 0.6,
'hover-opacity': 0.04,
'focus-opacity': 0.1,
'selected-opacity': 0.12,
'activated-opacity': 0.1,
'pressed-opacity': 0.14,
'dragged-opacity': 0.1,
'border-color': '#E7E3FC',
'table-header-background': '#3D3759',
'custom-background': '#373452',
// Shadows
'shadow-key-umbra-opacity': 'rgba(20, 18, 33, 0.08)',
'shadow-key-penumbra-opacity': 'rgba(20, 18, 33, 0.12)',
'shadow-key-ambient-opacity': 'rgba(20, 18, 33, 0.04)',
},
},
},
}

View File

@@ -4,6 +4,9 @@ import type { Module } from 'vuex'
interface AuthState {
token: string | null
remember: boolean
superUser: boolean
userName: string
avatar: string
}
// 定义根状态类型
@@ -17,6 +20,9 @@ const authModule: Module<AuthState, RootState> = {
state: {
token: null,
remember: false,
superUser: false,
userName: '',
avatar: '',
},
mutations: {
setToken(state, token: string) {
@@ -28,6 +34,15 @@ const authModule: Module<AuthState, RootState> = {
setRemember(state, remember: boolean) {
state.remember = remember
},
setSuperUser(state, superUser: boolean) {
state.superUser = superUser
},
setUserName(state, userName: string) {
state.userName = userName
},
setAvatar(state, avatar: string) {
state.avatar = avatar
},
},
actions: {
updateToken({ commit }, token: string) {
@@ -39,10 +54,22 @@ const authModule: Module<AuthState, RootState> = {
updateRemember({ commit }, remember: boolean) {
commit('setRemember', remember)
},
updateSuperUser({ commit }, superUser: boolean) {
commit('setSuperUser', superUser)
},
updateUserName({ commit }, userName: string) {
commit('setUserName', userName)
},
updateAvatar({ commit }, avatar: string) {
commit('setAvatar', avatar)
},
},
getters: {
getToken: state => state.token,
getRemember: state => state.remember,
getSuperUser: state => state.superUser,
getUserName: state => state.userName,
getAvatar: state => state.avatar,
},
}

View File

@@ -3,14 +3,13 @@
@tailwind components;
@tailwind utilities;
#nprogress .bar {
background: #7D34FD !important;
background: rgb(var(--v-theme-primary)) !important;
top: env(safe-area-inset-top) !important;
}
#nprogress .peg {
box-shadow: 0 0 10px #7D34FD, 0 0 5px #7D34FD !important;
box-shadow: 0 0 10px rgb(var(--v-theme-primary)), 0 0 5px rgb(var(--v-theme-primary)) !important;
-webkit-transform: rotate(0deg) translate(0px, -1px);
-ms-transform: rotate(0deg) translate(0px, -1px);
transform: rotate(0deg) translate(0px, -1px);

View File

@@ -27,6 +27,7 @@ const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
chart: {
parentHeightOffset: 0,
toolbar: { show: false },
animations: { enabled: false },
},
tooltip: { enabled: false },
grid: {
@@ -75,6 +76,7 @@ const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
},
yaxis: {
labels: { show: false },
max: 100,
},
}
})

View File

@@ -20,14 +20,17 @@ const series = ref([
},
])
// 当前值
const current = ref(0)
// 占用的内存
const usedMemory = ref(0)
// 内存使用百分比
const memoryUsage = ref(0)
const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
return {
chart: {
parentHeightOffset: 0,
toolbar: { show: false },
animations: { enabled: false },
},
tooltip: { enabled: false },
grid: {
@@ -79,6 +82,7 @@ const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
},
yaxis: {
labels: { show: false },
max: 100,
},
}
})
@@ -87,9 +91,8 @@ const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
async function getMemorgUsage() {
try {
// 请求数据
current.value = await api.get('dashboard/memory') ?? 0
// 添加到序列
series.value[0].data.push(current.value / 1024 / 1024 / 1024)
[usedMemory.value, memoryUsage.value] = await api.get('dashboard/memory')
series.value[0].data.push(memoryUsage.value)
// 序列超过30条记录时清掉前面的
if (series.value[0].data.length > 30)
series.value[0].data.shift()
@@ -130,7 +133,7 @@ onUnmounted(() => {
/>
<p class="text-center font-weight-medium mb-0">
当前{{ formatBytes(current) }}
当前{{ formatBytes(usedMemory) }}
</p>
</VCardText>
</VCard>

View File

@@ -430,9 +430,13 @@ onBeforeMount(() => {
<div class="relative z-20 flex items-center false"><span>已入库</span></div>
</span>
</div>
<h1 class="flex flex-row items-baseline justify-start lg:justify-center">
<span>{{ mediaDetail.title }}</span>
<span v-if="mediaDetail.year" class="text-lg">{{ mediaDetail.year }}</span>
<h1 class="d-flex flex-column flex-lg-row align-baseline justify-center justify-lg-start">
<div class="align-self-center align-self-lg-end">
{{ mediaDetail.title }}
</div>
<div v-if="mediaDetail.year" class="text-lg align-self-center align-self-lg-end">
{{ mediaDetail.year }}
</div>
</h1>
<span class="media-attributes">
<span v-if="mediaDetail.runtime || mediaDetail.episode_run_time[0]">{{ mediaDetail.runtime || mediaDetail.episode_run_time[0] }} 分钟</span>

View File

@@ -176,6 +176,7 @@ function onRefresh() {
<!-- Dialog Content -->
<VCard title="新增自定义订阅">
<DialogCloseBtn @click="rssAddDialog = false" />
<VCardText class="pt-2">
<VForm @submit.prevent="() => {}">
<VRow>
@@ -232,6 +233,7 @@ function onRefresh() {
</VCol>
<VCol
cols="12"
md="6"
>
<VTextField
v-model="rssForm.include"
@@ -241,6 +243,7 @@ function onRefresh() {
</VCol>
<VCol
cols="12"
md="6"
>
<VTextField
v-model="rssForm.exclude"
@@ -250,6 +253,7 @@ function onRefresh() {
</VCol>
<VCol
cols="12"
md="6"
>
<VTextField
v-model="rssForm.save_path"