mirror of
https://github.com/jiangrui1994/CloudSaver.git
synced 2026-01-09 14:48:47 +08:00
refactor:优化移动端页面
This commit is contained in:
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -57,7 +57,6 @@ declare module 'vue' {
|
||||
VanImage: typeof import('vant/es')['Image']
|
||||
VanLoading: typeof import('vant/es')['Loading']
|
||||
VanOverlay: typeof import('vant/es')['Overlay']
|
||||
VanPopover: typeof import('vant/es')['Popover']
|
||||
VanPopup: typeof import('vant/es')['Popup']
|
||||
VanSearch: typeof import('vant/es')['Search']
|
||||
VanSwitch: typeof import('vant/es')['Switch']
|
||||
|
||||
@@ -2,7 +2,7 @@ module.exports = {
|
||||
plugins: {
|
||||
"postcss-pxtorem": {
|
||||
rootValue({ file }) {
|
||||
return file.indexOf("mobile") !== -1 || file.indexOf("vant") !== -1 ? 37.5 : 75;
|
||||
return file.indexOf("vant") !== -1 || file.indexOf("mobile") !== -1 ? 50 : 75;
|
||||
},
|
||||
propList: ["*"],
|
||||
exclude: (file) => {
|
||||
|
||||
@@ -1,38 +1,63 @@
|
||||
<template>
|
||||
<div class="folder-select">
|
||||
<div class="folder-select-header">
|
||||
当前位置:<el-icon style="margin: 0 5px"><Folder /></el-icon>
|
||||
<span
|
||||
v-for="(path, index) in currentFolderPath"
|
||||
:key="path.cid"
|
||||
class="path-item"
|
||||
@click="handleFolderClick(path, index)"
|
||||
>
|
||||
{{ path.name }}
|
||||
<span v-if="index !== currentFolderPath.length - 1" class="path-separator">></span>
|
||||
</span>
|
||||
<!-- 面包屑导航 -->
|
||||
<div class="folder-select__nav">
|
||||
<van-cell :border="false" class="nav-cell">
|
||||
<template #title>
|
||||
<div class="nav-breadcrumb">
|
||||
<van-icon name="wap-home-o" class="home-icon" @click="handleHomeClick" />
|
||||
<template v-for="(path, index) in currentFolderPath" :key="path.cid">
|
||||
<van-icon v-if="index !== 0" name="arrow" />
|
||||
<span
|
||||
class="path-item"
|
||||
:class="{ 'is-active': index === currentFolderPath.length - 1 }"
|
||||
@click="handleFolderClick(path, index)"
|
||||
>
|
||||
{{ path.name }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
</div>
|
||||
<div class="folder-item-list">
|
||||
<div v-for="item in folders" :key="item.cid" class="folder-item" @click="getList(item)">
|
||||
<span class="folder-node">
|
||||
<el-icon><Folder /></el-icon>
|
||||
{{ item.name }}
|
||||
</span>
|
||||
|
||||
<!-- 文件夹列表 -->
|
||||
<div class="folder-select__list">
|
||||
<div v-if="resourceStore.loadTree" class="folder-select__loading">
|
||||
<van-loading type="spinner" vertical>加载中...</van-loading>
|
||||
</div>
|
||||
<van-empty v-if="!resourceStore.loadTree && !folders.length" description="暂无文件夹" />
|
||||
<van-cell-group v-if="!resourceStore.loadTree && folders.length" :border="false">
|
||||
<van-cell
|
||||
v-for="folder in folders"
|
||||
:key="folder.cid"
|
||||
:border="false"
|
||||
clickable
|
||||
@click="getList(folder)"
|
||||
>
|
||||
<template #icon>
|
||||
<van-icon name="folder-o" class="folder-icon" />
|
||||
</template>
|
||||
<template #title>
|
||||
<span class="folder-name">{{ folder.name }}</span>
|
||||
</template>
|
||||
<template #right-icon>
|
||||
<van-icon name="arrow" />
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, defineProps, watch } from "vue";
|
||||
import { ref, defineProps, onBeforeUnmount } from "vue";
|
||||
import { cloud115Api } from "@/api/cloud115";
|
||||
import { quarkApi } from "@/api/quark";
|
||||
import type { Folder } from "@/types";
|
||||
import { type RequestResult } from "@/types/response";
|
||||
import { useResourceStore } from "@/stores/resource";
|
||||
|
||||
const resourceStore = useResourceStore();
|
||||
import { ElMessage } from "element-plus";
|
||||
import { showNotify } from "vant";
|
||||
|
||||
const props = defineProps({
|
||||
cloudType: {
|
||||
@@ -41,10 +66,12 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const resourceStore = useResourceStore();
|
||||
const folders = ref<Folder[]>([]);
|
||||
const currentFolderPath = ref<Folder[]>([]);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "select", currentFolderPath: Folder[]): void;
|
||||
(e: "select", currentFolderPath: Folder[] | null): void;
|
||||
(e: "close"): void;
|
||||
}>();
|
||||
|
||||
@@ -53,98 +80,182 @@ const cloudTypeApiMap = {
|
||||
quark: quarkApi,
|
||||
};
|
||||
|
||||
const handleFolderClick = (folder: Folder, index: number) => {
|
||||
const current = { ...folder };
|
||||
currentFolderPath.value = currentFolderPath.value.slice(0, index);
|
||||
getList(current);
|
||||
// 返回根目录
|
||||
const handleHomeClick = () => {
|
||||
currentFolderPath.value = [];
|
||||
getList();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => currentFolderPath.value,
|
||||
() => {
|
||||
emit("select", currentFolderPath.value);
|
||||
},
|
||||
{ deep: true } // 添加深度监听
|
||||
);
|
||||
const handleFolderClick = (folder: Folder, index: number) => {
|
||||
currentFolderPath.value = currentFolderPath.value.slice(0, index + 1);
|
||||
getList(folder);
|
||||
};
|
||||
|
||||
const getList = async (data?: Folder) => {
|
||||
const api = cloudTypeApiMap[props.cloudType as keyof typeof cloudTypeApiMap];
|
||||
try {
|
||||
let res: RequestResult<Folder[]> = { code: 0, data: [] as Folder[], message: "" };
|
||||
resourceStore.setLoadTree(true);
|
||||
if (api.getFolderList) {
|
||||
// 使用类型保护检查方法是否存在
|
||||
res = await api.getFolderList(data?.cid || "0");
|
||||
}
|
||||
const res: RequestResult<Folder[]> = await api.getFolderList?.(data?.cid || "0");
|
||||
|
||||
if (res?.code === 0) {
|
||||
folders.value = res.data.length ? res.data : [];
|
||||
currentFolderPath.value.push(
|
||||
data || {
|
||||
name: "根目录",
|
||||
cid: "0",
|
||||
}
|
||||
);
|
||||
folders.value = res.data || [];
|
||||
if (!data) {
|
||||
currentFolderPath.value = [
|
||||
{
|
||||
name: "根目录",
|
||||
cid: "0",
|
||||
},
|
||||
];
|
||||
} else if (!currentFolderPath.value.find((p) => p.cid === data.cid)) {
|
||||
currentFolderPath.value.push(data);
|
||||
}
|
||||
emit("select", currentFolderPath.value);
|
||||
} else {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
resourceStore.setLoadTree(false);
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? `${error.message}` : "获取目录失败");
|
||||
// 关闭模态框
|
||||
emit("close");
|
||||
resourceStore.setLoadTree(false);
|
||||
showNotify({
|
||||
type: "danger",
|
||||
message: error instanceof Error ? error.message : "获取目录失败",
|
||||
});
|
||||
currentFolderPath.value = [];
|
||||
folders.value = [];
|
||||
emit("select", null);
|
||||
emit("close");
|
||||
} finally {
|
||||
resourceStore.setLoadTree(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化加载
|
||||
getList();
|
||||
|
||||
// 组件销毁前重置状态
|
||||
onBeforeUnmount(() => {
|
||||
currentFolderPath.value = [];
|
||||
folders.value = [];
|
||||
emit("select", null);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/responsive.scss";
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.folder-select {
|
||||
position: relative;
|
||||
padding-top: var(--spacing-xl);
|
||||
height: 100%;
|
||||
background: var(--theme-other_background);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&-header {
|
||||
&__nav {
|
||||
flex-shrink: 0;
|
||||
border-bottom: 0.5px solid #f5f5f5;
|
||||
background: var(--theme-other_background);
|
||||
|
||||
.nav-cell {
|
||||
padding: 12px 16px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.nav-breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.home-icon {
|
||||
font-size: 16px;
|
||||
color: var(--theme-theme);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.path-item {
|
||||
color: #666;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
|
||||
&.is-active {
|
||||
color: var(--theme-theme);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
position: relative;
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: var(--font-size-base);
|
||||
padding: var(--spacing-sm) var(--spacing-base);
|
||||
border: 1px solid #e5e6e8;
|
||||
border-radius: var(--border-radius-base);
|
||||
flex-direction: column;
|
||||
|
||||
.van-empty {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.van-cell-group {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__loading {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.folder-item {
|
||||
font-size: var(--font-size-lg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--spacing-base) var(--spacing-sm);
|
||||
border-bottom: 1px dashed #ececec;
|
||||
|
||||
.folder-node {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
}
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
z-index: 0;
|
||||
|
||||
.path-item {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
.van-loading {
|
||||
padding: 16px 24px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.folder-icon {
|
||||
font-size: 20px;
|
||||
color: var(--theme-theme);
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.folder-name {
|
||||
font-size: 15px;
|
||||
color: var(--theme-color);
|
||||
}
|
||||
}
|
||||
|
||||
.path-separator {
|
||||
margin: 0 var(--spacing-xs);
|
||||
// 深度修改 Vant 组件样式
|
||||
:deep(.van-cell) {
|
||||
padding: 12px 16px;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-empty) {
|
||||
padding: 32px 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="content__image">
|
||||
<van-image
|
||||
:src="`/tele-images/?url=${encodeURIComponent(item.image as string)}`"
|
||||
fit="contain"
|
||||
fit="cover"
|
||||
lazy-load
|
||||
/>
|
||||
<!-- 来源标签移到图片左上角 -->
|
||||
@@ -125,10 +125,10 @@ const toggleExpand = (id: string) => {
|
||||
}
|
||||
|
||||
.resource-card {
|
||||
padding: var(--spacing-base);
|
||||
padding: 5px 10px;
|
||||
|
||||
&__item {
|
||||
margin-bottom: var(--spacing-base);
|
||||
margin-bottom: 12px;
|
||||
background: var(--theme-other_background);
|
||||
border-radius: var(--border-radius-lg);
|
||||
overflow: hidden;
|
||||
@@ -138,8 +138,8 @@ const toggleExpand = (id: string) => {
|
||||
.item {
|
||||
&__content {
|
||||
display: flex;
|
||||
gap: var(--spacing-base);
|
||||
padding: var(--spacing-base);
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,11 +240,21 @@ const toggleExpand = (id: string) => {
|
||||
&__action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 4px 0;
|
||||
|
||||
.van-button {
|
||||
font-size: 12px;
|
||||
height: 24px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
height: 32px;
|
||||
padding: 0 20px;
|
||||
|
||||
:deep(.van-button__text) {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,184 @@
|
||||
<template>
|
||||
<div class="resource-select">
|
||||
<van-checkbox-group v-model="selectedResourceIds" @change="handleCheckChange">
|
||||
<div v-for="item in resourceStore.shareInfo.list" :key="item.fileId" class="resource-item">
|
||||
<div class="resource-item-left">
|
||||
<span class="resource-node">
|
||||
<el-icon><Folder /></el-icon>
|
||||
<div class="resource-name">
|
||||
{{ item.fileName }}
|
||||
<span v-if="item.fileSize" class="file-size">
|
||||
({{ formattedFileSize(item.fileSize) }})
|
||||
</span>
|
||||
<van-checkbox-group v-model="selectedResourceIds">
|
||||
<van-cell-group :border="false">
|
||||
<van-cell
|
||||
v-for="item in resourceStore.shareInfo.list"
|
||||
:key="item.fileId"
|
||||
class="resource-item"
|
||||
:border="false"
|
||||
center
|
||||
@click="handleItemClick(item.fileId)"
|
||||
>
|
||||
<template #title>
|
||||
<div class="resource-item__content">
|
||||
<van-icon name="folder-o" class="content__icon" />
|
||||
<div class="content__info">
|
||||
<span class="info__name">{{ item.fileName }}</span>
|
||||
<span v-if="item.fileSize" class="info__size">
|
||||
{{ formattedFileSize(item.fileSize) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div class="resource-item-right">
|
||||
<van-checkbox :name="item.fileId"></van-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right-icon>
|
||||
<van-checkbox
|
||||
:name="item.fileId"
|
||||
class="resource-item__checkbox"
|
||||
@click.stop="handleItemClick(item.fileId)"
|
||||
/>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-checkbox-group>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<van-empty v-if="!resourceStore.shareInfo.list?.length" description="暂无可选资源" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
import { useResourceStore } from "@/stores/resource";
|
||||
import { formattedFileSize } from "@/utils/index";
|
||||
|
||||
const resourceStore = useResourceStore();
|
||||
const selectedResourceIds = ref<string[]>();
|
||||
selectedResourceIds.value = resourceStore.resourceSelect.map((x) => x.fileId);
|
||||
const selectedResourceIds = ref<string[]>([]);
|
||||
|
||||
const handleCheckChange = (Ids: string[]) => {
|
||||
// 初始化选中状态
|
||||
selectedResourceIds.value = resourceStore.resourceSelect
|
||||
.filter((x) => x.isChecked)
|
||||
.map((x) => x.fileId);
|
||||
|
||||
// 监听选中状态变化
|
||||
watch(selectedResourceIds, (newIds) => {
|
||||
const newResourceSelect = [...resourceStore.resourceSelect];
|
||||
newResourceSelect.forEach((x) => {
|
||||
x.isChecked = Ids.includes(x.fileId);
|
||||
x.isChecked = newIds.includes(x.fileId);
|
||||
});
|
||||
resourceStore.setSelectedResource(newResourceSelect);
|
||||
});
|
||||
|
||||
// 添加点击处理函数
|
||||
const handleItemClick = (fileId: string) => {
|
||||
const index = selectedResourceIds.value.indexOf(fileId);
|
||||
if (index === -1) {
|
||||
selectedResourceIds.value.push(fileId);
|
||||
} else {
|
||||
selectedResourceIds.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/responsive.scss";
|
||||
|
||||
.resource-select {
|
||||
min-height: 300px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
padding: var(--spacing-base);
|
||||
<style lang="scss" scoped>
|
||||
// 工具类
|
||||
@mixin text-ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.resource-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--spacing-base) 0;
|
||||
border-bottom: 1px dashed #ececec;
|
||||
.resource-select {
|
||||
height: 100%;
|
||||
background: var(--theme-other_background);
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
|
||||
&-left {
|
||||
flex: 1;
|
||||
margin-right: var(--spacing-base);
|
||||
.resource-item {
|
||||
position: relative;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 8px 0;
|
||||
margin-right: 40px;
|
||||
|
||||
.content__icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 20px;
|
||||
color: var(--theme-theme);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.content__info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
.info__name {
|
||||
font-size: 15px;
|
||||
line-height: 1.4;
|
||||
color: var(--van-text-color);
|
||||
word-break: break-all;
|
||||
white-space: normal;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.info__size {
|
||||
font-size: 13px;
|
||||
color: var(--van-gray-6);
|
||||
@include text-ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__checkbox {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
:deep(.van-checkbox__icon) {
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
|
||||
.van-icon {
|
||||
border-radius: 2px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--van-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.resource-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
font-size: var(--font-size-lg);
|
||||
// 深度修改 Vant 组件样式
|
||||
:deep(.van-cell) {
|
||||
align-items: flex-start;
|
||||
padding: 0 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
min-height: 60px;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.van-cell__title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.resource-name {
|
||||
word-break: break-all;
|
||||
:deep(.van-checkbox__icon--checked) {
|
||||
.van-icon {
|
||||
background-color: var(--theme-theme);
|
||||
border-color: var(--theme-theme);
|
||||
}
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: var(--font-size-sm);
|
||||
color: #999;
|
||||
margin-left: var(--spacing-xs);
|
||||
:deep(.van-empty) {
|
||||
padding: 32px 0;
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ElMessage } from "element-plus";
|
||||
|
||||
interface StoreType {
|
||||
hotList: HotListItem[];
|
||||
loading: boolean;
|
||||
currentParams: CurrentParams;
|
||||
}
|
||||
|
||||
@@ -16,6 +17,7 @@ interface CurrentParams {
|
||||
export const useDoubanStore = defineStore("douban", {
|
||||
state: (): StoreType => ({
|
||||
hotList: [],
|
||||
loading: false,
|
||||
currentParams: {
|
||||
type: "movie",
|
||||
tag: "热门",
|
||||
@@ -24,6 +26,7 @@ export const useDoubanStore = defineStore("douban", {
|
||||
|
||||
actions: {
|
||||
async getHotList() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const params = {
|
||||
type: this.currentParams.type,
|
||||
@@ -40,6 +43,8 @@ export const useDoubanStore = defineStore("douban", {
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error || "获取热门列表失败");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
setCurrentParams(currentParams: CurrentParams) {
|
||||
|
||||
@@ -1,47 +1,77 @@
|
||||
@mixin text-ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 响应式布局工具类
|
||||
@mixin mobile {
|
||||
@media screen and (max-width: 768px) {
|
||||
@content;
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin tablet {
|
||||
@media screen and (min-width: 769px) and (max-width: 1024px) {
|
||||
@content;
|
||||
}
|
||||
@media screen and (min-width: 769px) and (max-width: 1024px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin desktop {
|
||||
@media screen and (min-width: 1025px) {
|
||||
@content;
|
||||
}
|
||||
@media screen and (min-width: 1025px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// 通用样式变量
|
||||
:root {
|
||||
// 字体大小
|
||||
--font-size-xs: 12px;
|
||||
--font-size-sm: 14px;
|
||||
--font-size-base: 16px;
|
||||
--font-size-lg: 18px;
|
||||
--font-size-xl: 20px;
|
||||
// 字体大小 - 整体缩小约25%
|
||||
--font-size-xs: 20px; // 原24px
|
||||
--font-size-sm: 22px; // 原26px
|
||||
--font-size-base: 24px; // 原28px
|
||||
--font-size-lg: 28px; // 原32px
|
||||
--font-size-xl: 32px; // 原36px
|
||||
|
||||
// 间距 - 也相应缩小
|
||||
--spacing-xs: 6px; // 原8px
|
||||
--spacing-sm: 10px; // 原12px
|
||||
--spacing-base: 14px; // 原16px
|
||||
--spacing-lg: 20px; // 原24px
|
||||
--spacing-xl: 28px; // 原32px
|
||||
|
||||
// 圆角 - 适当调整
|
||||
--border-radius-sm: 6px; // 原8px
|
||||
--border-radius-base: 10px; // 原12px
|
||||
--border-radius-lg: 14px; // 原16px
|
||||
--border-radius-xl: 20px; // 原24px
|
||||
|
||||
// 移动端特殊变量
|
||||
@include mobile {
|
||||
--font-size-base: 14px;
|
||||
--spacing-base: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端适配
|
||||
@media screen and (max-width: 768px) {
|
||||
:root {
|
||||
// 间距
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-base: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-xs: 3px;
|
||||
--spacing-sm: 5px;
|
||||
--spacing-base: 7px;
|
||||
--spacing-lg: 10px;
|
||||
--spacing-xl: 14px;
|
||||
|
||||
// 字体大小
|
||||
--font-size-xs: 10px;
|
||||
--font-size-sm: 11px;
|
||||
--font-size-base: 12px;
|
||||
--font-size-lg: 14px;
|
||||
--font-size-xl: 16px;
|
||||
|
||||
// 圆角
|
||||
--border-radius-sm: 4px;
|
||||
--border-radius-base: 8px;
|
||||
--border-radius-lg: 16px;
|
||||
--border-radius-xl: 24px;
|
||||
|
||||
// 移动端特殊变量
|
||||
@include mobile {
|
||||
--font-size-base: 14px;
|
||||
--spacing-base: 12px;
|
||||
}
|
||||
}
|
||||
--border-radius-sm: 3px;
|
||||
--border-radius-base: 5px;
|
||||
--border-radius-lg: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,3 +14,16 @@ export function isMobileDevice() {
|
||||
window.innerWidth <= 768
|
||||
);
|
||||
}
|
||||
|
||||
export function throttle<T extends (...args: any[]) => any>(fn: T, delay: number): T {
|
||||
let lastTime = 0;
|
||||
|
||||
return function (this: any, ...args: Parameters<T>) {
|
||||
const now = Date.now();
|
||||
|
||||
if (now - lastTime >= delay) {
|
||||
fn.apply(this, args);
|
||||
lastTime = now;
|
||||
}
|
||||
} as T;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<div class="mobile-page douban">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="doubanStore.loading" class="douban__loading">
|
||||
<van-loading type="spinner" size="24px" vertical>加载中...</van-loading>
|
||||
</div>
|
||||
|
||||
<!-- 电影列表 -->
|
||||
<div class="douban__grid">
|
||||
<div v-else class="douban__grid">
|
||||
<div v-for="movie in doubanStore.hotList" :key="movie.id" class="douban__item">
|
||||
<!-- 海报卡片 -->
|
||||
<div class="douban__poster">
|
||||
@@ -37,7 +42,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<van-empty v-if="!doubanStore.hotList.length" description="暂无数据" />
|
||||
<van-empty v-if="!doubanStore.loading && !doubanStore.hotList.length" description="暂无数据" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -174,6 +179,24 @@ const getRateColor = (rate: string | number) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载状态
|
||||
&__loading {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--theme-background);
|
||||
z-index: 1;
|
||||
|
||||
:deep(.van-loading) {
|
||||
padding: 16px 24px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 深度修改 Vant 组件样式
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<!-- 底部导航栏 -->
|
||||
<van-tabbar class="home__tabbar" route>
|
||||
<van-tabbar-item to="/resource" icon="search">搜索</van-tabbar-item>
|
||||
<van-tabbar-item to="/douban" icon="video">影视</van-tabbar-item>
|
||||
<van-tabbar-item to="/douban" icon="video">热门</van-tabbar-item>
|
||||
<van-tabbar-item to="/setting" icon="setting-o">设置</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
|
||||
@@ -118,6 +118,8 @@ const handleLogout = () => {
|
||||
// 布局
|
||||
min-height: 100vh;
|
||||
background: var(--theme-background);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// 头部搜索
|
||||
&__header {
|
||||
@@ -159,9 +161,11 @@ const handleLogout = () => {
|
||||
// 主内容区 - 调整顶部间距
|
||||
&__content {
|
||||
padding-top: 64px; // 搜索框高度(48px) + 上下padding(8px * 2)
|
||||
padding-bottom: calc(50px + var(--safe-area-bottom)); // tabbar高度 + 底部安全区域
|
||||
min-height: 100vh;
|
||||
padding-bottom: 100px; // tabbar高度 + 底部安全区域
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
// 加载状态
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<main class="login__content">
|
||||
<!-- 头部 Logo -->
|
||||
<header class="login__header">
|
||||
<img :src="logo" alt="Cloud Saver Logo" class="login__logo" width="50" height="50" />
|
||||
<img :src="logo" alt="Cloud Saver Logo" class="login__logo" width="60" height="60" />
|
||||
<h1 class="login__title">Cloud Saver</h1>
|
||||
</header>
|
||||
|
||||
@@ -156,7 +156,6 @@ const handleSubmit = async () => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login {
|
||||
// 布局
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
@@ -167,7 +166,8 @@ const handleSubmit = async () => {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: url("@/assets/images/mobile-login-bg.png") no-repeat;
|
||||
background-size: 100% auto;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
// 主内容区
|
||||
@@ -176,10 +176,10 @@ const handleSubmit = async () => {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
min-height: 60%;
|
||||
padding: var(--spacing-lg);
|
||||
min-height: 65%;
|
||||
padding: 40px 20px;
|
||||
background-color: var(--theme-other_background);
|
||||
border-radius: var(--border-radius-xl) var(--border-radius-xl) 0 0;
|
||||
border-radius: 24px 24px 0 0;
|
||||
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
@@ -189,87 +189,100 @@ const handleSubmit = async () => {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
&__logo {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-right: var(--spacing-base);
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-right: 12px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: 700;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--theme-theme);
|
||||
}
|
||||
|
||||
// 表单
|
||||
&__form {
|
||||
padding: 0 var(--spacing-base);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&__form-group {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
|
||||
border-radius: var(--border-radius-lg);
|
||||
margin: 0 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__submit {
|
||||
margin-top: var(--spacing-xl);
|
||||
margin: 32px 12px 0;
|
||||
}
|
||||
|
||||
&__button {
|
||||
height: 40px;
|
||||
font-size: 14px;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 新增记住密码容器样式
|
||||
// 记住密码区域
|
||||
&__remember {
|
||||
padding: var(--spacing-sm) var(--spacing-lg);
|
||||
border-top: 1px solid #f5f5f5;
|
||||
padding: 12px 16px;
|
||||
border-top: 0.5px solid #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
// Vant 组件样式覆盖
|
||||
// Vant 组件样式优化
|
||||
:deep(.van-cell-group--inset) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.van-field) {
|
||||
padding: var(--spacing-base);
|
||||
padding: 16px;
|
||||
|
||||
.van-field__label {
|
||||
width: 4em;
|
||||
color: var(--theme-color);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.van-field__value {
|
||||
.van-field__body {
|
||||
input {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.van-field__left-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-field__label) {
|
||||
width: 4em;
|
||||
color: var(--theme-color);
|
||||
}
|
||||
|
||||
:deep(.van-field__left-icon) {
|
||||
margin-right: var(--spacing-sm);
|
||||
}
|
||||
|
||||
// 优化记住密码样式
|
||||
// 记住密码复选框样式优化
|
||||
:deep(.remember-checkbox) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
|
||||
.van-checkbox__icon {
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
|
||||
.van-icon {
|
||||
border: 1px solid #dcdee0;
|
||||
border-radius: 2px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
.van-checkbox__label {
|
||||
margin-left: 6px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&.van-checkbox--checked {
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
<template>
|
||||
<div class="resource-list">
|
||||
<div ref="listRef" class="resource-list">
|
||||
<!-- 头部刷新区 -->
|
||||
<div class="resource-list__header">
|
||||
<van-cell center @click="refreshResources">
|
||||
<van-cell-group :border="false" class="resource-list__header">
|
||||
<van-cell center clickable @click="refreshResources">
|
||||
<template #icon>
|
||||
<van-icon name="replay" class="header__icon" />
|
||||
</template>
|
||||
<template #title>
|
||||
<div class="header__title">
|
||||
<van-icon name="replay" size="18" />
|
||||
<span class="title__text">最新资源</span>
|
||||
<span class="title__time">上次刷新:{{ resourceStore.lastUpdateTime }}</span>
|
||||
<div class="header__content">
|
||||
<span class="content__title">最新资源</span>
|
||||
<span class="content__tip">(点击可获取最新资源)</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #label>
|
||||
<span class="header__time">上次刷新:{{ resourceStore.lastUpdateTime }}</span>
|
||||
</template>
|
||||
</van-cell>
|
||||
</div>
|
||||
</van-cell-group>
|
||||
|
||||
<!-- 资源列表 -->
|
||||
<van-tabs v-model:active="currentTab" sticky swipeable animated>
|
||||
<van-tabs
|
||||
v-model:active="currentTab"
|
||||
swipeable
|
||||
animated
|
||||
class="resource-list__tabs"
|
||||
:border="false"
|
||||
>
|
||||
<van-tab
|
||||
v-for="item in resourceStore.resources"
|
||||
:key="item.id"
|
||||
@@ -36,47 +47,74 @@
|
||||
closeable
|
||||
position="bottom"
|
||||
:style="{ height: '80%' }"
|
||||
class="save-popup"
|
||||
>
|
||||
<!-- 弹窗头部 -->
|
||||
<div class="popup__header">
|
||||
<van-tag :color="getTagColor(currentResource?.cloudType)" round>
|
||||
{{ currentResource?.cloudType }}
|
||||
</van-tag>
|
||||
<span class="header__title">{{ saveDialogMap[saveDialogStep].title }}</span>
|
||||
<span v-if="resourceStore.shareInfo.fileSize && saveDialogStep === 1" class="header__size">
|
||||
{{ formattedFileSize(resourceStore.shareInfo.fileSize) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗内容 -->
|
||||
<div class="popup__content">
|
||||
<van-loading v-if="resourceStore.loadTree" vertical>加载中...</van-loading>
|
||||
|
||||
<resource-select
|
||||
v-if="saveDialogVisible && saveDialogStep === 1 && resourceStore.resourceSelect.length"
|
||||
:cloud-type="currentResource?.cloudType"
|
||||
/>
|
||||
|
||||
<folder-select
|
||||
v-if="saveDialogVisible && saveDialogStep === 2 && currentResource"
|
||||
:cloud-type="currentResource.cloudType"
|
||||
@select="handleFolderSelect"
|
||||
@close="saveDialogVisible = false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗底部 -->
|
||||
<div class="popup__footer">
|
||||
<div class="footer__path">
|
||||
<span class="path__label">保存至</span>
|
||||
<div class="path__value">
|
||||
<van-icon name="notes-o" />
|
||||
<span>{{ getCurrentFolderName }}</span>
|
||||
</div>
|
||||
<div class="save-popup__container">
|
||||
<!-- 弹窗头部 -->
|
||||
<div class="save-popup__header">
|
||||
<van-tag :color="getTagColor(currentResource?.cloudType)" round size="medium">
|
||||
{{ currentResource?.cloudType }}
|
||||
</van-tag>
|
||||
<span class="header__title">{{ saveDialogMap[saveDialogStep].title }}</span>
|
||||
<span
|
||||
v-if="resourceStore.shareInfo.fileSize && saveDialogStep === 1"
|
||||
class="header__size"
|
||||
>
|
||||
{{ formattedFileSize(resourceStore.shareInfo.fileSize) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗内容 -->
|
||||
<div class="save-popup__content">
|
||||
<van-empty v-if="resourceStore.loadTree && saveDialogStep === 1" class="content__loading">
|
||||
<template #image>
|
||||
<van-loading size="24px" vertical>加载中...</van-loading>
|
||||
</template>
|
||||
</van-empty>
|
||||
|
||||
<resource-select
|
||||
v-if="saveDialogVisible && saveDialogStep === 1 && resourceStore.resourceSelect.length"
|
||||
:cloud-type="currentResource?.cloudType"
|
||||
/>
|
||||
|
||||
<folder-select
|
||||
v-if="saveDialogVisible && saveDialogStep === 2 && currentResource"
|
||||
:cloud-type="currentResource.cloudType"
|
||||
@select="handleFolderSelect"
|
||||
@close="saveDialogVisible = false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗底部 -->
|
||||
<div class="save-popup__footer">
|
||||
<van-cell class="footer__path" :border="false">
|
||||
<template #title>
|
||||
<div class="path__label">保存至:</div>
|
||||
</template>
|
||||
<template #value>
|
||||
<div class="path__value">
|
||||
<van-icon name="folder-o" class="value__icon" />
|
||||
<span
|
||||
class="value__text"
|
||||
:class="{ 'value__text--placeholder': !currentFolderPath }"
|
||||
>
|
||||
{{ getCurrentFolderName }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
|
||||
<van-button
|
||||
round
|
||||
block
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="isLoading"
|
||||
@click="handleConfirmClick"
|
||||
>
|
||||
{{ saveDialogMap[saveDialogStep].buttonText }}
|
||||
</van-button>
|
||||
</div>
|
||||
<van-button round type="primary" block @click="handleConfirmClick">
|
||||
{{ saveDialogMap[saveDialogStep].buttonText }}
|
||||
</van-button>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
@@ -87,7 +125,7 @@ import { ref, watch, onMounted, onUnmounted, computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { showToast } from "vant";
|
||||
import { useResourceStore } from "@/stores/resource";
|
||||
import { formattedFileSize } from "@/utils/index";
|
||||
import { formattedFileSize, throttle } from "@/utils/index";
|
||||
import type { Folder, ResourceItem } from "@/types";
|
||||
import FolderSelect from "@/components/mobile/FolderSelect.vue";
|
||||
import ResourceSelect from "@/components/mobile/ResourceSelect.vue";
|
||||
@@ -104,6 +142,8 @@ const currentFolderId = ref<string | null>(null);
|
||||
const currentFolderPath = ref<Folder[] | null>(null);
|
||||
const saveDialogStep = ref<1 | 2>(1);
|
||||
const currentTab = ref<string>("");
|
||||
const isLoading = ref(false);
|
||||
const listRef = ref<HTMLElement | null>(null);
|
||||
|
||||
// 计算属性
|
||||
const getCurrentFolderName = computed(() => {
|
||||
@@ -152,10 +192,10 @@ const handleSave = async (resource: ResourceItem) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleFolderSelect = (folders: Folder[]) => {
|
||||
const handleFolderSelect = (folders: Folder[] | null) => {
|
||||
if (!currentResource.value) return;
|
||||
currentFolderPath.value = folders;
|
||||
currentFolderId.value = folders[folders.length - 1]?.cid || "0";
|
||||
currentFolderId.value = folders?.[folders.length - 1]?.cid || "0";
|
||||
};
|
||||
|
||||
const handleConfirmClick = async () => {
|
||||
@@ -176,62 +216,138 @@ const handleSaveBtnClick = async () => {
|
||||
await resourceStore.saveResource(currentResource.value, currentFolderId.value);
|
||||
};
|
||||
|
||||
const handleLoadMore = (channelId: string) => {
|
||||
resourceStore.searchResources("", true, channelId);
|
||||
};
|
||||
|
||||
const searchMovieforTag = (tag: string) => {
|
||||
router.push({ path: "/resource", query: { keyword: tag } });
|
||||
};
|
||||
|
||||
// 使用节流包装加载更多函数
|
||||
const throttledLoadMore = throttle((channelId: string) => {
|
||||
resourceStore.searchResources("", true, channelId);
|
||||
}, 200);
|
||||
|
||||
// 滚动加载
|
||||
const doScroll = () => {
|
||||
const { scrollHeight, scrollTop, clientHeight } = document.documentElement;
|
||||
if (clientHeight + scrollTop >= scrollHeight - 50) {
|
||||
handleLoadMore(currentTab.value);
|
||||
const appElement = document.querySelector("#app") as HTMLElement;
|
||||
if (appElement) {
|
||||
const { scrollHeight, scrollTop, clientHeight } = appElement;
|
||||
if (scrollHeight - (clientHeight + scrollTop) <= 200) {
|
||||
throttledLoadMore(currentTab.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
window.addEventListener("scroll", doScroll);
|
||||
const appElement = document.querySelector("#app");
|
||||
if (appElement) {
|
||||
appElement.addEventListener("scroll", doScroll);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("scroll", doScroll);
|
||||
const appElement = document.querySelector("#app");
|
||||
if (appElement) {
|
||||
appElement.removeEventListener("scroll", doScroll);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听标签页切换
|
||||
watch(currentTab, () => {
|
||||
const appElement = document.querySelector("#app");
|
||||
if (appElement) {
|
||||
appElement.scrollTo(0, 0);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.resource-list {
|
||||
&__header {
|
||||
margin-bottom: var(--spacing-base);
|
||||
min-height: 100%;
|
||||
background: var(--van-background);
|
||||
padding-bottom: 20px;
|
||||
|
||||
.header__title {
|
||||
&__header {
|
||||
margin-bottom: 8px;
|
||||
background: var(--theme-other_background);
|
||||
|
||||
:deep(.van-cell) {
|
||||
padding: 12px 16px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.header__icon {
|
||||
font-size: 30px;
|
||||
color: var(--theme-theme);
|
||||
margin-right: 10px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.header__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
font-size: 14px;
|
||||
gap: 6px;
|
||||
|
||||
.title__text {
|
||||
.content__title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.title__time {
|
||||
color: var(--van-gray-6);
|
||||
.content__tip {
|
||||
font-size: 12px;
|
||||
color: var(--van-gray-6);
|
||||
background: var(--van-gray-1);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.header__time {
|
||||
font-size: 12px;
|
||||
color: var(--van-gray-6);
|
||||
line-height: 1.4;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
:deep(.van-tabs__wrap) {
|
||||
background: var(--theme-other_background);
|
||||
}
|
||||
|
||||
:deep(.van-tab) {
|
||||
font-size: 14px;
|
||||
padding: 0 20px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
:deep(.van-tabs__line) {
|
||||
background: var(--theme-theme);
|
||||
}
|
||||
|
||||
:deep(.van-tabs__content) {
|
||||
padding: 8px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
.save-popup {
|
||||
&__container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__header {
|
||||
padding: var(--spacing-lg);
|
||||
border-bottom: 1px solid var(--van-gray-3);
|
||||
flex-shrink: 0;
|
||||
padding: 16px;
|
||||
border-bottom: 0.5px solid var(--van-gray-3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
gap: 8px;
|
||||
padding-right: 40px;
|
||||
|
||||
.header__title {
|
||||
font-size: 16px;
|
||||
@@ -239,57 +355,114 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.header__size {
|
||||
margin-left: auto;
|
||||
font-size: 13px;
|
||||
color: var(--van-gray-6);
|
||||
font-size: 14px;
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
height: calc(100% - 140px);
|
||||
padding: var(--spacing-base);
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background: var(--van-background-2);
|
||||
|
||||
.van-loading {
|
||||
margin: var(--spacing-xl) auto;
|
||||
.content__loading {
|
||||
padding: 32px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: var(--spacing-base);
|
||||
flex-shrink: 0;
|
||||
padding: 12px 16px 16px;
|
||||
background: var(--theme-other_background);
|
||||
border-top: 1px solid var(--van-gray-3);
|
||||
border-top: 0.5px solid var(--van-gray-3);
|
||||
|
||||
.footer__path {
|
||||
margin-bottom: var(--spacing-base);
|
||||
margin: 0 0 12px;
|
||||
|
||||
:deep(.van-cell__title) {
|
||||
flex: none;
|
||||
padding-right: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.van-cell__value) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.path__label {
|
||||
font-size: 14px;
|
||||
margin-right: var(--spacing-xs);
|
||||
color: var(--van-text-color);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.path__value {
|
||||
display: inline-flex;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
background: var(--van-gray-2);
|
||||
border-radius: var(--border-radius-sm);
|
||||
font-size: 14px;
|
||||
max-width: 80%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
gap: 4px;
|
||||
padding: 6px 12px;
|
||||
background: var(--van-gray-1);
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.value__icon {
|
||||
font-size: 16px;
|
||||
color: var(--theme-theme);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.value__text {
|
||||
font-size: 14px;
|
||||
color: var(--van-text-color);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
display: block;
|
||||
|
||||
&--placeholder {
|
||||
color: var(--van-gray-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-cell__value) {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.van-button {
|
||||
height: 40px;
|
||||
font-size: 14px;
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 全局样式优化
|
||||
:deep(.van-cell) {
|
||||
padding: 16px 20px;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-popup) {
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
:deep(.van-overlay) {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user