站点拖动排序

This commit is contained in:
jxxghp
2024-05-23 19:39:33 +08:00
parent 11b649dc8c
commit 52cf517a91
17 changed files with 218 additions and 239 deletions
+137 -139
View File
@@ -167,6 +167,12 @@ watch(resourceDialog, value => {
if (!value) getSiteStats() if (!value) getSiteStats()
}) })
// 保存站点
function saveSite() {
siteEditDialog.value = false
emit('update')
}
// 装载时查询站点图标 // 装载时查询站点图标
onMounted(() => { onMounted(() => {
getSiteIcon() getSiteIcon()
@@ -175,150 +181,142 @@ onMounted(() => {
</script> </script>
<template> <template>
<VCard <div>
:height="cardProps.height" <VCard
:width="cardProps.width" :height="cardProps.height"
:variant="cardProps.site?.is_active ? 'elevated' : 'outlined'" :width="cardProps.width"
class="overflow-hidden" :variant="cardProps.site?.is_active ? 'elevated' : 'outlined'"
@click="siteEditDialog = true" class="overflow-hidden"
> @click="siteEditDialog = true"
<template #image> >
<VAvatar class="absolute right-2 bottom-2 rounded" variant="flat" rounded="0"> <template #image>
<VImg :src="siteIcon" /> <VAvatar class="absolute right-2 bottom-2 rounded" variant="flat" rounded="0">
</VAvatar> <VImg :src="siteIcon" />
</template> </VAvatar>
</template>
<VCardItem> <VCardItem>
<VCardTitle class="font-bold"> <VCardTitle class="font-bold">
<span @click.stop="openSitePage">{{ cardProps.site?.name }}</span> <span @click.stop="openSitePage">{{ cardProps.site?.name }}</span>
</VCardTitle> </VCardTitle>
<VCardSubtitle> <VCardSubtitle>
<span @click.stop="openSitePage">{{ cardProps.site?.url }}</span> <span @click.stop="openSitePage">{{ cardProps.site?.url }}</span>
</VCardSubtitle> </VCardSubtitle>
</VCardItem> </VCardItem>
<VCardText class="py-2">
<StatIcon v-if="cardProps.site?.is_active" :color="statColor" /> <VTooltip v-if="cardProps.site?.render === 1" text="浏览器仿真">
<template #activator="{ props }">
<VCardText class="py-2"> <VIcon color="primary" class="me-2" v-bind="props" icon="mdi-apple-safari" />
<VTooltip v-if="cardProps.site?.render === 1" text="浏览器仿真"> </template>
<template #activator="{ props }"> </VTooltip>
<VIcon color="primary" class="me-2" v-bind="props" icon="mdi-apple-safari" /> <VTooltip v-if="cardProps.site?.proxy === 1" text="代理">
</template> <template #activator="{ props }">
</VTooltip> <VIcon color="primary" class="me-2" v-bind="props" icon="mdi-network-outline" />
</template>
<VTooltip v-if="cardProps.site?.proxy === 1" text="代理"> </VTooltip>
<template #activator="{ props }"> <VTooltip v-if="cardProps.site?.limit_interval" text="流控">
<VIcon color="primary" class="me-2" v-bind="props" icon="mdi-network-outline" /> <template #activator="{ props }">
</template> <VIcon color="primary" class="me-2" v-bind="props" icon="mdi-speedometer" />
</VTooltip> </template>
</VTooltip>
<VTooltip v-if="cardProps.site?.limit_interval" text="流控"> <VTooltip v-if="cardProps.site?.filter" text="过滤">
<template #activator="{ props }"> <template #activator="{ props }">
<VIcon color="primary" class="me-2" v-bind="props" icon="mdi-speedometer" /> <VIcon color="primary" class="me-2" v-bind="props" icon="mdi-filter-cog-outline" />
</template> </template>
</VTooltip> </VTooltip>
<VTooltip v-if="cardProps.site?.filter" text="过滤">
<template #activator="{ props }">
<VIcon color="primary" class="me-2" v-bind="props" icon="mdi-filter-cog-outline" />
</template>
</VTooltip>
</VCardText>
<VDivider />
<VCardActions>
<VBtn v-if="!cardProps.site?.public" :disabled="updateButtonDisable" @click.stop="handleSiteUpdate">
<template #prepend>
<VIcon icon="mdi-refresh" />
</template>
更新
</VBtn>
<VBtn :disabled="testButtonDisable" @click.stop="testSite">
<template #prepend>
<VIcon icon="mdi-link" />
</template>
{{ testButtonText }}
</VBtn>
<VBtn @click.stop="handleResourceBrowse">
<template #prepend>
<VIcon icon="mdi-web" />
</template>
浏览
</VBtn>
</VCardActions>
</VCard>
<!-- 更新站点Cookie & UA弹窗 -->
<VDialog v-model="siteCookieDialog" max-width="50rem">
<!-- Dialog Content -->
<VCard title="更新站点Cookie & UA">
<DialogCloseBtn @click="siteCookieDialog = false" />
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<VCol cols="12" md="4">
<VTextField v-model="userPwForm.username" label="用户名" :rules="[requiredValidator]" />
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="userPwForm.password"
label="密码"
:type="isPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
:rules="[requiredValidator]"
@click:append-inner="isPasswordVisible = !isPasswordVisible"
@keydown.enter="updateSiteCookie"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField v-model="userPwForm.code" label="两步验证" />
</VCol>
</VRow>
</VForm>
</VCardText> </VCardText>
<VCardActions>
<VSpacer />
<VBtn variant="elevated" @click="updateSiteCookie" prepend-icon="mdi-refresh" class="px-5"> 开始更新 </VBtn>
</VCardActions>
</VCard>
</VDialog>
<SiteAddEditDialog
v-if="siteEditDialog"
v-model="siteEditDialog"
:siteid="cardProps.site?.id"
@save="
() => {
siteEditDialog = false
emit('update')
}
"
@remove="emit('remove')"
@close="siteEditDialog = false"
/>
<!-- 站点资源弹窗 -->
<VDialog
v-if="resourceDialog"
v-model="resourceDialog"
max-width="80rem"
scrollable
z-index="1010"
:fullscreen="!display.mdAndUp.value"
>
<!-- Dialog Content -->
<VCard :title="`浏览站点 - ${cardProps.site?.name}`">
<DialogCloseBtn @click="resourceDialog = false" />
<VDivider /> <VDivider />
<VCardText class="pt-2"> <VCardActions>
<SiteTorrentTable :site="cardProps.site?.id" /> <VBtn v-if="!cardProps.site?.public" :disabled="updateButtonDisable" @click.stop="handleSiteUpdate">
</VCardText> <template #prepend>
<VIcon icon="mdi-refresh" />
</template>
更新
</VBtn>
<VBtn :disabled="testButtonDisable" @click.stop="testSite">
<template #prepend>
<VIcon icon="mdi-link" />
</template>
{{ testButtonText }}
</VBtn>
<VBtn @click.stop="handleResourceBrowse">
<template #prepend>
<VIcon icon="mdi-web" />
</template>
浏览
</VBtn>
</VCardActions>
<StatIcon v-if="cardProps.site?.is_active" :color="statColor" />
<span class="absolute top-1 right-8">
<VIcon class="cursor-move">mdi-drag</VIcon>
</span>
</VCard> </VCard>
</VDialog> <!-- 更新站点Cookie & UA弹窗 -->
<!-- 进度框 --> <VDialog v-model="siteCookieDialog" max-width="50rem">
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" /> <!-- Dialog Content -->
<VCard title="更新站点Cookie & UA">
<DialogCloseBtn @click="siteCookieDialog = false" />
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<VCol cols="12" md="4">
<VTextField v-model="userPwForm.username" label="用户名" :rules="[requiredValidator]" />
</VCol>
<VCol cols="12" md="4">
<VTextField
v-model="userPwForm.password"
label="密码"
:type="isPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
:rules="[requiredValidator]"
@click:append-inner="isPasswordVisible = !isPasswordVisible"
@keydown.enter="updateSiteCookie"
/>
</VCol>
<VCol cols="12" md="4">
<VTextField v-model="userPwForm.code" label="两步验证" />
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardActions>
<VSpacer />
<VBtn variant="elevated" @click="updateSiteCookie" prepend-icon="mdi-refresh" class="px-5"> 开始更新 </VBtn>
</VCardActions>
</VCard>
</VDialog>
<!-- 站点编辑弹窗 -->
<SiteAddEditDialog
v-if="siteEditDialog"
v-model="siteEditDialog"
:siteid="cardProps.site?.id"
@save="saveSite"
@remove="emit('remove')"
@close="siteEditDialog = false"
/>
<!-- 站点资源弹窗 -->
<VDialog
v-if="resourceDialog"
v-model="resourceDialog"
max-width="80rem"
scrollable
z-index="1010"
:fullscreen="!display.mdAndUp.value"
>
<VCard :title="`浏览站点 - ${cardProps.site?.name}`">
<DialogCloseBtn @click="resourceDialog = false" />
<VDivider />
<VCardText class="pt-2">
<SiteTorrentTable :site="cardProps.site?.id" />
</VCardText>
</VCard>
</VDialog>
<!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
</div>
</template> </template>
<style lang="scss"> <style lang="scss" scoped>
.v-table th { .v-table th {
white-space: nowrap; white-space: nowrap;
} }
+1 -1
View File
@@ -48,7 +48,7 @@ const statusItems = [
// 生成1到50的优先级下拉框选项 // 生成1到50的优先级下拉框选项
const priorityItems = ref( const priorityItems = ref(
Array.from({ length: 50 }, (_, i) => i + 1).map(item => ({ Array.from({ length: 100 }, (_, i) => i + 1).map(item => ({
title: item, title: item,
value: item, value: item,
})), })),
+49
View File
@@ -139,3 +139,52 @@
.apexcharts-title-text { .apexcharts-title-text {
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity)) !important; color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity)) !important;
} }
.grid-site-card {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
padding-block-end: 1rem;
}
.grid-media-card {
grid-template-columns: repeat(auto-fill, minmax(9.375rem, 1fr));
}
.grid-backdrop-card {
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
padding-block-end: 1rem;
}
.grid-torrent-card {
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
padding-block-end: 1rem;
}
.grid-plugin-card {
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
padding-block-end: 1rem;
}
.grid-downloading-card {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
padding-block-end: 1rem;
}
.grid-directory-card {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
padding-block-end: 1rem;
}
.grid-filterrule-card {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
padding-block-end: 1rem;
}
.grid-subscribe-card {
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
padding-block-end: 1rem;
}
.v-tabs:not(.v-tabs-pill).v-tabs--horizontal {
border-block-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
}
@@ -38,9 +38,3 @@ onMounted(() => {
</template> </template>
</VHover> </VHover>
</template> </template>
<style lang="scss">
.grid-media-card {
grid-template-columns: repeat(auto-fill, minmax(9.375rem, 1fr));
}
</style>
@@ -38,10 +38,3 @@ onMounted(() => {
</template> </template>
</VHover> </VHover>
</template> </template>
<style lang="scss">
.grid-backdrop-card {
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
padding-block-end: 1rem;
}
</style>
@@ -38,10 +38,3 @@ onMounted(() => {
</template> </template>
</VHover> </VHover>
</template> </template>
<style lang="scss">
.grid-backdrop-card {
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
padding-block-end: 1rem;
}
</style>
+1 -3
View File
@@ -125,7 +125,5 @@ async function fetchData({ done }: { done: any }) {
</template> </template>
<style lang="scss"> <style lang="scss">
.grid-media-card {
grid-template-columns: repeat(auto-fill, minmax(9.375rem, 1fr));
}
</style> </style>
@@ -124,9 +124,3 @@ async function fetchData({ done }: { done: any }) {
/> />
</VInfiniteScroll> </VInfiniteScroll>
</template> </template>
<style lang="scss">
.grid-media-card {
grid-template-columns: repeat(auto-fill, minmax(9.375rem, 1fr));
}
</style>
+1 -4
View File
@@ -233,8 +233,5 @@ watchEffect(() => {
</template> </template>
<style lang="scss"> <style lang="scss">
.grid-torrent-card {
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
padding-block-end: 1rem;
}
</style> </style>
-11
View File
@@ -516,14 +516,3 @@ onBeforeMount(async () => {
</VCard> </VCard>
</VDialog> </VDialog>
</template> </template>
<style lang="scss">
.grid-plugin-card {
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
padding-block-end: 1rem;
}
.v-tabs:not(.v-tabs-pill).v-tabs--horizontal {
border-block-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
}
</style>
@@ -93,10 +93,3 @@ onUnmounted(() => {
/> />
</PullRefresh> </PullRefresh>
</template> </template>
<style lang="scss">
.grid-downloading-card {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
padding-block-end: 1rem;
}
</style>
@@ -310,9 +310,3 @@ onMounted(() => {
</VCol> </VCol>
</VRow> </VRow>
</template> </template>
<style lang="scss">
.grid-directory-card {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
padding-block-end: 1rem;
}
</style>
@@ -440,10 +440,3 @@ onMounted(() => {
<ImportCodeDialog v-model="importCodeString" title="导入优先级规则" @close="importCodeDialog = false" /> <ImportCodeDialog v-model="importCodeString" title="导入优先级规则" @close="importCodeDialog = false" />
</VDialog> </VDialog>
</template> </template>
<style lang="scss">
.grid-filterrule-card {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
padding-block-end: 1rem;
}
</style>
@@ -550,10 +550,3 @@ onMounted(() => {
<ImportCodeDialog v-model="importCodeString" title="导入优先级规则" @close="importCodeDialog = false" /> <ImportCodeDialog v-model="importCodeString" title="导入优先级规则" @close="importCodeDialog = false" />
</VDialog> </VDialog>
</template> </template>
<style lang="scss">
.grid-filterrule-card {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
padding-block-end: 1rem;
}
</style>
+29 -11
View File
@@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import draggable from 'vuedraggable'
import api from '@/api' import api from '@/api'
import type { Site } from '@/api/types' import type { Site } from '@/api/types'
import SiteCard from '@/components/cards/SiteCard.vue' import SiteCard from '@/components/cards/SiteCard.vue'
@@ -24,16 +25,40 @@ async function fetchData() {
} }
} }
// 保存站点排序
async function savaSitesPriority() {
// 重新排序
const priorities = dataList.value.map((site, index) => ({ id: site.id, pri: index + 1 }))
try {
const result: { [key: string]: any } = await api.post('site/priorities', priorities)
if (result.success) {
fetchData()
}
} catch (error) {
console.error(error)
}
}
// 加载时获取数据 // 加载时获取数据
onBeforeMount(fetchData) onBeforeMount(fetchData)
</script> </script>
<template> <template>
<LoadingBanner v-if="!isRefreshed" class="mt-12" /> <LoadingBanner v-if="!isRefreshed" class="mt-12" />
<div v-if="dataList.length > 0" class="grid gap-3 grid-site-card"> <div>
<div v-for="(data, index) in dataList" :key="index"> <draggable
<SiteCard :key="data.id" :site="data" @remove="fetchData" @update="fetchData" /> v-if="dataList.length > 0"
</div> v-model="dataList"
@end="savaSitesPriority"
handle=".cursor-move"
item-key="id"
tag="div"
:component-data="{ 'class': 'grid gap-3 grid-site-card' }"
>
<template #item="{ element }">
<SiteCard :site="element" @remove="fetchData" @update="fetchData" />
</template>
</draggable>
</div> </div>
<NoDataFound <NoDataFound
v-if="dataList.length === 0 && isRefreshed" v-if="dataList.length === 0 && isRefreshed"
@@ -57,10 +82,3 @@ onBeforeMount(fetchData)
@close="siteAddDialog = false" @close="siteAddDialog = false"
/> />
</template> </template>
<style lang="scss">
.grid-site-card {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
padding-block-end: 1rem;
}
</style>
@@ -123,10 +123,3 @@ const filteredDataList = computed(() => {
" "
/> />
</template> </template>
<style lang="scss">
.grid-subscribe-card {
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
padding-block-end: 1rem;
}
</style>
@@ -129,13 +129,3 @@ async function fetchData({ done }: { done: any }) {
/> />
</VInfiniteScroll> </VInfiniteScroll>
</template> </template>
<style lang="scss">
.grid-media-card {
grid-template-columns: repeat(auto-fill, minmax(9.375rem, 1fr));
}
.v-tabs:not(.v-tabs-pill).v-tabs--horizontal {
border-block-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
}
</style>