1277 lines
38 KiB
Vue
1277 lines
38 KiB
Vue
<script lang="ts" setup>
|
||
import { getBase64 } from '@/utils/file';
|
||
import { FormInstance } from 'ant-design-vue';
|
||
import { computed, reactive, ref, onMounted, onBeforeUnmount, UnwrapRef, watch, nextTick } from 'vue';
|
||
import dayjs from 'dayjs';
|
||
import { Dayjs } from 'dayjs';
|
||
import {
|
||
EditFilled,
|
||
DeleteFilled,
|
||
SearchOutlined,
|
||
PlusOutlined,
|
||
ExclamationCircleOutlined,
|
||
StopOutlined,
|
||
ClockCircleOutlined,
|
||
DeleteOutlined,
|
||
} from '@ant-design/icons-vue';
|
||
import { useApiStore } from '@/store';
|
||
import { message } from 'ant-design-vue';
|
||
|
||
import { StarOutlined, StarFilled, StarTwoTone } from '@ant-design/icons-vue';
|
||
|
||
const storageType = ref(0);
|
||
const isRemoteStorage = computed(() => storageType.value === 1 || storageType.value === 2);
|
||
const columns = computed(() => [
|
||
{ title: 'Cookie名称', dataIndex: 'userName', width: 180 },
|
||
{ title: 'Cookie状态', dataIndex: 'statusMsg', width: 120 },
|
||
{ title: '收藏路径', dataIndex: isRemoteStorage.value ? 'webDavCollectPath' : 'savePath', width: 240 },
|
||
{ title: '喜欢路径', dataIndex: isRemoteStorage.value ? 'webDavFavoritePath' : 'favSavePath', width: 240 },
|
||
{ title: '博主路径', dataIndex: isRemoteStorage.value ? 'webDavFollowPath' : 'upSavePath', width: 240 },
|
||
{ title: '状态', dataIndex: 'status', width: 180 },
|
||
{ title: '操作', dataIndex: 'edit', width: 200 },
|
||
]);
|
||
|
||
const isMobile = ref(window.innerWidth <= 768);
|
||
const updateViewport = () => (isMobile.value = window.innerWidth <= 768);
|
||
const drawerWidth = computed(() => (isMobile.value ? '100%' : '800px'));
|
||
|
||
interface UpSecUserIdItem {
|
||
uper?: string;
|
||
uid?: string;
|
||
syncAll: boolean;
|
||
}
|
||
|
||
type DataItem = {
|
||
id?: string;
|
||
userName?: string;
|
||
cookies?: string;
|
||
savePath?: string;
|
||
favSavePath?: string;
|
||
secUserId?: string;
|
||
status?: number;
|
||
statusMsg?: string;
|
||
statusCode?: number;
|
||
_isNew?: boolean;
|
||
upSecUserIdsJson?: UpSecUserIdItem[];
|
||
upSecUserIds?: string;
|
||
upSavePath?: string;
|
||
useCollectFolder?: boolean;
|
||
downMix?: boolean;
|
||
downSeries?: boolean;
|
||
mixPath?: string;
|
||
seriesPath?: string;
|
||
downCollect?: boolean;
|
||
downFavorite?: boolean;
|
||
downFollowd?: boolean;
|
||
webDavCollectPath?: string;
|
||
webDavFavoritePath?: string;
|
||
webDavFollowPath?: string;
|
||
webDavMixPath?: string;
|
||
webDavSeriesPath?: string;
|
||
sourceCooldownUntil?: string;
|
||
sourceRequiresAuthorization?: boolean;
|
||
sourceProbePending?: boolean;
|
||
lastSourceStatusCode?: number;
|
||
lastSourceError?: string;
|
||
};
|
||
|
||
const loading = ref(false);
|
||
const datas: UnwrapRef<DataItem[]> = reactive([]);
|
||
const pagination = ref({
|
||
current: 1,
|
||
defaultPageSize: 10,
|
||
total: 0,
|
||
showTotal: () => `共 ${0} 条`,
|
||
});
|
||
|
||
interface QuaryParam {
|
||
pageIndex: number;
|
||
pageSize: number;
|
||
}
|
||
const quaryData: UnwrapRef<QuaryParam> = reactive({
|
||
pageIndex: 0,
|
||
pageSize: 20,
|
||
});
|
||
|
||
const GetRecords = () => {
|
||
loading.value = true;
|
||
quaryData.pageIndex = pagination.value.current;
|
||
quaryData.pageSize = pagination.value.defaultPageSize;
|
||
useApiStore()
|
||
.CookiePageList(quaryData)
|
||
.then((res) => {
|
||
loading.value = false;
|
||
if (res.code === 0) {
|
||
dataSource.value = res.data.data;
|
||
pagination.value.current = res.data.pageIndex;
|
||
pagination.value.defaultPageSize = res.data.pageSize;
|
||
pagination.value.total = res.data.total;
|
||
pagination.value.showTotal = () => `共 ${res.data.total} 条`;
|
||
}
|
||
});
|
||
};
|
||
|
||
function addNew() {
|
||
showModal.value = true;
|
||
form._isNew = true;
|
||
}
|
||
|
||
const showModal = ref(false);
|
||
|
||
const newCookie = (cookie?: DataItem) => {
|
||
if (!cookie) {
|
||
cookie = { _isNew: true };
|
||
}
|
||
cookie.userName = undefined;
|
||
cookie.cookies = undefined;
|
||
cookie.savePath = undefined;
|
||
cookie.favSavePath = undefined;
|
||
cookie.secUserId = undefined;
|
||
cookie.status = 0;
|
||
cookie.id = '0';
|
||
cookie.upSecUserIdsJson = undefined;
|
||
cookie.upSavePath = undefined;
|
||
cookie.useCollectFolder = false;
|
||
cookie.downMix = false;
|
||
cookie.downSeries = false;
|
||
cookie.mixPath = undefined;
|
||
cookie.seriesPath = undefined;
|
||
cookie.downCollect = false;
|
||
cookie.downFavorite = false;
|
||
cookie.downFollowd = false;
|
||
cookie.webDavCollectPath = undefined;
|
||
cookie.webDavFavoritePath = undefined;
|
||
cookie.webDavFollowPath = undefined;
|
||
cookie.webDavMixPath = undefined;
|
||
cookie.webDavSeriesPath = undefined;
|
||
return cookie;
|
||
};
|
||
|
||
const copyObject = (target: any, source?: any) => {
|
||
if (!source) {
|
||
return target;
|
||
}
|
||
Object.keys(target).forEach((key) => (target[key] = source[key]));
|
||
};
|
||
|
||
const form = reactive<DataItem>(newCookie());
|
||
|
||
function reset() {
|
||
return newCookie(form);
|
||
}
|
||
|
||
function cancel() {
|
||
showModal.value = false;
|
||
reset();
|
||
}
|
||
|
||
const formModel = ref<FormInstance>();
|
||
|
||
const formLoading = ref(false);
|
||
|
||
function submit() {
|
||
formLoading.value = true;
|
||
|
||
formModel.value
|
||
?.validateFields()
|
||
.then((resData: DataItem) => {
|
||
if (form._isNew) {
|
||
} else {
|
||
copyObject(editRecord.value, resData);
|
||
}
|
||
useApiStore()
|
||
.UpdateConfig(resData)
|
||
.then((res) => {
|
||
loading.value = false;
|
||
if (res.code === 0) {
|
||
showModal.value = false;
|
||
message.success('保存成功,同步任务将在5-10秒后重新启动...');
|
||
reset();
|
||
GetRecords();
|
||
} else {
|
||
message.error('保存失败' + res.message);
|
||
}
|
||
});
|
||
})
|
||
.catch((e) => {
|
||
console.error(e);
|
||
})
|
||
.finally(() => {
|
||
formLoading.value = false;
|
||
});
|
||
}
|
||
|
||
const editRecord = ref<DataItem>();
|
||
|
||
import { Modal } from 'ant-design-vue';
|
||
|
||
const deleted = (id: string) => {
|
||
Modal.confirm({
|
||
title: '确认删除',
|
||
content: '确定要删除这条记录吗?此操作不可撤销。',
|
||
okText: '确认',
|
||
cancelText: '取消',
|
||
onOk: () => {
|
||
useApiStore()
|
||
.deleteCookie(id)
|
||
.then((res) => {
|
||
loading.value = false;
|
||
if (res.code === 0) {
|
||
showModal.value = false;
|
||
reset();
|
||
GetRecords();
|
||
}
|
||
});
|
||
},
|
||
onCancel: () => {
|
||
console.log('已取消删除');
|
||
},
|
||
});
|
||
};
|
||
|
||
function edit(record: DataItem) {
|
||
cookieId.value = record.id;
|
||
editRecord.value = record;
|
||
console.log(record);
|
||
copyObject(form, record);
|
||
showModal.value = true;
|
||
}
|
||
|
||
const switchSyncStatus = (record: DataItem) => {
|
||
const statusText = record.status === 1 ? '开启' : '停止';
|
||
const title = `确认${statusText}同步`;
|
||
const content = `确定要${statusText}【${record.userName || '该'}】Cookie的同步任务吗?`;
|
||
|
||
Modal.confirm({
|
||
title,
|
||
content,
|
||
okText: '确认',
|
||
cancelText: '取消',
|
||
onOk: () => {
|
||
loading.value = true;
|
||
useApiStore()
|
||
.SwitchCookieStatus({
|
||
id: record.id,
|
||
status: record.status,
|
||
})
|
||
.then((res) => {
|
||
loading.value = false;
|
||
if (res.code === 0) {
|
||
message.success(`${statusText}同步成功`);
|
||
GetRecords();
|
||
} else {
|
||
message.error(`${statusText}同步失败:${res.message || '未知错误'}`);
|
||
record.status = record.status === 1 ? 0 : 1;
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
loading.value = false;
|
||
console.error('切换同步状态失败:', err);
|
||
message.error('切换同步状态失败,请稍后重试');
|
||
record.status = record.status === 1 ? 0 : 1;
|
||
});
|
||
},
|
||
onCancel: () => {
|
||
console.log(`已取消${statusText}同步`);
|
||
record.status = record.status === 1 ? 0 : 1;
|
||
},
|
||
});
|
||
};
|
||
const StatusDict: Record<number, string> = {
|
||
0: '同步已停止',
|
||
1: '同步已开启',
|
||
};
|
||
|
||
const dataSource = ref(datas);
|
||
|
||
const addRow = () => {
|
||
if (!form.upSecUserIdsJson) {
|
||
form.upSecUserIdsJson = [];
|
||
}
|
||
form.upSecUserIdsJson.push({ uper: '', uid: '', syncAll: false });
|
||
};
|
||
const removeRow = (index: number) => {
|
||
if (form.upSecUserIdsJson) {
|
||
form.upSecUserIdsJson.splice(index, 1);
|
||
}
|
||
};
|
||
const rowCount = 10;
|
||
const handlePageChange = (page: number, pageSize: number) => {
|
||
pagination.value.current = page;
|
||
pagination.value.defaultPageSize = pageSize;
|
||
GetRecords();
|
||
};
|
||
|
||
onMounted(async () => {
|
||
window.addEventListener('resize', updateViewport);
|
||
try {
|
||
const res = await useApiStore().StorageConfig();
|
||
if (res.code === 0) storageType.value = Number(res.data.storageType ?? 0);
|
||
} catch {
|
||
message.warning('读取存储模式失败,路径表单暂按本地存储显示');
|
||
}
|
||
GetRecords();
|
||
});
|
||
|
||
onBeforeUnmount(() => {
|
||
window.removeEventListener('resize', updateViewport);
|
||
});
|
||
|
||
const showDrawer = ref(false);
|
||
type DrawerType = 'collect' | 'mix' | 'series';
|
||
|
||
const cookieId = ref('');
|
||
const cateType = ref(5);
|
||
|
||
const drawerType = ref<DrawerType>('collect');
|
||
const drawerScrollRef = ref<HTMLDivElement | null>(null);
|
||
const drawerDataList = ref<DrawerItem[]>([]);
|
||
const drawerPagination = reactive({
|
||
current: 1,
|
||
pageSize: 10,
|
||
total: 0,
|
||
loading: false,
|
||
hasMore: true,
|
||
});
|
||
|
||
interface DrawerItem {
|
||
id: string;
|
||
name: string;
|
||
saveFolder: string;
|
||
sync: boolean;
|
||
coverUrl: string;
|
||
cookieId: string;
|
||
xId: string;
|
||
total: number;
|
||
}
|
||
|
||
const openCollectFolderSetModal = () => {
|
||
drawerType.value = 'collect';
|
||
cateType.value = 5;
|
||
openCommonDrawer();
|
||
};
|
||
|
||
const openMixDownSetModal = () => {
|
||
drawerType.value = 'mix';
|
||
cateType.value = 6;
|
||
openCommonDrawer();
|
||
};
|
||
|
||
const openSeriesDownSetModal = () => {
|
||
drawerType.value = 'series';
|
||
cateType.value = 7;
|
||
openCommonDrawer();
|
||
};
|
||
|
||
const openCommonDrawer = () => {
|
||
drawerDataList.value = [];
|
||
drawerPagination.current = 1;
|
||
drawerPagination.total = 0;
|
||
drawerPagination.hasMore = true;
|
||
showDrawer.value = true;
|
||
|
||
nextTick()
|
||
.then(() => {
|
||
loadDrawerData();
|
||
bindDrawerScrollEvent();
|
||
})
|
||
.catch(() => {});
|
||
};
|
||
|
||
const bindDrawerScrollEvent = () => {
|
||
const scrollContainer = drawerScrollRef.value;
|
||
if (!scrollContainer) {
|
||
setTimeout(() => {
|
||
bindDrawerScrollEvent();
|
||
}, 100);
|
||
return;
|
||
}
|
||
|
||
scrollContainer.removeEventListener('scroll', handleDrawerScroll);
|
||
scrollContainer.addEventListener('scroll', handleDrawerScroll);
|
||
|
||
setTimeout(() => {
|
||
handleDrawerScroll();
|
||
}, 200);
|
||
};
|
||
|
||
const debounce = (func: Function, delay = 100) => {
|
||
let timeoutId: any;
|
||
return (...args: any[]) => {
|
||
clearTimeout(timeoutId);
|
||
timeoutId = setTimeout(() => func.apply(this, args), delay);
|
||
};
|
||
};
|
||
|
||
const handleDrawerScroll = debounce(() => {
|
||
const scrollContainer = drawerScrollRef.value;
|
||
if (!scrollContainer) return;
|
||
if (drawerPagination.loading || !drawerPagination.hasMore) return;
|
||
|
||
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
|
||
const isBottom = scrollTop + clientHeight + 50 >= scrollHeight;
|
||
|
||
if (isBottom) {
|
||
console.log('✅ 触底加载触发');
|
||
drawerPagination.current += 1;
|
||
loadDrawerData();
|
||
}
|
||
}, 100);
|
||
|
||
const loadDrawerData = () => {
|
||
if (drawerPagination.loading || !drawerPagination.hasMore) {
|
||
console.log('🚫 阻止重复加载');
|
||
return;
|
||
}
|
||
|
||
drawerPagination.loading = true;
|
||
useApiStore()
|
||
.CatePageList({
|
||
cookieId: cookieId.value,
|
||
cateType: cateType.value,
|
||
pageIndex: drawerPagination.current,
|
||
pageSize: drawerPagination.pageSize,
|
||
})
|
||
.then((res) => {
|
||
if (res.code === 0) {
|
||
const newData = res.data.data || [];
|
||
drawerDataList.value = [...drawerDataList.value, ...newData];
|
||
|
||
drawerPagination.total = res.data.total || 0;
|
||
drawerPagination.hasMore = drawerDataList.value.length < drawerPagination.total;
|
||
|
||
setTimeout(() => {
|
||
handleDrawerScroll();
|
||
}, 100);
|
||
} else {
|
||
message.error(res.message || '加载失败');
|
||
}
|
||
})
|
||
.catch(() => {
|
||
message.error('网络异常,加载失败');
|
||
})
|
||
.finally(() => {
|
||
drawerPagination.loading = false;
|
||
});
|
||
};
|
||
|
||
const getDrawerTypeName = () => {
|
||
switch (drawerType.value) {
|
||
case 'collect':
|
||
return '收藏夹';
|
||
case 'mix':
|
||
return '合集';
|
||
case 'series':
|
||
return '短剧';
|
||
default:
|
||
return '';
|
||
}
|
||
};
|
||
|
||
const toggleDrawerItemSync = (item: DrawerItem, index: number) => {
|
||
if (drawerDataList.value[index]) {
|
||
drawerDataList.value[index].sync = !item.sync;
|
||
console.log(`切换${getDrawerTypeName()}【${item.name}】的同步状态为:${!item.sync}`);
|
||
}
|
||
};
|
||
|
||
const closeDrawer = () => {
|
||
showDrawer.value = false;
|
||
const scrollContainer = drawerScrollRef.value;
|
||
if (scrollContainer) {
|
||
scrollContainer.removeEventListener('scroll', handleDrawerScroll);
|
||
}
|
||
};
|
||
|
||
const saveDrawerData = () => {
|
||
if (drawerDataList.value.length === 0) {
|
||
message.info('暂无需要保存的配置数据');
|
||
return;
|
||
}
|
||
drawerPagination.loading = true;
|
||
useApiStore()
|
||
.BatchSaveCate(drawerDataList.value)
|
||
.then((res) => {
|
||
if (res.code === 0) {
|
||
showDrawer.value = false;
|
||
message.success('保存成功');
|
||
} else {
|
||
message.error(res.message);
|
||
}
|
||
})
|
||
.finally(() => {
|
||
drawerPagination.loading = false;
|
||
});
|
||
};
|
||
|
||
const switchdownCollect = (e: any) => {
|
||
if (!e) form.useCollectFolder = e;
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<a-modal :title="form._isNew ? '新增授权' : '编辑授权'" v-model:visible="showModal" @ok="submit" @cancel="cancel" width="100%" wrap-class-name="full-modal">
|
||
<a-form ref="formModel" :model="form" :labelCol="isMobile ? { span: 24 } : { span: 3 }" :wrapperCol="isMobile ? { span: 24 } : { span: 20 }">
|
||
<a-form-item label="Cookie名称" required name="userName">
|
||
<a-input v-model:value="form.userName" />
|
||
</a-form-item>
|
||
<a-form-item label="id" required name="id" v-show="false">
|
||
<a-input v-model:value="form.id" />
|
||
</a-form-item>
|
||
<a-form-item label="Cookie值" name="cookies">
|
||
<a-textarea v-model:value="form.cookies" :rows="rowCount" />
|
||
</a-form-item>
|
||
|
||
<a-form-item label="我的secUserId" name="secUserId">
|
||
<div style="display: flex; align-items: center; gap: 6px;">
|
||
<a-input v-model:value="form.secUserId" style="flex: 1;" placeholder="" />
|
||
<a-tooltip title="如果要同步“我喜欢”的视频和关注列表时,必填!!!">
|
||
<ExclamationCircleOutlined style="color: #faad14;font-size: 16px;" />
|
||
</a-tooltip>
|
||
</div>
|
||
</a-form-item>
|
||
|
||
<a-form-item label="下载收藏视频" name="downCollect">
|
||
<div class="sync-option-row">
|
||
<div class="form-item-div">
|
||
<a-switch v-model:checked="form.downCollect" @change="switchdownCollect" :checked-value="true" :un-checked-value="false" size="default" />
|
||
<a-form-item-rest v-if="form.downCollect">
|
||
<a-form-item :name="isRemoteStorage ? 'webDavCollectPath' : 'savePath'" noStyle>
|
||
<a-input v-if="isRemoteStorage" v-model:value="form.webDavCollectPath" placeholder="OpenList 相对路径,留空自动生成" class="form-item-div-input" />
|
||
<a-input v-else v-model:value="form.savePath" placeholder="请输入容器路径" class="form-item-div-input" />
|
||
</a-form-item>
|
||
</a-form-item-rest>
|
||
</div>
|
||
<a-alert :message="isRemoteStorage ? '开启后写入目标基础目录下的这个 OpenList 相对路径;留空保存时会自动生成。' : '开启后自动下载默认收藏夹视频,记得填写映射路径(容器内部路径)。'" type="info" size="small" class="path-alert" />
|
||
</div>
|
||
</a-form-item>
|
||
|
||
<a-form-item v-if="isRemoteStorage || (form.savePath && form.savePath.length>0)" label="自定义收藏夹" name="useCollectFolder">
|
||
<div class="sync-option-row">
|
||
<div class="form-item-div">
|
||
<a-switch v-model:checked="form.useCollectFolder" :checked-value="true" :un-checked-value="false" size="default" />
|
||
<a-form-item-rest v-if="form.useCollectFolder">
|
||
<a-input v-if="isRemoteStorage" v-model:value="form.webDavCollectPath" :disabled="form.useCollectFolder&&form.downCollect" placeholder="与收藏路径一致" class="form-item-div-input" />
|
||
<a-input v-else v-model:value="form.savePath" :disabled="form.useCollectFolder&&form.downCollect" placeholder="" class="form-item-div-input" />
|
||
<a-button @click="openCollectFolderSetModal" shape="circle" type="dashed" style="margin-left:5px;" v-if="form.useCollectFolder">
|
||
<star-outlined />
|
||
</a-button>
|
||
</a-form-item-rest>
|
||
</div>
|
||
<a-alert message="开启后按收藏夹分类同步,存储根路径与默认收藏路径一致。" :type="form.useCollectFolder?'error':'info'" size="small" class="path-alert" />
|
||
</div>
|
||
</a-form-item>
|
||
|
||
<a-form-item label="下载喜欢视频" name="downFavorite">
|
||
<div class="sync-option-row">
|
||
<div class="form-item-div">
|
||
<a-switch v-model:checked="form.downFavorite" :checked-value="true" :un-checked-value="false" size="default" />
|
||
<a-form-item-rest v-if="form.downFavorite">
|
||
<a-form-item :name="isRemoteStorage ? 'webDavFavoritePath' : 'favSavePath'" noStyle>
|
||
<a-input v-if="isRemoteStorage" v-model:value="form.webDavFavoritePath" placeholder="OpenList 相对路径,留空自动生成" class="form-item-div-input" />
|
||
<a-input v-else v-model:value="form.favSavePath" placeholder="请输入容器路径" class="form-item-div-input" />
|
||
</a-form-item>
|
||
<a-button shape="circle" @click="()=>{message.success('别点了,这只是为了好看的😄')}" type="dashed" style="margin-left:5px;">
|
||
<like-outlined />
|
||
</a-button>
|
||
</a-form-item-rest>
|
||
</div>
|
||
<a-alert :message="isRemoteStorage ? '喜欢的视频会写入目标基础目录下的这个 OpenList 相对路径。' : '开启后自动下载喜欢(点赞)的视频,记得填写映射路径(容器内部路径)。'" type="info" size="small" class="path-alert" />
|
||
</div>
|
||
</a-form-item>
|
||
|
||
<a-form-item label="下载关注视频" name="downFollowd">
|
||
<div class="sync-option-row">
|
||
<div class="form-item-div">
|
||
<a-switch v-model:checked="form.downFollowd" :checked-value="true" :un-checked-value="false" size="default" />
|
||
<a-form-item-rest v-if="form.downFollowd">
|
||
<a-form-item :name="isRemoteStorage ? 'webDavFollowPath' : 'upSavePath'" noStyle>
|
||
<a-input v-if="isRemoteStorage" v-model:value="form.webDavFollowPath" placeholder="OpenList 相对路径,留空自动生成" class="form-item-div-input" />
|
||
<a-input v-else v-model:value="form.upSavePath" placeholder="请输入容器路径" class="form-item-div-input" />
|
||
</a-form-item>
|
||
<a-button shape="circle" @click="()=>{message.success('别点了,这只是为了好看的😄')}" type="dashed" style="margin-left:5px;">
|
||
<heart-outlined />
|
||
</a-button>
|
||
</a-form-item-rest>
|
||
</div>
|
||
<a-alert :message="isRemoteStorage ? '关注博主的视频会写入目标基础目录下的这个 OpenList 相对路径。' : '开启后自动下载关注的博主视频,记得填写映射路径(容器内部路径)。'" type="info" size="small" class="path-alert" />
|
||
</div>
|
||
</a-form-item>
|
||
|
||
<a-form-item label="下载合集视频" name="downMix">
|
||
<div class="sync-option-row">
|
||
<div class="form-item-div">
|
||
<a-switch v-model:checked="form.downMix" :checked-value="true" :un-checked-value="false" size="default" />
|
||
<a-form-item-rest v-if="form.downMix">
|
||
<a-form-item :name="isRemoteStorage ? 'webDavMixPath' : 'mixPath'" noStyle>
|
||
<a-input v-if="isRemoteStorage" v-model:value="form.webDavMixPath" class="form-item-div-input" placeholder="OpenList 相对路径,留空自动生成" />
|
||
<a-input v-else v-model:value="form.mixPath" class="form-item-div-input" placeholder="默认使用收藏夹路径" />
|
||
</a-form-item>
|
||
<a-button @click="openMixDownSetModal" shape="circle" type="dashed" style="margin-left:5px;">
|
||
<gift-outlined />
|
||
</a-button>
|
||
</a-form-item-rest>
|
||
</div>
|
||
<a-alert :message="isRemoteStorage ? '收藏的合集会同步到该 OpenList 相对路径;付费视频下载后可能无法播放。' : '开启后自动下载收藏的合集视频;不填路径默认使用收藏目录,付费视频下载后可能无法播放。'" :type="form.downMix?'error':'info'" size="small" class="path-alert" />
|
||
</div>
|
||
</a-form-item>
|
||
|
||
<a-form-item label="下载短剧视频" name="downSeries">
|
||
<div class="sync-option-row">
|
||
<div class="form-item-div">
|
||
<a-switch v-model:checked="form.downSeries" :checked-value="true" :un-checked-value="false" size="default" />
|
||
<a-form-item-rest v-if="form.downSeries">
|
||
<a-form-item :name="isRemoteStorage ? 'webDavSeriesPath' : 'seriesPath'" noStyle>
|
||
<a-input v-if="isRemoteStorage" v-model:value="form.webDavSeriesPath" placeholder="OpenList 相对路径,留空自动生成" class="form-item-div-input" />
|
||
<a-input v-else v-model:value="form.seriesPath" placeholder="默认使用收藏夹路径" class="form-item-div-input" />
|
||
</a-form-item>
|
||
<a-button @click="openSeriesDownSetModal" shape="circle" type="dashed" style="margin-left:5px;">
|
||
<fire-outlined />
|
||
</a-button>
|
||
</a-form-item-rest>
|
||
</div>
|
||
<a-alert :message="isRemoteStorage ? '收藏的短剧会同步到该 OpenList 相对路径;付费视频下载后可能无法播放。' : '开启后自动下载收藏的短剧视频;不填路径默认使用收藏目录,付费视频下载后可能无法播放。'" :type="form.downSeries?'error':'info'" size="small" class="path-alert" />
|
||
</div>
|
||
</a-form-item>
|
||
|
||
<a-form-item label="任务同步状态" name="status">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<a-switch v-model:checked="form.status" :checked-value="1" :un-checked-value="0" size="default" />
|
||
<span>{{ form.status === 1 ? '' : '' }}</span>
|
||
</div>
|
||
</a-form-item>
|
||
</a-form>
|
||
</a-modal>
|
||
|
||
<a-drawer :title="getDrawerTypeName()+'配置'" v-model:visible="showDrawer" placement="right" :width="drawerWidth" :z-index="10010" :mask-z-index="10009" @close="closeDrawer" class="common-drawer">
|
||
<template #extra>
|
||
<a-button type="primary" :loading="drawerPagination.loading" @click="saveDrawerData" class="drawer-save-btn">
|
||
<template #icon>
|
||
<SaveOutlined />
|
||
</template>
|
||
保存
|
||
</a-button>
|
||
</template>
|
||
|
||
<div ref="drawerScrollRef" class="drawer-scroll-container">
|
||
<div v-if="drawerPagination.loading && drawerDataList.length === 0" class="drawer-loading">
|
||
<Spin size="large" />
|
||
</div>
|
||
|
||
<a-card v-else :bordered="false" class="drawer-card-container grid-container">
|
||
<a-card-grid v-for="(item, index) in drawerDataList" :key="item.id" class="drawer-card-grid">
|
||
<div class="grid-cover vertical-cover" v-if="drawerType!='collect'">
|
||
<a-image :preview="false" :src="item.coverUrl" fit="cover" />
|
||
</div>
|
||
|
||
<div class="grid-item horizontal-item name">
|
||
<label class="drawer-label">名称:</label>
|
||
<span>{{ item.name || '未命名' }}</span>
|
||
</div>
|
||
|
||
<div class="grid-item horizontal-item save-folder">
|
||
<label class="drawer-label">保存:</label>
|
||
<a-input v-model:value="item.saveFolder" size="small" placeholder="默认用名称作文件夹" class="save-path-input" />
|
||
</div>
|
||
<div class="grid-item horizontal-item name">
|
||
<label class="drawer-label">集数:</label>
|
||
<span>{{ item.total || '0' }}</span>
|
||
</div>
|
||
|
||
<div class="grid-item horizontal-item sync-switch">
|
||
<label class="drawer-label">同步:</label>
|
||
<a-switch :checked="item.sync" @change="() => toggleDrawerItemSync(item, index)" size="small" />
|
||
</div>
|
||
</a-card-grid>
|
||
</a-card>
|
||
|
||
<div v-if="!drawerPagination.hasMore && drawerDataList.length > 0" class="no-more-data">
|
||
</div>
|
||
|
||
<div v-if="drawerPagination.loading && drawerDataList.length > 0" class="loading-more">
|
||
<Spin size="small" />
|
||
<span>加载中...</span>
|
||
</div>
|
||
</div>
|
||
</a-drawer>
|
||
|
||
<div class="cookie-toolbar">
|
||
<a-button @click="GetRecords()" :loading="loading">
|
||
<template #icon><SearchOutlined /></template>
|
||
刷新
|
||
</a-button>
|
||
<a-button type="primary" @click="addNew" :loading="formLoading">
|
||
<template #icon><PlusOutlined /></template>
|
||
新增授权
|
||
</a-button>
|
||
</div>
|
||
|
||
<a-table v-if="!isMobile" v-bind="$attrs" :columns="columns" :dataSource="dataSource" :pagination="false" :scroll="{ x: isRemoteStorage ? 1400 : 1200 }">
|
||
<template #bodyCell="{ column, text, record }">
|
||
<template v-if="column.dataIndex === 'statusMsg'">
|
||
<a-tooltip v-if="record.sourceRequiresAuthorization" :title="record.lastSourceError || '请编辑并保存新的 Cookie'">
|
||
<a-tag color="error">需要重新授权</a-tag>
|
||
</a-tooltip>
|
||
<a-tooltip v-else-if="record.sourceCooldownUntil || record.sourceProbePending" :title="record.lastSourceError || '等待自动探测'">
|
||
<a-tag color="warning">{{ record.sourceCooldownUntil ? `冷却至 ${dayjs(record.sourceCooldownUntil).format('HH:mm:ss')}` : '等待来源探测' }}</a-tag>
|
||
</a-tooltip>
|
||
<a-tag v-else :color="record.statusCode === 0 ? 'success' : 'default'">{{ record.statusMsg || '未知' }}</a-tag>
|
||
</template>
|
||
<template v-else-if="column.dataIndex === 'status'">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<a-switch v-model:checked="record.status" :checked-value="1" :un-checked-value="0" size="small" :disabled="loading" @change="() => switchSyncStatus(record)" />
|
||
<span :style="{
|
||
fontSize: '12px',
|
||
color: record.status === 1 ? '#52c41a' : '#ff4d4f'
|
||
}">
|
||
{{ StatusDict[record.status] }}
|
||
</span>
|
||
</div>
|
||
</template>
|
||
<template v-else-if="column.dataIndex === 'edit'">
|
||
<a-button :disabled="showModal || loading" type="link" @click="edit(record)">
|
||
<template #icon>
|
||
<EditFilled />
|
||
</template>
|
||
编辑
|
||
</a-button>
|
||
|
||
<a-button :disabled="loading" type="link" @click="deleted(record.id)" danger>
|
||
<template #icon>
|
||
<DeleteOutlined />
|
||
</template>
|
||
删除
|
||
</a-button>
|
||
</template>
|
||
<div v-else class="text-subtext">
|
||
{{ text }}
|
||
</div>
|
||
</template>
|
||
</a-table>
|
||
|
||
<a-spin v-else :spinning="loading">
|
||
<a-empty v-if="!loading && dataSource.length === 0" description="暂无抖音授权" />
|
||
<div class="mobile-cookie-list">
|
||
<article v-for="record in dataSource" :key="record.id" class="mobile-cookie-card">
|
||
<div class="mobile-cookie-header">
|
||
<div>
|
||
<h3>{{ record.userName || '未命名授权' }}</h3>
|
||
<span :class="record.status === 1 ? 'status-on' : 'status-off'">{{ record.statusMsg || StatusDict[record.status || 0] }}</span>
|
||
<a-tag v-if="record.sourceRequiresAuthorization" color="error">需要重新授权</a-tag>
|
||
<a-tag v-else-if="record.sourceCooldownUntil || record.sourceProbePending" color="warning">
|
||
{{ record.sourceCooldownUntil ? `冷却至 ${dayjs(record.sourceCooldownUntil).format('HH:mm:ss')}` : '等待来源探测' }}
|
||
</a-tag>
|
||
</div>
|
||
<a-switch v-model:checked="record.status" :checked-value="1" :un-checked-value="0" size="small" :disabled="loading" @change="() => switchSyncStatus(record)" />
|
||
</div>
|
||
<dl class="mobile-cookie-paths">
|
||
<template v-if="isRemoteStorage">
|
||
<div><dt>收藏</dt><dd>{{ record.webDavCollectPath || '未设置' }}</dd></div>
|
||
<div><dt>喜欢</dt><dd>{{ record.webDavFavoritePath || '未设置' }}</dd></div>
|
||
<div><dt>关注</dt><dd>{{ record.webDavFollowPath || '未设置' }}</dd></div>
|
||
<div><dt>合集</dt><dd>{{ record.webDavMixPath || '未设置' }}</dd></div>
|
||
<div><dt>短剧</dt><dd>{{ record.webDavSeriesPath || '未设置' }}</dd></div>
|
||
</template>
|
||
<template v-else>
|
||
<div><dt>收藏</dt><dd>{{ record.savePath || '未设置' }}</dd></div>
|
||
<div><dt>喜欢</dt><dd>{{ record.favSavePath || '未设置' }}</dd></div>
|
||
<div><dt>关注</dt><dd>{{ record.upSavePath || '未设置' }}</dd></div>
|
||
</template>
|
||
</dl>
|
||
<div class="mobile-cookie-actions">
|
||
<a-button :disabled="showModal || loading" @click="edit(record)"><EditFilled />编辑</a-button>
|
||
<a-button :disabled="loading || !record.id" danger @click="record.id && deleted(record.id)"><DeleteOutlined />删除</a-button>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
</a-spin>
|
||
|
||
<a-pagination
|
||
v-if="pagination.total > 0"
|
||
class="cookie-pagination"
|
||
:simple="isMobile"
|
||
:current="pagination.current"
|
||
:page-size="pagination.defaultPageSize"
|
||
:total="pagination.total"
|
||
:show-size-changer="false"
|
||
@change="handlePageChange"
|
||
/>
|
||
</template>
|
||
|
||
<style scoped lang="less">
|
||
.ant-form-item {
|
||
margin-bottom: 10px;
|
||
}
|
||
.form-item-div {
|
||
width: 430px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.form-item-div-input {
|
||
width: 310px;
|
||
margin-left: 10px;
|
||
}
|
||
.sync-option-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
.path-alert {
|
||
flex: 1;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.cookie-toolbar {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
padding: 8px 12px 12px;
|
||
}
|
||
|
||
.cookie-pagination {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin: 16px 12px 4px;
|
||
}
|
||
|
||
.mobile-cookie-list {
|
||
display: grid;
|
||
gap: 12px;
|
||
padding: 0 8px;
|
||
}
|
||
|
||
.mobile-cookie-card {
|
||
padding: 14px;
|
||
border: 1px solid #e8e8e8;
|
||
border-radius: 12px;
|
||
background: #fff;
|
||
box-shadow: 0 3px 14px rgba(15, 23, 42, 0.06);
|
||
}
|
||
|
||
.mobile-cookie-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
|
||
.mobile-cookie-header h3 {
|
||
margin: 0 0 3px;
|
||
color: #1f2937;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.mobile-cookie-header .status-on {
|
||
color: #52c41a;
|
||
}
|
||
|
||
.mobile-cookie-header .status-off {
|
||
color: #ff4d4f;
|
||
}
|
||
|
||
.mobile-cookie-paths {
|
||
margin: 12px 0;
|
||
}
|
||
|
||
.mobile-cookie-paths > div {
|
||
display: grid;
|
||
grid-template-columns: 44px minmax(0, 1fr);
|
||
gap: 8px;
|
||
padding: 3px 0;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.mobile-cookie-paths dt {
|
||
color: #8c8c8c;
|
||
}
|
||
|
||
.mobile-cookie-paths dd {
|
||
min-width: 0;
|
||
margin: 0;
|
||
overflow-wrap: anywhere;
|
||
color: #334155;
|
||
}
|
||
|
||
.mobile-cookie-actions {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 8px;
|
||
}
|
||
|
||
:deep(.ant-input-textarea-input) {
|
||
overflow-y: auto;
|
||
scrollbar-width: thin;
|
||
scrollbar-color: rgba(150, 150, 150, 0.2) transparent;
|
||
}
|
||
:deep(.ant-input-textarea-input)::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
:deep(.ant-input-textarea-input)::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
:deep(.ant-input-textarea-input)::-webkit-scrollbar-thumb {
|
||
background: rgba(150, 150, 150, 0.2);
|
||
border-radius: 3px;
|
||
}
|
||
:deep(.ant-input-textarea-input)::-webkit-scrollbar-thumb:hover {
|
||
background: rgba(150, 150, 150, 0.4);
|
||
}
|
||
:deep(.ant-input-textarea-input)::-webkit-scrollbar-corner {
|
||
background: transparent;
|
||
}
|
||
|
||
:deep(.ant-input-disabled) {
|
||
background-color: #f5f5f5 !important;
|
||
color: #666 !important;
|
||
}
|
||
|
||
.alert-wrapper {
|
||
flex: 1;
|
||
margin-bottom: 0 !important;
|
||
}
|
||
|
||
.drawer-card-container.grid-container {
|
||
:deep(.ant-card-body) {
|
||
padding: 16px;
|
||
margin: 0;
|
||
}
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 20px;
|
||
box-sizing: border-box;
|
||
justify-content: flex-start;
|
||
min-height: 200px;
|
||
padding-left: 5px;
|
||
}
|
||
|
||
.drawer-card-container.grid-container:has(:only-child) :deep(.drawer-card-grid) {
|
||
width: calc(33.333% - 10.333px) !important;
|
||
min-width: 200px;
|
||
}
|
||
|
||
:deep(.drawer-card-grid) {
|
||
width: calc(33.333% - 10.333px) !important;
|
||
min-width: 200px;
|
||
margin: 5px !important;
|
||
border-radius: 12px !important;
|
||
padding: 16px !important;
|
||
box-sizing: border-box;
|
||
border: 1px solid #f0f0f0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
:deep(.drawer-card-grid .grid-cover.vertical-cover) {
|
||
width: 100% !important;
|
||
padding: 4px;
|
||
box-sizing: border-box;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
:deep(.drawer-card-grid .vertical-cover .ant-image) {
|
||
width: 100% !important;
|
||
height: auto !important;
|
||
display: block;
|
||
}
|
||
:deep(.drawer-card-grid .vertical-cover .ant-image-img) {
|
||
width: 100% !important;
|
||
height: 100% !important;
|
||
object-fit: cover !important;
|
||
}
|
||
|
||
:deep(.drawer-card-grid .grid-item.horizontal-item) {
|
||
width: 100% !important;
|
||
display: flex;
|
||
align-items: center;
|
||
margin: 0 !important;
|
||
padding: 4px 0;
|
||
}
|
||
|
||
:deep(.drawer-label) {
|
||
flex-shrink: 0;
|
||
font-size: 12px;
|
||
color: rgba(0, 0, 0, 0.6);
|
||
}
|
||
|
||
:deep(.drawer-card-grid .horizontal-item span) {
|
||
flex: 1;
|
||
font-size: 12px;
|
||
color: rgba(0, 0, 0, 0.88);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
:deep(.drawer-card-grid .horizontal-item .save-path-input) {
|
||
flex: 1;
|
||
width: 100% !important;
|
||
font-size: 12px;
|
||
}
|
||
|
||
:deep(.drawer-card-grid .sync-switch) {
|
||
justify-content: space-between;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.cookie-toolbar {
|
||
position: sticky;
|
||
z-index: 20;
|
||
top: 0;
|
||
padding: 10px;
|
||
margin-bottom: 10px;
|
||
border: 1px solid var(--mobile-border, #e5e7eb);
|
||
border-radius: 16px;
|
||
background: var(--mobile-card, #fff);
|
||
box-shadow: 0 6px 20px rgba(15, 23, 42, 0.05);
|
||
}
|
||
|
||
.cookie-toolbar .ant-btn {
|
||
flex: 1;
|
||
}
|
||
|
||
.cookie-pagination {
|
||
justify-content: center;
|
||
}
|
||
|
||
.sync-option-row {
|
||
align-items: stretch;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.form-item-div {
|
||
width: 100%;
|
||
}
|
||
|
||
.form-item-div-input {
|
||
width: 100%;
|
||
max-width: none;
|
||
}
|
||
|
||
.mobile-cookie-list { padding: 0; }
|
||
.mobile-cookie-card {
|
||
border-color: var(--mobile-border, #e8e8e8);
|
||
border-radius: 16px;
|
||
background: var(--mobile-card, #fff);
|
||
}
|
||
|
||
.mobile-cookie-header h3 { color: var(--mobile-text, #1f2937); }
|
||
.mobile-cookie-paths dd { color: var(--mobile-text, #334155); }
|
||
|
||
:deep(.ant-modal-footer) {
|
||
position: sticky;
|
||
z-index: 5;
|
||
bottom: 0;
|
||
padding-bottom: max(10px, env(safe-area-inset-bottom));
|
||
background: var(--mobile-card, #fff);
|
||
}
|
||
|
||
:deep(.ant-form-item-label) {
|
||
padding-bottom: 4px;
|
||
text-align: left;
|
||
}
|
||
|
||
:deep(.ant-form-item-label > label) {
|
||
height: auto;
|
||
}
|
||
|
||
:deep(.drawer-card-grid) {
|
||
width: calc(50% - 10px) !important;
|
||
min-width: 180px;
|
||
}
|
||
|
||
.drawer-card-container.grid-container:has(:only-child) :deep(.drawer-card-grid) {
|
||
width: calc(50% - 10px) !important;
|
||
min-width: 180px;
|
||
}
|
||
}
|
||
@media (max-width: 480px) {
|
||
:deep(.drawer-card-grid) {
|
||
width: 100% !important;
|
||
min-width: 100%;
|
||
}
|
||
|
||
.drawer-card-container.grid-container:has(:only-child) :deep(.drawer-card-grid) {
|
||
width: 100% !important;
|
||
min-width: 100%;
|
||
}
|
||
}
|
||
|
||
html.dark-mode .drawer-card-container.grid-container .drawer-card-grid {
|
||
border-color: rgba(142, 140, 140, 0.1) !important;
|
||
background-color: #1a1a2e !important;
|
||
box-shadow: 0 6px 16px rgba(0, 20, 60, 0.4), 0 2px 6px rgba(100, 120, 255, 0.2),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.05) !important;
|
||
transition: all 0.3s ease-in-out !important;
|
||
}
|
||
|
||
html.dark-mode .drawer-card-container.grid-container .drawer-card-grid:hover {
|
||
box-shadow: 0 8px 24px rgba(0, 30, 80, 0.5), 0 4px 12px rgba(120, 140, 255, 0.35),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.08) !important;
|
||
transform: translateY(-2px) !important;
|
||
}
|
||
|
||
html.dark-mode .drawer-card-container.grid-container .drawer-card-grid .horizontal-item label {
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
html.dark-mode .drawer-card-container.grid-container .drawer-card-grid .horizontal-item span {
|
||
color: rgba(255, 255, 255, 0.88) !important;
|
||
}
|
||
|
||
html.dark-mode .drawer-card-container.grid-container .drawer-card-grid .ant-input {
|
||
background-color: #2f2f2f !important;
|
||
border-color: #404040 !important;
|
||
color: rgba(255, 255, 255, 0.88) !important;
|
||
}
|
||
</style>
|
||
|
||
<style lang="less">
|
||
.full-modal {
|
||
.ant-modal {
|
||
max-width: 100%;
|
||
top: 0;
|
||
padding-bottom: 0;
|
||
margin: 0;
|
||
}
|
||
.ant-modal-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: calc(100vh);
|
||
}
|
||
.ant-modal-body {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 16px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.full-modal {
|
||
.ant-modal-header,
|
||
.ant-modal-footer {
|
||
padding-left: 16px;
|
||
padding-right: 16px;
|
||
}
|
||
|
||
.ant-modal-content {
|
||
height: 100dvh;
|
||
}
|
||
}
|
||
}
|
||
|
||
.ant-alert {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
color: rgba(0, 0, 0, 0.88);
|
||
font-size: 14px;
|
||
line-height: 1.5714285714;
|
||
list-style: none;
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
border-radius: 8px;
|
||
}
|
||
.ant-alert-small {
|
||
padding: 8px 16px;
|
||
font-size: 12px;
|
||
border-radius: 4px;
|
||
}
|
||
.ant-alert-info {
|
||
background-color: #e6f4ff;
|
||
border: 1px solid #91caff;
|
||
}
|
||
.ant-alert-info .ant-alert-message {
|
||
color: #1677ff;
|
||
}
|
||
.ant-alert-success {
|
||
background-color: #daf1d3;
|
||
border: 1px solid #5bbc51;
|
||
}
|
||
.ant-alert-success .ant-alert-message {
|
||
color: #228b22;
|
||
}
|
||
|
||
.ant-alert-error {
|
||
background-color: #fff1f0;
|
||
border: 1px solid #ff4d4f;
|
||
}
|
||
.ant-alert-error .ant-alert-message {
|
||
color: #f5222d;
|
||
}
|
||
|
||
.ant-alert-warning {
|
||
background-color: #fffbe6;
|
||
border: 1px solid #ffe58f;
|
||
}
|
||
.ant-alert-warning .ant-alert-message {
|
||
color: #faad14;
|
||
}
|
||
.ant-alert-info .ant-alert-icon {
|
||
color: #1677ff;
|
||
}
|
||
.ant-alert-warning .ant-alert-icon {
|
||
color: #faad14;
|
||
}
|
||
|
||
.drawer-scroll-container {
|
||
width: 100%;
|
||
height: calc(100vh - 130px) !important;
|
||
min-height: 300px !important;
|
||
overflow-y: auto !important;
|
||
overflow-x: hidden !important;
|
||
padding: 0 8px;
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
z-index: 1;
|
||
|
||
&::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
&::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
&::-webkit-scrollbar-thumb {
|
||
background: rgba(0, 0, 0, 0.15);
|
||
border-radius: 3px;
|
||
}
|
||
html.dark-mode &::-webkit-scrollbar-thumb {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
}
|
||
}
|
||
|
||
.drawer-loading {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 200px;
|
||
width: 100%;
|
||
}
|
||
|
||
.loading-more {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding: 16px 0;
|
||
font-size: 12px;
|
||
color: rgba(0, 0, 0, 0.6);
|
||
|
||
html.dark-mode & {
|
||
color: rgba(255, 255, 255, 0.6);
|
||
}
|
||
}
|
||
|
||
.no-more-data {
|
||
text-align: center;
|
||
padding: 16px 0;
|
||
font-size: 12px;
|
||
color: rgba(0, 0, 0, 0.45);
|
||
|
||
html.dark-mode & {
|
||
color: rgba(255, 255, 255, 0.45);
|
||
}
|
||
}
|
||
</style>
|