结构调整

This commit is contained in:
jianzhichu
2026-01-19 09:57:40 +08:00
parent d9cb721f64
commit f5449f3ace
77 changed files with 4060 additions and 1516 deletions
+1 -1
View File
@@ -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>
+475 -47
View File
@@ -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;
-22
View File
@@ -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
View File
@@ -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,