结构调整
This commit is contained in:
@@ -44,7 +44,7 @@ onMounted(() => {
|
||||
</div> -->
|
||||
|
||||
<!-- 新增:显示当前版本号,增加样式分隔提升美观度 -->
|
||||
<span class="version-text">{{currentVersion.deploy}}_{{ currentVersion.tag }}</span>
|
||||
<span class="version-text" v-if="currentVersion">{{currentVersion.deploy}}_{{ currentVersion.tag }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
// 原有代码不变,此处省略(仅保留模板相关修改,脚本逻辑无变化)
|
||||
import { getBase64 } from '@/utils/file';
|
||||
import { FormInstance } from 'ant-design-vue';
|
||||
import { reactive, ref, onMounted, UnwrapRef, watch } from 'vue';
|
||||
import { reactive, ref, onMounted, UnwrapRef, watch, nextTick } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { Dayjs } from 'dayjs';
|
||||
import {
|
||||
@@ -26,14 +25,14 @@ columns.value = [
|
||||
dataIndex: 'userName',
|
||||
width: 180,
|
||||
},
|
||||
{ title: 'Cookie状态', dataIndex: 'statusMsg' },
|
||||
{ title: '收藏路径', dataIndex: 'savePath' },
|
||||
{ title: '喜欢路径', dataIndex: 'favSavePath' },
|
||||
{ title: '博主路径', dataIndex: 'upSavePath' },
|
||||
{ title: '图文视频', dataIndex: 'imgSavePath' },
|
||||
{ title: 'Cookie', dataIndex: 'cookies' },
|
||||
{ title: '有效状态', dataIndex: 'statusMsg' },
|
||||
// { title: 'Cookie', dataIndex: 'cookies' },
|
||||
{ title: '状态', dataIndex: 'status', width: 180 },
|
||||
{ title: '操作', dataIndex: 'edit', width: 350 }, // 加宽操作列宽度
|
||||
{ title: '操作', dataIndex: 'edit', width: 200 }, // 加宽操作列宽度
|
||||
];
|
||||
|
||||
// 定义数组中单个对象的类型:包含uper和uid字段(可选字符串类型,根据实际需求调整是否可选)
|
||||
@@ -57,6 +56,9 @@ type DataItem = {
|
||||
upSavePath?: string;
|
||||
imgSavePath?: string;
|
||||
useSinglePath?: boolean; // 新增:是否全部用一个地址
|
||||
useCollectFolder?: boolean;
|
||||
downMix?: boolean;
|
||||
downSeries?: boolean;
|
||||
};
|
||||
|
||||
const loading = ref(false);
|
||||
@@ -117,6 +119,9 @@ const newCookie = (cookie?: DataItem) => {
|
||||
cookie.upSavePath = undefined;
|
||||
cookie.imgSavePath = undefined;
|
||||
cookie.useSinglePath = false; // 新增:默认不使用单一路径
|
||||
cookie.useCollectFolder = false; //是否按收藏夹来下载。
|
||||
cookie.downMix = false; //是否下载收藏夹的合集
|
||||
cookie.downSeries = false; //是否下载短剧
|
||||
return cookie;
|
||||
};
|
||||
|
||||
@@ -216,6 +221,7 @@ const deleted = (id: string) => {
|
||||
|
||||
function edit(record: DataItem) {
|
||||
editRecord.value = record;
|
||||
console.log(record);
|
||||
copyObject(form, record);
|
||||
// 确保useSinglePath有默认值
|
||||
if (form.useSinglePath === undefined) {
|
||||
@@ -225,9 +231,10 @@ function edit(record: DataItem) {
|
||||
}
|
||||
|
||||
// 新增:切换同步状态方法
|
||||
|
||||
const switchSyncStatus = (record: DataItem) => {
|
||||
const targetStatus = record.status === 1 ? 0 : 1;
|
||||
const statusText = targetStatus === 1 ? '开启' : '停止';
|
||||
// 保存原始状态,用于弹窗取消后还原
|
||||
const statusText = record.status === 1 ? '开启' : '停止';
|
||||
const title = `确认${statusText}同步`;
|
||||
const content = `确定要${statusText}【${record.userName || '该'}】Cookie的同步任务吗?`;
|
||||
|
||||
@@ -241,33 +248,33 @@ const switchSyncStatus = (record: DataItem) => {
|
||||
useApiStore()
|
||||
.SwitchCookieStatus({
|
||||
id: record.id,
|
||||
status: targetStatus,
|
||||
status: record.status,
|
||||
})
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.code === 0) {
|
||||
message.success(`${statusText}同步成功`);
|
||||
GetRecords(); // 刷新列表
|
||||
GetRecords(); // 刷新列表,更新 switch 状态
|
||||
} 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;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
type Status = 0 | 1;
|
||||
|
||||
const StatusDict = {
|
||||
0: '同步已关闭',
|
||||
0: '同步已停止',
|
||||
1: '同步已开启',
|
||||
};
|
||||
|
||||
@@ -297,17 +304,183 @@ const removeRow = (index: number) => {
|
||||
form.upSecUserIdsJson.splice(index, 1);
|
||||
}
|
||||
};
|
||||
const rowCount = 8;
|
||||
const rowCount = 5;
|
||||
|
||||
// 组件挂载时获取配置
|
||||
onMounted(() => {
|
||||
GetRecords();
|
||||
});
|
||||
const downImgVideo = ref(true);
|
||||
|
||||
// ========== 抽屉相关响应式数据 ==========
|
||||
// 抽屉显示状态
|
||||
const showDrawer = ref(false);
|
||||
// 抽屉类型(区分收藏夹/合集/短剧)
|
||||
type DrawerType = 'collect' | 'mix' | 'series';
|
||||
const drawerType = ref<DrawerType>('collect');
|
||||
// 抽屉滚动容器引用(用于监听触底分页)
|
||||
const drawerScrollRef = ref<HTMLDivElement | null>(null);
|
||||
// 抽屉数据列表
|
||||
const drawerDataList = ref<DrawerItem[]>([]);
|
||||
// 抽屉分页配置
|
||||
const drawerPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
loading: false,
|
||||
hasMore: true, // 是否还有更多数据
|
||||
});
|
||||
|
||||
// ========== 定义抽屉数据项接口 ==========
|
||||
interface DrawerItem {
|
||||
Id: string; // 不显示
|
||||
Name: string; // 名称
|
||||
SaveFolder: string; // 保存文件夹
|
||||
Sync: boolean; // 是否同步
|
||||
CoverUrl: string; // 封面
|
||||
CookieId: string; // 不显示
|
||||
XId: string; // 不显示
|
||||
}
|
||||
|
||||
// ========== 3个打开抽屉的方法 ==========
|
||||
const openCollectFolderSetModal = () => {
|
||||
drawerType.value = 'collect';
|
||||
openCommonDrawer();
|
||||
};
|
||||
|
||||
const openMixDownSetModal = () => {
|
||||
drawerType.value = 'mix';
|
||||
openCommonDrawer();
|
||||
};
|
||||
|
||||
const openSeriesDownSetModal = () => {
|
||||
drawerType.value = 'series';
|
||||
openCommonDrawer();
|
||||
};
|
||||
|
||||
// ========== 公用抽屉打开逻辑 ==========
|
||||
const openCommonDrawer = () => {
|
||||
// 重置抽屉状态
|
||||
drawerDataList.value = [];
|
||||
drawerPagination.current = 1;
|
||||
drawerPagination.total = 0;
|
||||
drawerPagination.hasMore = true;
|
||||
// 显示抽屉
|
||||
showDrawer.value = true;
|
||||
// 加载第一页数据
|
||||
loadDrawerData();
|
||||
// 监听滚动触底(延迟绑定,确保DOM已渲染)
|
||||
nextTick(() => {
|
||||
bindDrawerScrollEvent();
|
||||
});
|
||||
};
|
||||
|
||||
// ========== 绑定抽屉滚动触底事件 ==========
|
||||
const bindDrawerScrollEvent = () => {
|
||||
const scrollContainer = drawerScrollRef.value;
|
||||
if (!scrollContainer) return;
|
||||
|
||||
// 先移除原有事件,避免重复绑定
|
||||
scrollContainer.removeEventListener('scroll', handleDrawerScroll);
|
||||
// 添加滚动事件
|
||||
scrollContainer.addEventListener('scroll', handleDrawerScroll);
|
||||
};
|
||||
|
||||
// ========== 滚动触底处理逻辑 ==========
|
||||
const handleDrawerScroll = () => {
|
||||
const scrollContainer = drawerScrollRef.value;
|
||||
if (!scrollContainer || drawerPagination.loading || !drawerPagination.hasMore) return;
|
||||
|
||||
// 计算滚动位置(触底阈值20px,避免精准触底才触发)
|
||||
const { scrollTop, clientHeight, scrollHeight } = scrollContainer;
|
||||
const isBottom = scrollTop + clientHeight >= scrollHeight - 20;
|
||||
|
||||
if (isBottom) {
|
||||
// 加载下一页
|
||||
drawerPagination.current += 1;
|
||||
loadDrawerData();
|
||||
}
|
||||
};
|
||||
|
||||
// ========== 加载抽屉数据(模拟接口请求,可替换为真实接口) ==========
|
||||
const loadDrawerData = () => {
|
||||
if (drawerPagination.loading || !drawerPagination.hasMore) return;
|
||||
|
||||
drawerPagination.loading = true;
|
||||
|
||||
// 模拟接口请求(实际项目中替换为真实API调用)
|
||||
setTimeout(() => {
|
||||
// 模拟数据(可根据drawerType返回不同类型数据)
|
||||
const mockData: DrawerItem[] = Array.from({ length: drawerPagination.pageSize }, (_, index) => ({
|
||||
Id: `${drawerPagination.current}-${index}`,
|
||||
Name: `${getDrawerTypeName()} ${(drawerPagination.current - 1) * drawerPagination.pageSize + index + 1}`,
|
||||
SaveFolder: `./${drawerType.value}/${(drawerPagination.current - 1) * drawerPagination.pageSize + index + 1}`,
|
||||
Sync: Math.random() > 0.5,
|
||||
CoverUrl: `https://picsum.photos/120/180?random=${
|
||||
// 改为竖向图片尺寸 120x180
|
||||
(drawerPagination.current - 1) * drawerPagination.pageSize + index + 1
|
||||
}`,
|
||||
CookieId: form.id || '0',
|
||||
XId: `X-${drawerPagination.current}-${index}`,
|
||||
}));
|
||||
|
||||
// 拼接数据列表
|
||||
drawerDataList.value = [...drawerDataList.value, ...mockData];
|
||||
// 更新分页状态
|
||||
drawerPagination.total = 100; // 模拟总条数
|
||||
drawerPagination.loading = false;
|
||||
// 判断是否还有更多数据
|
||||
drawerPagination.hasMore = drawerDataList.value.length < drawerPagination.total;
|
||||
}, 800);
|
||||
};
|
||||
|
||||
// ========== 获取抽屉类型名称(用于页面展示) ==========
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
// 2. 新增:抽屉右上角保存按钮的点击方法(仅抽屉相关,不影响其他逻辑)
|
||||
const saveDrawerData = () => {
|
||||
if (drawerDataList.value.length === 0) {
|
||||
message.info('暂无需要保存的配置数据');
|
||||
return;
|
||||
}
|
||||
// 模拟保存逻辑(实际项目中可替换为真实接口,提交 drawerDataList.value 数据)
|
||||
drawerPagination.loading = true;
|
||||
setTimeout(() => {
|
||||
drawerPagination.loading = false;
|
||||
message.success(`${getDrawerTypeName()}配置保存成功`);
|
||||
}, 800);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal :title="form._isNew ? '新增' : '编辑'" v-model:visible="showModal" @ok="submit" @cancel="cancel" width="1000px">
|
||||
<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="{ span: 3 }" :wrapperCol="{ span: 20 }">
|
||||
<a-form-item label="Cookie名称" required name="userName">
|
||||
<a-input v-model:value="form.userName" />
|
||||
@@ -321,7 +494,7 @@ const downImgVideo = ref(true);
|
||||
|
||||
<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-input v-model:value="form.secUserId" style="flex: 1;" placeholder="" />
|
||||
<a-tooltip title="如果要同步“我喜欢”的视频和关注列表时,必填!!!">
|
||||
<ExclamationCircleOutlined style="color: #faad14;font-size: 16px;" />
|
||||
</a-tooltip>
|
||||
@@ -335,12 +508,54 @@ const downImgVideo = ref(true);
|
||||
<a-alert message="不想同步收藏的视频就空着" type="info" size="small" style="flex: 1; margin-bottom: 0;" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 新增:是否全部用一个地址开关 -->
|
||||
<a-form-item label="统一存储路径" name="useSinglePath">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<a-switch v-model:checked="form.useSinglePath" :checked-value="true" :un-checked-value="false" size="default" />
|
||||
<span>{{ form.useSinglePath ? '共用收藏视频路径,如果是容器部署-docker-compose配置,只需要映射一个路径就行了' : '各类视频路径分开独立配置' }}</span>
|
||||
<a-form-item v-if="form.savePath&&form.savePath.length>0" label="是否统一存储路径" name="useSinglePath">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="width: 200px;">
|
||||
<a-switch v-model:checked="form.useSinglePath" :checked-value="true" :un-checked-value="false" size="default" />
|
||||
<span style="margin-left:10px;">{{form.useSinglePath ?'是':'否'}}</span>
|
||||
</div>
|
||||
|
||||
<a-alert message="开启后,所有视频都存储在收藏视频存储的路径,如果是容器部署:docker-compose配置,此时只需要映射一个路径' " :type="form.useSinglePath?'success':'info'" size="small" style="flex: 1; margin-bottom: 0;" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="form.savePath&&form.savePath.length>0" label="下载收藏夹" name="useCollectFolder">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="width: 200px;">
|
||||
<a-switch v-model:checked="form.useCollectFolder" :checked-value="true" :un-checked-value="false" size="default" />
|
||||
<span style="margin-left:10px;">{{ form.useCollectFolder ? '是' : '否' }}</span>
|
||||
<a-button @click="openCollectFolderSetModal" shape="circle" type="dashed" style="margin-left:10px;" v-if="form.useCollectFolder">
|
||||
<setting-outlined />
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<a-alert message="开启后按照设置的自定义收藏文件夹进行下载(需要开启收藏文件夹同步开关)" :type="form.useCollectFolder?'success':'info'" size="small" style="flex: 1; margin-bottom: 0;" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-if="form.savePath&&form.savePath.length>0" label="下载合集" name="downMix">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="width: 200px;">
|
||||
<a-switch v-model:checked="form.downMix" :checked-value="true" :un-checked-value="false" size="default" />
|
||||
<span style="margin-left:10px;">{{ form.downMix ? '是' : '否' }}</span>
|
||||
<a-button @click="openMixDownSetModal" shape="circle" type="dashed" style="margin-left:10px;" v-if="form.downMix"> <setting-outlined />
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<a-alert message="开启后会同步下载收藏的合集(需要开启合集同步开关)" :type="form.downMix?'success':'info'" size="small" style="flex: 1; margin-bottom: 0;" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-if="form.savePath&&form.savePath.length>0" label="下载短剧" name="downSeries">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="width: 200px;">
|
||||
<a-switch v-model:checked="form.downSeries" :checked-value="true" :un-checked-value="false" size="default" />
|
||||
<span style="margin-left:10px;">{{ form.downSeries ? '是' : '否' }}</span>
|
||||
<a-button @click="openSeriesDownSetModal" shape="circle" type="dashed" style="margin-left:10px;" v-if="form.downSeries"> <setting-outlined />
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<a-alert message="开启后会同步下载收藏的短剧(需要开启短剧同步开关)" :type="form.downSeries?'success':'info'" size="small" style="flex: 1; margin-bottom: 0;" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
@@ -355,7 +570,7 @@ const downImgVideo = ref(true);
|
||||
<!-- 关注的存储路径 -->
|
||||
<a-form-item label="关注的存储路径" name="upSavePath">
|
||||
<div style="display: flex; align-items: center; gap: 12px; width: 100%;">
|
||||
<a-input v-model:value="form.upSavePath" :disabled="form.useSinglePath" placeholder="开启统一存储路径模式后将自动同步收藏路径的值" style="width: 200px;" />
|
||||
<a-input v-model:value="form.upSavePath" :disabled="form.useSinglePath" placeholder="" style="width: 200px;" />
|
||||
<a-alert message="不想同步关注列表博主的视频就空着" type="info" size="small" style="flex: 1; margin-bottom: 0;" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
@@ -363,7 +578,7 @@ const downImgVideo = ref(true);
|
||||
<!-- 图文的存储路径 -->
|
||||
<a-form-item label="图文的存储路径" name="imgSavePath">
|
||||
<div style="display: flex; align-items: center; gap: 12px; width: 100%;">
|
||||
<a-input v-model:value="form.imgSavePath" :disabled="form.useSinglePath" placeholder="开启统一存储路径模式后将自动同步收藏路径的值" style="width: 200px;" />
|
||||
<a-input v-model:value="form.imgSavePath" :disabled="form.useSinglePath" style="width: 200px;" />
|
||||
<a-alert message="如果系统配置页面开启了同步图文视频,且开启了单独存储,则必填!!!" type="info" size="small" style="flex: 1; margin-bottom: 0;" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
@@ -378,6 +593,65 @@ const downImgVideo = ref(true);
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 公用抽屉组件(层级高于原有弹窗)- 优化后 -->
|
||||
<a-drawer :title="getDrawerTypeName()+'配置'" v-model:visible="showDrawer" placement="right" width="800px" :z-index="10010" :mask-z-index="10009" @close="closeDrawer" class="common-drawer">
|
||||
<!-- 抽屉右上角:添加保存按钮(利用 extra 插槽,不改动其他模板) -->
|
||||
<template #extra>
|
||||
<a-button type="primary" :loading="drawerPagination.loading" @click="saveDrawerData" class="drawer-save-btn">
|
||||
<template #icon>
|
||||
<SaveOutlined />
|
||||
</template>
|
||||
保存
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<!-- 抽屉滚动容器(绑定ref和滚动样式) -->
|
||||
<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" fallback="https://placeholder.picsum.photos/120/180" 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 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">
|
||||
已加载全部{{ getDrawerTypeName() }}数据
|
||||
</div>
|
||||
|
||||
<!-- 加载下一页中 -->
|
||||
<div v-if="drawerPagination.loading && drawerDataList.length > 0" class="loading-more">
|
||||
<Spin size="small" />
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-drawer>
|
||||
<!-- 成员表格 -->
|
||||
<a-table v-bind="$attrs" :columns="columns" :dataSource="dataSource" :pagination="false">
|
||||
<template #title>
|
||||
@@ -399,11 +673,16 @@ const downImgVideo = ref(true);
|
||||
</template>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="column.dataIndex === 'status'">
|
||||
<a-badge class="text-subtext" :color="text === 1 ? 'green' : 'red'">
|
||||
<template #text>
|
||||
<span class="text-subtext">{{ StatusDict[text as Status] }}</span>
|
||||
</template>
|
||||
</a-badge>
|
||||
<!-- 新:a-switch 显示状态,绑定切换逻辑 -->
|
||||
<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 === 'cookies'">
|
||||
<a-button @click="showCookies(record)">查看</a-button>
|
||||
@@ -412,19 +691,7 @@ const downImgVideo = ref(true);
|
||||
<a-button @click="showUpers(record)">查看</a-button>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'edit'">
|
||||
<!-- 同步状态切换按钮 -->
|
||||
<a-button :disabled="loading" type="link" @click="switchSyncStatus(record)" :style="{ color: record.status === 1 ? '#ff4d4f' : 'green' }">
|
||||
<template #icon>
|
||||
<span v-if="record.status === 1" style="margin-right:5px;">
|
||||
<StopOutlined />
|
||||
</span>
|
||||
<span v-else style="margin-right:5px;">
|
||||
<ClockCircleOutlined />
|
||||
</span>
|
||||
</template>
|
||||
{{ record.status === 1 ? '停止同步' : '开启同步' }}
|
||||
</a-button>
|
||||
|
||||
<!-- 已删除「开启/停止」按钮,仅保留编辑和删除 -->
|
||||
<a-button :disabled="showModal || loading" type="link" @click="edit(record)">
|
||||
<template #icon>
|
||||
<EditFilled />
|
||||
@@ -459,8 +726,8 @@ const downImgVideo = ref(true);
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
// 原有非抽屉样式(保留,不改动)
|
||||
.cookie-content {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
@@ -526,16 +793,168 @@ const downImgVideo = ref(true);
|
||||
}
|
||||
|
||||
/* 修复:移除对alert的自定义样式修改,还原官方默认样式 */
|
||||
/* 删除了原有的ant-alert-small和ant-alert-message自定义样式,避免覆盖官方主题 */
|
||||
/* 如需调整alert间距,仅修改外层容器,不修改alert内部样式 */
|
||||
.alert-wrapper {
|
||||
flex: 1;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
// ========== 精准匹配模板:a-card-grid 样式优化 ==========
|
||||
/* 父容器:解决卡片间隙,避免换行(不改动模板,仅样式控制) */
|
||||
.drawer-card-container.grid-container {
|
||||
// 清除 a-card 自带内边距,避免与卡片间隙冲突
|
||||
:deep(.ant-card-body) {
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
// 启用 Flex 布局,实现卡片均匀间隙(核心:gap 不与组件自带样式冲突)
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px; /* 卡片之间的横向/纵向间隙(可自定义,不会换行) */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* a-card-grid 核心样式:圆角 + 固定宽度 + 内边距 */
|
||||
:deep(.drawer-card-grid) {
|
||||
// 精确宽度计算:3列均分,减去 gap 分摊值,避免换行
|
||||
width: calc(33.333% - 10.333px) !important; /* 20px gap 分摊到3列,每列减 ~13.333px */
|
||||
margin: 5px !important; /* 清除组件自带 margin,避免间隙重复 */
|
||||
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) {
|
||||
:deep(.drawer-card-grid) {
|
||||
width: calc(50% - 10px) !important; /* 2列均分,20px gap 每列减10px */
|
||||
}
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
:deep(.drawer-card-grid) {
|
||||
width: 100% !important; /* 1列铺满,无间距分摊 */
|
||||
}
|
||||
}
|
||||
// ========== 黑暗模式样式(html.dark-mode + 链式类名,光效增强 + hover 效果) ==========
|
||||
/* 卡片:深色背景 + 隐式边框 + 增强光效(默认状态) */
|
||||
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; /* 内阴影:轻微高光,增强卡片质感 */
|
||||
/* 可选:完全隐藏边框 */
|
||||
/* border: none !important; */
|
||||
transition: all 0.3s ease-in-out !important; /* 过渡动画:hover 效果平滑切换,不生硬 */
|
||||
}
|
||||
|
||||
/* 卡片 hover 效果:悬浮时光效更突出,层次感拉满 */
|
||||
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; /* 可选:轻微上浮,增强交互感(可删除) */
|
||||
}
|
||||
|
||||
/* label:强制纯白色(核心需求,格式不变) */
|
||||
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>
|
||||
|
||||
<!-- 新增全局样式块,确保alert样式不受scoped影响(仅针对alert还原默认) -->
|
||||
<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;
|
||||
}
|
||||
}
|
||||
|
||||
/* 还原Ant Design Vue alert官方默认样式,消除scoped样式的间接影响 */
|
||||
.ant-alert {
|
||||
box-sizing: border-box;
|
||||
@@ -563,6 +982,15 @@ const downImgVideo = ref(true);
|
||||
.ant-alert-info .ant-alert-message {
|
||||
color: #1677ff;
|
||||
}
|
||||
/* 还原alert不同类型的官方默认颜色 */
|
||||
.ant-alert-success {
|
||||
background-color: #daf1d3;
|
||||
border: 1px solid #5bbc51;
|
||||
}
|
||||
.ant-alert-success .ant-alert-message {
|
||||
color: #228b22;
|
||||
}
|
||||
|
||||
.ant-alert-warning {
|
||||
background-color: #fffbe6;
|
||||
border: 1px solid #ffe58f;
|
||||
|
||||
@@ -1,26 +1,6 @@
|
||||
<template>
|
||||
<a-drawer v-model:visible="visible" class="custom-class" title="个人设置" placement="right" @after-visible-change="afterVisibleChange" :maskClosable="true">
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<!-- <a-tab-pane key="1" style="height:100%">
|
||||
<template #tab>
|
||||
<span>
|
||||
<user-outlined />
|
||||
修改头像
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<a-upload style="" v-model:file-list="fileList" name="file" list-type="picture-card" class="avatar-uploader" :show-upload-list="false" :action="uploadAction" :before-upload="beforeUpload" @change="handleChange" accept=".jpg, .jpeg, .png">
|
||||
<img v-if="imageUrl" :src="imageUrl" alt="avatar" style="height: 112px; width: 112px; border-radius: 50%;" />
|
||||
<div v-else>
|
||||
<loading-outlined v-if="loading"></loading-outlined>
|
||||
<plus-outlined v-else></plus-outlined>
|
||||
<div class="ant-upload-text">选择图片</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
<div style="margin-top:20px;">
|
||||
上传成功即修改成功
|
||||
</div>
|
||||
</a-tab-pane> -->
|
||||
<a-tab-pane key="2">
|
||||
<template #tab>
|
||||
<span>
|
||||
@@ -60,7 +40,6 @@ import { checkPass } from '@/utils/regexHelper';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
|
||||
const activeKey = ref<string>('2');
|
||||
const uploadAction = ref<string>('');
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
function show(vis: boolean) {
|
||||
@@ -122,7 +101,6 @@ const loadUserInfo = () => {
|
||||
if (res.code === 0) {
|
||||
if (res.data.avatar != null && res.data.avatar !== '') imageUrl.value = `/upload/${res.data.avatar}`;
|
||||
passwordFormData.UserId = res.data.id;
|
||||
uploadAction.value = `/api/auth/UpdateUserAvatar?Uid=${res.data.id}`;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
+23
-33
@@ -151,15 +151,6 @@ export const useApiStore = defineStore('coreapi', () => {
|
||||
});
|
||||
}
|
||||
|
||||
async function GetAppPort() {
|
||||
return http.request<any, Response<any>>('/api/config/appport', 'get').then(r => {
|
||||
return r;
|
||||
}).finally(() => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function deleteCookie(id: string) {
|
||||
return http.request<any, Response<any>>('/api/config/delete?id=' + id, 'get').then(r => {
|
||||
@@ -347,28 +338,28 @@ export const useApiStore = defineStore('coreapi', () => {
|
||||
}
|
||||
|
||||
|
||||
// 音频文件上传接口
|
||||
async function apiUploadAudio(formData: FormData, options?: { onUploadProgress?: (progressEvent: ProgressEvent) => void }) {
|
||||
return http
|
||||
.request<any, Response<any>>(
|
||||
'/api/config/UploadAudio', // 请求地址
|
||||
'post_form', // 使用新增的 post_form 类型
|
||||
formData, // FormData 参数(文件+其他参数)
|
||||
{
|
||||
onUploadProgress: options?.onUploadProgress, // 上传进度回调(原生 ProgressEvent)
|
||||
timeout: 120000 // 上传文件超时时间设为2分钟(可选)
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
// console.log('音频上传结果:', res);
|
||||
// 适配你的响应格式(如果响应是包裹层,取 data)
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('音频上传失败:', err);
|
||||
throw err; // 抛出错误让前端捕获
|
||||
});
|
||||
}
|
||||
// // 音频文件上传接口
|
||||
// async function apiUploadAudio(formData: FormData, options?: { onUploadProgress?: (progressEvent: ProgressEvent) => void }) {
|
||||
// return http
|
||||
// .request<any, Response<any>>(
|
||||
// '/api/config/UploadAudio', // 请求地址
|
||||
// 'post_form', // 使用新增的 post_form 类型
|
||||
// formData, // FormData 参数(文件+其他参数)
|
||||
// {
|
||||
// onUploadProgress: options?.onUploadProgress, // 上传进度回调(原生 ProgressEvent)
|
||||
// timeout: 120000 // 上传文件超时时间设为2分钟(可选)
|
||||
// }
|
||||
// )
|
||||
// .then((res) => {
|
||||
// // console.log('音频上传结果:', res);
|
||||
// // 适配你的响应格式(如果响应是包裹层,取 data)
|
||||
// return res;
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// console.error('音频上传失败:', err);
|
||||
// throw err; // 抛出错误让前端捕获
|
||||
// });
|
||||
// }
|
||||
|
||||
return {
|
||||
getVer,
|
||||
@@ -376,8 +367,7 @@ export const useApiStore = defineStore('coreapi', () => {
|
||||
BathRealDelete,
|
||||
DeleteByAuthor,
|
||||
Renfo,
|
||||
apiUploadAudio,
|
||||
GetAppPort,
|
||||
// apiUploadAudio,
|
||||
AppisInit,
|
||||
DeskInitAsync,
|
||||
SwitchCookieStatus,
|
||||
|
||||
Reference in New Issue
Block a user