mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-07-08 22:11:25 +08:00
修改菜单名
添加设置切换动画
This commit is contained in:
467
src/views/setting/AccountSettingAccount.vue
Normal file
467
src/views/setting/AccountSettingAccount.vue
Normal file
@@ -0,0 +1,467 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { requiredValidator } from '@/@validators'
|
||||
import api from '@/api'
|
||||
import type { User } from '@/api/types'
|
||||
import avatar1 from '@images/avatars/avatar-1.png'
|
||||
|
||||
const isNewPasswordVisible = ref(false)
|
||||
const isConfirmPasswordVisible = ref(false)
|
||||
const isPasswordVisible = ref(false)
|
||||
const newPassword = ref('')
|
||||
const confirmPassword = ref('')
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
const refInputEl = ref<HTMLElement>()
|
||||
|
||||
// 新增用户窗口
|
||||
const addUserDialog = ref(false)
|
||||
|
||||
// 新增用户表单
|
||||
const userForm = reactive({
|
||||
name: '',
|
||||
password: '',
|
||||
email: '',
|
||||
})
|
||||
|
||||
// 当前用户信息
|
||||
const accountInfo = ref<User>({
|
||||
id: 0,
|
||||
name: '',
|
||||
password: '',
|
||||
email: '',
|
||||
is_active: false,
|
||||
is_superuser: false,
|
||||
avatar: '',
|
||||
})
|
||||
|
||||
// 所有用户信息
|
||||
const allUsers = ref<User[]>([])
|
||||
|
||||
// changeAvatar function
|
||||
function changeAvatar(file: Event) {
|
||||
const fileReader = new FileReader()
|
||||
const { files } = file.target as HTMLInputElement
|
||||
|
||||
if (files && files.length > 0) {
|
||||
fileReader.readAsDataURL(files[0])
|
||||
fileReader.onload = () => {
|
||||
if (typeof fileReader.result === 'string') {
|
||||
accountInfo.value.avatar = fileReader.result
|
||||
saveAccountInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reset avatar image
|
||||
function resetAvatar() {
|
||||
accountInfo.value.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)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存用户信息
|
||||
async function saveAccountInfo() {
|
||||
if (newPassword.value || confirmPassword.value) {
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
$toast.error('两次输入的密码不一致')
|
||||
|
||||
return
|
||||
}
|
||||
accountInfo.value.password = newPassword.value
|
||||
}
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.put('user', accountInfo.value)
|
||||
if (result.success)
|
||||
$toast.success('用户信息保存成功!')
|
||||
else
|
||||
$toast.error(`用户信息保存失败:${result.message}!`)
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API,查询所有用户
|
||||
async function loadAllUsers() {
|
||||
try {
|
||||
const result: User[] = await api.get('/user')
|
||||
|
||||
allUsers.value = result
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
async function deleteUser(user: User) {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.delete(`user/${user.name}`)
|
||||
if (result.success) {
|
||||
$toast.success('用户删除成功!')
|
||||
loadAllUsers()
|
||||
}
|
||||
else {
|
||||
$toast.error(`用户删除失败:${result.message}!`)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 冻结用户
|
||||
async function deactivateUser(user: User) {
|
||||
try {
|
||||
user.is_active = !user.is_active
|
||||
|
||||
const result: { [key: string]: any } = await api.put('user', user)
|
||||
if (result.success) {
|
||||
$toast.success('用户冻结成功!')
|
||||
loadAllUsers()
|
||||
}
|
||||
else {
|
||||
$toast.error(`用户冻结失败:${result.message}!`)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 新增用户
|
||||
async function addUser() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('user', userForm)
|
||||
if (result.success) {
|
||||
$toast.success('用户新增成功!')
|
||||
loadAllUsers()
|
||||
addUserDialog.value = false
|
||||
}
|
||||
else {
|
||||
$toast.error(`用户新增失败:${result.message}!`)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载当前用户数据
|
||||
onMounted(() => {
|
||||
loadAccountInfo()
|
||||
loadAllUsers()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard title="个人信息">
|
||||
<VCardText class="d-flex">
|
||||
<!-- 👉 Avatar -->
|
||||
<VAvatar
|
||||
rounded="lg"
|
||||
size="100"
|
||||
class="me-6"
|
||||
:image="accountInfo.avatar"
|
||||
/>
|
||||
|
||||
<!-- 👉 Upload Photo -->
|
||||
<form class="d-flex flex-column justify-center gap-5">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<VBtn
|
||||
color="primary"
|
||||
@click="refInputEl?.click()"
|
||||
>
|
||||
<VIcon
|
||||
icon="mdi-cloud-upload-outline"
|
||||
class="d-sm-none"
|
||||
/>
|
||||
<span class="d-none d-sm-block">上传头像</span>
|
||||
</VBtn>
|
||||
|
||||
<input
|
||||
ref="refInputEl"
|
||||
type="file"
|
||||
name="file"
|
||||
accept=".jpeg,.png,.jpg,GIF"
|
||||
hidden
|
||||
@input="changeAvatar"
|
||||
>
|
||||
|
||||
<VBtn
|
||||
type="reset"
|
||||
color="error"
|
||||
variant="tonal"
|
||||
@click="resetAvatar"
|
||||
>
|
||||
<span class="d-none d-sm-block">重置</span>
|
||||
<VIcon
|
||||
icon="mdi-refresh"
|
||||
class="d-sm-none"
|
||||
/>
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<p class="text-body-1 mb-0">
|
||||
允许 JPG、GIF 或 PNG 格式, 最大尽寸 800K。
|
||||
</p>
|
||||
</form>
|
||||
</VCardText>
|
||||
|
||||
<VDivider />
|
||||
|
||||
<VCardText>
|
||||
<!-- 👉 Form -->
|
||||
<VForm class="mt-6">
|
||||
<VRow>
|
||||
<!-- 👉 Name -->
|
||||
<VCol
|
||||
md="6"
|
||||
cols="12"
|
||||
>
|
||||
<VTextField
|
||||
v-model="accountInfo.name"
|
||||
readonly
|
||||
label="用户名"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Email -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="accountInfo.email"
|
||||
label="邮箱"
|
||||
type="email"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 new password -->
|
||||
<VTextField
|
||||
v-model="newPassword"
|
||||
:type="isNewPasswordVisible ? 'text' : 'password'"
|
||||
:append-inner-icon="
|
||||
isNewPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
|
||||
"
|
||||
label="新密码"
|
||||
autocomplete="new-password"
|
||||
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 confirm password -->
|
||||
<VTextField
|
||||
v-model="confirmPassword"
|
||||
:type="isConfirmPasswordVisible ? 'text' : 'password'"
|
||||
:append-inner-icon="
|
||||
isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
|
||||
"
|
||||
label="确认新密码"
|
||||
@click:append-inner="
|
||||
isConfirmPasswordVisible = !isConfirmPasswordVisible
|
||||
"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Form Actions -->
|
||||
<VCol
|
||||
cols="12"
|
||||
class="d-flex flex-wrap gap-4"
|
||||
>
|
||||
<VBtn @click="saveAccountInfo">
|
||||
保存
|
||||
</VBtn>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
v-if="accountInfo.is_superuser"
|
||||
cols="12"
|
||||
>
|
||||
<!-- 👉 Accounts -->
|
||||
<VCard title="所有用户">
|
||||
<template #append>
|
||||
<IconBtn @click.stop="addUserDialog = true">
|
||||
<VIcon icon="mdi-plus" />
|
||||
</IconBtn>
|
||||
</template>
|
||||
<VTable class="text-no-wrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
用户名
|
||||
</th>
|
||||
<th scope="col">
|
||||
邮箱
|
||||
</th>
|
||||
<th scope="col">
|
||||
状态
|
||||
</th>
|
||||
<th scope="col">
|
||||
管理员
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="w-5"
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="user in allUsers"
|
||||
:key="user.name"
|
||||
>
|
||||
<td>
|
||||
{{ user.name }}
|
||||
</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
<VChip
|
||||
v-if="user.is_active"
|
||||
color="success"
|
||||
text-color="white"
|
||||
>
|
||||
激活
|
||||
</VChip>
|
||||
<VChip
|
||||
v-else
|
||||
color="error"
|
||||
text-color="white"
|
||||
>
|
||||
冻结
|
||||
</VChip>
|
||||
</td>
|
||||
<td>{{ user.is_superuser ? "是" : "否" }}</td>
|
||||
<td>
|
||||
<IconBtn v-show="!user.is_superuser">
|
||||
<VIcon icon="mdi-dots-vertical" />
|
||||
<VMenu
|
||||
activator="parent"
|
||||
close-on-content-click
|
||||
>
|
||||
<VList>
|
||||
<VListItem
|
||||
variant="plain"
|
||||
@click="deactivateUser(user)"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-lock" />
|
||||
</template>
|
||||
<VListItemTitle>
|
||||
{{
|
||||
user.is_active ? "冻结" : "解冻"
|
||||
}}
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem
|
||||
variant="plain"
|
||||
base-color="error"
|
||||
@click="deleteUser(user)"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-delete" />
|
||||
</template>
|
||||
<VListItemTitle>删除</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</IconBtn>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<!-- 站点编辑弹窗 -->
|
||||
<VDialog
|
||||
v-model="addUserDialog"
|
||||
max-width="800"
|
||||
persistent
|
||||
>
|
||||
<!-- Dialog Content -->
|
||||
<VCard title="新增用户">
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="userForm.name"
|
||||
label="用户名"
|
||||
:rules="[requiredValidator]"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="userForm.password"
|
||||
label="密码"
|
||||
:rules="[requiredValidator]"
|
||||
:type="isPasswordVisible ? 'text' : 'password'"
|
||||
:append-inner-icon="
|
||||
isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
|
||||
"
|
||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="userForm.email"
|
||||
label="邮箱"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VBtn @click="addUserDialog = false">
|
||||
取消
|
||||
</VBtn>
|
||||
<VSpacer />
|
||||
<VBtn @click="addUser">
|
||||
确定
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
112
src/views/setting/AccountSettingNotification.vue
Normal file
112
src/views/setting/AccountSettingNotification.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import api from '@/api'
|
||||
import type { NotificationSwitch } from '@/api/types'
|
||||
|
||||
const messagemTypes = ref<NotificationSwitch[]>([])
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 调用API查询消息开关
|
||||
async function loadNotificationSwitchs() {
|
||||
try {
|
||||
const result: NotificationSwitch[] = await api.get('message/switchs')
|
||||
|
||||
messagemTypes.value = result
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存消息开关
|
||||
async function saveNotificationSwitchs() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post(
|
||||
'message/switchs',
|
||||
messagemTypes.value,
|
||||
)
|
||||
|
||||
if (result.success)
|
||||
$toast.success('保存通知消息设置成功')
|
||||
else
|
||||
$toast.error('保存通知消息设置失败!')
|
||||
|
||||
// messagemTypes.value = messagemTypes.value
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadNotificationSwitchs()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard title="消息通知">
|
||||
<VCardText> 对应消息类型只会发送给选中的消息渠道 </VCardText>
|
||||
|
||||
<VTable class="text-no-wrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
消息类型
|
||||
</th>
|
||||
<th scope="col">
|
||||
微信
|
||||
</th>
|
||||
<th scope="col">
|
||||
Telegram
|
||||
</th>
|
||||
<th scope="col">
|
||||
Slack
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="message in messagemTypes"
|
||||
:key="message.mtype"
|
||||
>
|
||||
<td>
|
||||
{{ message.mtype }}
|
||||
</td>
|
||||
<td>
|
||||
<VCheckbox v-model="message.wechat" />
|
||||
</td>
|
||||
<td>
|
||||
<VCheckbox v-model="message.telegram" />
|
||||
</td>
|
||||
<td>
|
||||
<VCheckbox v-model="message.slack" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="messagemTypes.length === 0">
|
||||
<td
|
||||
colspan="4"
|
||||
class="text-center"
|
||||
>
|
||||
没有设置任何通知渠道
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
<VDivider />
|
||||
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn
|
||||
mtype="submit"
|
||||
@click="saveNotificationSwitchs"
|
||||
>
|
||||
保存
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
229
src/views/setting/AccountSettingRule.vue
Normal file
229
src/views/setting/AccountSettingRule.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import api from '@/api'
|
||||
import FilterRuleCard from '@/components/cards/FilterRuleCard.vue'
|
||||
|
||||
// 规则卡片类型
|
||||
interface FilterCard {
|
||||
|
||||
// 优先级
|
||||
pri: string
|
||||
|
||||
// 已选规则
|
||||
rules: string[]
|
||||
|
||||
// 是否可见
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 种子优先规则
|
||||
const selectedTorrentPriority = ref<string>('seeder')
|
||||
|
||||
// 种子优先规则下拉框
|
||||
const TorrentPriorityItems = [
|
||||
{ title: '站点优先', value: 'site' },
|
||||
{ title: '做种数优先', value: 'seeder' },
|
||||
]
|
||||
|
||||
// 规则卡片列表
|
||||
const filterCards = ref<FilterCard[]>([])
|
||||
|
||||
// 查询已设置过滤规则
|
||||
async function queryCustomFilters() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/FilterRules')
|
||||
if (result.success) {
|
||||
// 保存的是个字符串,需要分割成数组
|
||||
const groups = result.data?.value.split('>')
|
||||
|
||||
// 生成规则卡片
|
||||
filterCards.value = groups?.map((group: string, index: number) => {
|
||||
return {
|
||||
pri: (index + 1).toString(),
|
||||
rules: group.split('&'),
|
||||
visible: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 查询种子优先规则
|
||||
async function queryTorrentPriority() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get(
|
||||
'system/setting/TorrentsPriority',
|
||||
)
|
||||
|
||||
selectedTorrentPriority.value = result.data?.value
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存用户设置的识别词
|
||||
async function saveCustomFilters() {
|
||||
try {
|
||||
// 有值才处理
|
||||
if (filterCards.value.length === 0)
|
||||
return
|
||||
|
||||
// 将卡片规则接装为字符串
|
||||
const value = filterCards.value
|
||||
.filter(card => card.rules.length > 0)
|
||||
.map(card => card.rules.join('&'))
|
||||
.join('>')
|
||||
|
||||
// 保存
|
||||
const result: { [key: string]: any } = await api.post(
|
||||
'system/setting/FilterRules',
|
||||
value,
|
||||
)
|
||||
|
||||
if (result.success)
|
||||
$toast.success('过滤规则保存成功')
|
||||
else
|
||||
$toast.error('过滤规则保存失败!')
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存种子优先规则
|
||||
async function saveTorrentPriority() {
|
||||
try {
|
||||
// 用户名密码
|
||||
const result: { [key: string]: any } = await api.post(
|
||||
'system/setting/TorrentsPriority',
|
||||
selectedTorrentPriority.value,
|
||||
)
|
||||
|
||||
if (result.success)
|
||||
$toast.success('优先规则保存成功')
|
||||
else
|
||||
$toast.error('优先规则保存失败!')
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新规则卡片的值
|
||||
function updateFilterCardValue(pri: string, rules: string[]) {
|
||||
const card = filterCards.value.find(card => card.pri === pri)
|
||||
if (card)
|
||||
card.rules = rules
|
||||
}
|
||||
|
||||
// 移除卡片
|
||||
function filterCardClose(pri: string) {
|
||||
// 将卡片从列表中删除,并更新剩余卡片的序号
|
||||
const index = filterCards.value.findIndex(card => card.pri === pri)
|
||||
if (index !== -1) {
|
||||
// 创建新的数组,然后使用 splice 方法来删除元素
|
||||
const updatedCards = [...filterCards.value]
|
||||
|
||||
updatedCards.splice(index, 1)
|
||||
|
||||
// 更新剩余卡片的序号
|
||||
updatedCards.forEach((card, i) => {
|
||||
card.pri = (i + 1).toString()
|
||||
})
|
||||
|
||||
// 更新 filterCards.value
|
||||
filterCards.value = updatedCards
|
||||
}
|
||||
}
|
||||
|
||||
// 增加卡片
|
||||
function addFilterCard() {
|
||||
// 优先级
|
||||
const pri = (filterCards.value.length + 1).toString()
|
||||
|
||||
// 新卡片
|
||||
const newCard: FilterCard = { pri, rules: [], visible: true }
|
||||
|
||||
// 添加到列表
|
||||
filterCards.value.push(newCard)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
queryTorrentPriority()
|
||||
queryCustomFilters()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard title="过滤规则">
|
||||
<VCardSubtitle> 设置在搜索和订阅时默认使用的过滤规则 </VCardSubtitle>
|
||||
<VCardItem>
|
||||
<div class="grid gap-3 grid-filterrule-card">
|
||||
<FilterRuleCard
|
||||
v-for="(card, index) in filterCards"
|
||||
:key="index"
|
||||
:pri="card.pri"
|
||||
:rules="card.rules"
|
||||
:visible="card.visible"
|
||||
@changed="updateFilterCardValue"
|
||||
@close="filterCardClose(card.pri)"
|
||||
/>
|
||||
</div>
|
||||
</VCardItem>
|
||||
<VCardItem>
|
||||
<VBtn
|
||||
type="submit"
|
||||
class="me-2"
|
||||
@click="saveCustomFilters"
|
||||
>
|
||||
保存
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="success"
|
||||
variant="tonal"
|
||||
@click="addFilterCard"
|
||||
>
|
||||
<VIcon icon="mdi-plus" />
|
||||
<span class="d-none d-sm-block">增加规则</span>
|
||||
</VBtn>
|
||||
</VCardItem>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VCard title="下载优先规则">
|
||||
<VCardText>
|
||||
<VSelect
|
||||
v-model="selectedTorrentPriority"
|
||||
:items="TorrentPriorityItems"
|
||||
label="优先规则"
|
||||
outlined
|
||||
/>
|
||||
</VCardText>
|
||||
<VCardItem>
|
||||
<VBtn
|
||||
type="submit"
|
||||
@click="saveTorrentPriority"
|
||||
>
|
||||
保存
|
||||
</VBtn>
|
||||
</VCardItem>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.grid-filterrule-card {
|
||||
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
|
||||
padding-block-end: 1rem;
|
||||
}
|
||||
</style>
|
||||
152
src/views/setting/AccountSettingSite.vue
Normal file
152
src/views/setting/AccountSettingSite.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import api from '@/api'
|
||||
import type { Site } from '@/api/types'
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 选中站点
|
||||
const selectedSites = ref<number[]>([])
|
||||
|
||||
// 所有站点
|
||||
const allSites = ref<Site[]>([])
|
||||
|
||||
// 站点重置
|
||||
const isConfirmResetSites = ref(false)
|
||||
|
||||
// 站点重置按钮文本
|
||||
const resetSitesText = ref('重置站点数据')
|
||||
|
||||
// 站点重置按钮可用状态
|
||||
const resetSitesDisabled = ref(false)
|
||||
|
||||
// 查询所有站点
|
||||
async function querySites() {
|
||||
try {
|
||||
const data: Site[] = await api.get('site')
|
||||
|
||||
// 过滤站点,只有启用的站点才显示
|
||||
allSites.value = data.filter(item => item.is_active)
|
||||
querySelectedSites()
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 查询用户选中的站点
|
||||
async function querySelectedSites() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/IndexerSites')
|
||||
|
||||
selectedSites.value = result.data?.value
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存用户选中的站点
|
||||
async function saveSelectedSites() {
|
||||
try {
|
||||
// 用户名密码
|
||||
const result: { [key: string]: any } = await api.post(
|
||||
'system/setting/IndexerSites',
|
||||
selectedSites.value,
|
||||
)
|
||||
|
||||
if (result.success)
|
||||
$toast.success('索引站点保存成功')
|
||||
else
|
||||
$toast.error('索引站点保存失败!')
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 重置站点
|
||||
async function resetSites() {
|
||||
try {
|
||||
resetSitesDisabled.value = true
|
||||
resetSitesText.value = '正在重置...'
|
||||
|
||||
const result: { [key: string]: any } = await api.get('site/reset')
|
||||
if (result.success) {
|
||||
$toast.success('站点重置成功,请等待CookieCloud同步完成!')
|
||||
querySites()
|
||||
}
|
||||
else {
|
||||
$toast.error('站点重置失败!')
|
||||
}
|
||||
resetSitesDisabled.value = false
|
||||
resetSitesText.value = '重置站点数据'
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
querySites()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard title="索引站点">
|
||||
<VCardSubtitle> 只有选中的站点才会在搜索和订阅中使用 </VCardSubtitle>
|
||||
|
||||
<VCardItem>
|
||||
<VChipGroup
|
||||
v-model="selectedSites"
|
||||
column
|
||||
multiple
|
||||
>
|
||||
<VChip
|
||||
v-for="site in allSites"
|
||||
:key="site.id"
|
||||
filter
|
||||
variant="outlined"
|
||||
:value="site.id"
|
||||
>
|
||||
{{ site.name }}
|
||||
</VChip>
|
||||
</VChipGroup>
|
||||
</VCardItem>
|
||||
|
||||
<VCardItem>
|
||||
<VBtn
|
||||
type="submit"
|
||||
@click="saveSelectedSites"
|
||||
>
|
||||
保存
|
||||
</VBtn>
|
||||
</VCardItem>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VCard title="站点重置">
|
||||
<VCardText>
|
||||
<div>
|
||||
<VCheckbox
|
||||
v-model="isConfirmResetSites"
|
||||
label="确认删除所有站点数据并重新同步"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
:disabled="!isConfirmResetSites || resetSitesDisabled"
|
||||
color="error"
|
||||
class="mt-3"
|
||||
@click="resetSites"
|
||||
>
|
||||
{{ resetSitesText }}
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
132
src/views/setting/AccountSettingWords.vue
Normal file
132
src/views/setting/AccountSettingWords.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import api from '@/api'
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 自定义识别词
|
||||
const customIdentifiers = ref('')
|
||||
|
||||
// 自定义制作组
|
||||
const customReleaseGroups = ref('')
|
||||
|
||||
// 查询已设置的识别词
|
||||
async function queryCustomIdentifiers() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get(
|
||||
'system/setting/CustomIdentifiers',
|
||||
)
|
||||
|
||||
customIdentifiers.value = result.data?.value.join('\n')
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 查询已设置的制作组
|
||||
async function queryCustomReleaseGroups() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get(
|
||||
'system/setting/CustomReleaseGroups',
|
||||
)
|
||||
|
||||
customReleaseGroups.value = result.data?.value.join('\n')
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存用户设置的识别词
|
||||
async function saveCustomIdentifiers() {
|
||||
try {
|
||||
// 用户名密码
|
||||
const result: { [key: string]: any } = await api.post(
|
||||
'system/setting/CustomIdentifiers',
|
||||
customIdentifiers.value.split('\n'),
|
||||
)
|
||||
|
||||
if (result.success)
|
||||
$toast.success('自定义识别词保存成功')
|
||||
else
|
||||
$toast.error('自定义识别词保存失败!')
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存自定义制作组
|
||||
async function saveCustomReleaseGroups() {
|
||||
try {
|
||||
// 用户名密码
|
||||
const result: { [key: string]: any } = await api.post(
|
||||
'system/setting/CustomReleaseGroups',
|
||||
customReleaseGroups.value.split('\n'),
|
||||
)
|
||||
|
||||
if (result.success)
|
||||
$toast.success('自定义制作组/字幕组保存成功')
|
||||
else
|
||||
$toast.error('自定义制作组/字幕组保存失败!')
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
queryCustomIdentifiers()
|
||||
queryCustomReleaseGroups()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard title="自定义识别词">
|
||||
<VCardSubtitle> 添加规则对种子名或者文件名进行预处理以校正识别 </VCardSubtitle>
|
||||
<VCardItem>
|
||||
<VTextarea
|
||||
v-model="customIdentifiers"
|
||||
auto-grow
|
||||
placeholder="支持正则表达式,特殊字符需要\转义,一行为一组,支持三种配置格式:
|
||||
屏蔽词
|
||||
被替换词 => 替换词
|
||||
前定位词 <> 后定位词 >> 偏移量(EP)"
|
||||
/>
|
||||
</VCardItem>
|
||||
<VCardItem>
|
||||
<VBtn
|
||||
type="submit"
|
||||
@click="saveCustomIdentifiers"
|
||||
>
|
||||
保存
|
||||
</VBtn>
|
||||
</VCardItem>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VCard title="自定义制作组/字幕组">
|
||||
<VCardSubtitle> 添加无法识别的制作组/字幕组 </VCardSubtitle>
|
||||
<VCardItem>
|
||||
<VTextarea
|
||||
v-model="customReleaseGroups"
|
||||
auto-grow
|
||||
placeholder="支持正则表达式,特殊字符需要\转义,一行代表一个制作组/字幕组"
|
||||
/>
|
||||
</VCardItem>
|
||||
<VCardItem>
|
||||
<VBtn
|
||||
type="submit"
|
||||
@click="saveCustomReleaseGroups"
|
||||
>
|
||||
保存
|
||||
</VBtn>
|
||||
</VCardItem>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
@@ -1,146 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import api from '@/api'
|
||||
import type { Plugin } from '@/api/types'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import PluginAppCard from '@/components/cards/PluginAppCard.vue'
|
||||
import PluginCard from '@/components/cards/PluginCard.vue'
|
||||
|
||||
// 数据列表
|
||||
const dataList = ref<Plugin[]>([])
|
||||
|
||||
// 是否刷新过
|
||||
const isRefreshed = ref(false)
|
||||
|
||||
// APP市场窗口
|
||||
const PluginAppDialog = ref(false)
|
||||
|
||||
// 获取已安装的插件列表
|
||||
const getInstalledPluginList = computed(() => {
|
||||
return dataList.value.filter(item => item.installed)
|
||||
})
|
||||
|
||||
// 获取未安装的插件列表
|
||||
const getUninstalledPluginList = computed(() => {
|
||||
return dataList.value.filter(item => !item.installed)
|
||||
})
|
||||
|
||||
// 关闭插件市场窗口
|
||||
function pluginDialogClose() {
|
||||
PluginAppDialog.value = false
|
||||
}
|
||||
|
||||
// 新安装了插件
|
||||
function pluginInstalled() {
|
||||
fetchData()
|
||||
pluginDialogClose()
|
||||
}
|
||||
|
||||
// 获取插件列表数据
|
||||
async function fetchData() {
|
||||
try {
|
||||
dataList.value = await api.get('plugin')
|
||||
isRefreshed.value = true
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载时获取数据
|
||||
onBeforeMount(fetchData)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VProgressCircular
|
||||
v-if="!isRefreshed"
|
||||
class="centered"
|
||||
indeterminate
|
||||
color="primary"
|
||||
/>
|
||||
<div
|
||||
v-if="dataList.length > 0"
|
||||
class="grid gap-3 grid-plugin-card"
|
||||
>
|
||||
<PluginCard
|
||||
v-for="data in getInstalledPluginList"
|
||||
:key="data.id"
|
||||
:plugin="data"
|
||||
@remove="fetchData"
|
||||
/>
|
||||
</div>
|
||||
<NoDataFound
|
||||
v-if="getInstalledPluginList.length === 0 && isRefreshed"
|
||||
error-code="404"
|
||||
error-title="没有安装插件"
|
||||
error-description="点击右下角按钮,前往插件市场安装插件。"
|
||||
/>
|
||||
<!-- App市场 -->
|
||||
<VDialog
|
||||
v-model="PluginAppDialog"
|
||||
fullscreen
|
||||
:scrim="false"
|
||||
transition="dialog-bottom-transition"
|
||||
>
|
||||
<!-- Dialog Activator -->
|
||||
<template #activator="{ props }">
|
||||
<VBtn
|
||||
icon="mdi-plus"
|
||||
v-bind="props"
|
||||
size="x-large"
|
||||
class="fixed right-5 bottom-5"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Dialog Content -->
|
||||
<VCard>
|
||||
<!-- Toolbar -->
|
||||
<div>
|
||||
<VToolbar color="primary">
|
||||
<VToolbarTitle>插件市场</VToolbarTitle>
|
||||
|
||||
<VSpacer />
|
||||
|
||||
<VToolbarItems>
|
||||
<VBtn
|
||||
size="x-large"
|
||||
@click="pluginDialogClose"
|
||||
>
|
||||
<VIcon
|
||||
color="white"
|
||||
icon="mdi-close"
|
||||
/>
|
||||
</VBtn>
|
||||
</VToolbarItems>
|
||||
</VToolbar>
|
||||
</div>
|
||||
<div class="pa-4">
|
||||
<div class="grid gap-3 grid-plugin-card">
|
||||
<PluginAppCard
|
||||
v-for="data in getUninstalledPluginList"
|
||||
:key="data.id"
|
||||
:plugin="data"
|
||||
@install="pluginInstalled"
|
||||
/>
|
||||
</div>
|
||||
<NoDataFound
|
||||
v-if="getUninstalledPluginList.length === 0 && isRefreshed"
|
||||
error-code="404"
|
||||
error-title="没有未安装插件"
|
||||
error-description="所有可用插件均已安装。"
|
||||
/>
|
||||
</div>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.grid-plugin-card {
|
||||
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
|
||||
padding-block-end: 1rem;
|
||||
}
|
||||
|
||||
dialog-bottom-transition-enter-active,
|
||||
.dialog-bottom-transition-leave-active {
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user