ow
This commit is contained in:
1 parent
bd678b0112
commit
4007871658
8 files changed
+569
-23
No files matched your search
@@ -0,0 +1,258 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* 文件操作面板:条目点「…」弹出的底部抽屉(图1 版式:头部信息 + 宫格动作 + 行式动作)。
|
||||||
|
* 动作集参考旧系统 DmsFileEnquiry 的单选规则(不同类型显示不同动作):
|
||||||
|
* 目录只有 重命名/移动/删除;文件多一个 下载;非目录可 共享给设备内的用户/发布;
|
||||||
|
* 图片=预览、视频=播放、音频=试听(旧系统 preview/play-video/play-audio 三合一按类型取一)。
|
||||||
|
* 组件只渲染与派发 action,业务由页面统一处理;数据契约见 @/data/custom/fileBrowser。
|
||||||
|
*/
|
||||||
|
import type { PropType } from "vue";
|
||||||
|
import type { FileItem, FileKind } from "@/data/custom/fileBrowser";
|
||||||
|
import { useLocale } from "uview-pro";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
interface ActionDef {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/** 面板是否展开 */
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/** 目标条目;null 时渲染空面板(正常情况下 visible=true 必有值) */
|
||||||
|
file: {
|
||||||
|
type: Object as PropType<FileItem | null>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
/** 抽屉关闭(遮罩/下滑) */
|
||||||
|
(e: "close"): void;
|
||||||
|
/** 点了某个动作,原样带上目标条目 */
|
||||||
|
(e: "action", key: string, file: FileItem): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { t } = useLocale();
|
||||||
|
|
||||||
|
// 类型 -> 头部图标与配色,与 file-grid-list 的 kindVisual 同一套
|
||||||
|
const kindVisual: Record<Exclude<FileKind, "folder">, { icon: string; color: string }> = {
|
||||||
|
image: { icon: "photo-fill", color: "#0ea5e9" },
|
||||||
|
video: { icon: "play-right-fill", color: "#8b5cf6" },
|
||||||
|
document: { icon: "file-text-fill", color: "#3b82f6" },
|
||||||
|
audio: { icon: "volume-up", color: "#f59e0b" },
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 宫格区:重命名/移动人人有;下载仅文件;删除收尾(旧系统目录也可删) */
|
||||||
|
const gridActions = computed<ActionDef[]>(() => {
|
||||||
|
const file = props.file;
|
||||||
|
if (!file) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const actions: ActionDef[] = [
|
||||||
|
{ key: "rename", label: t("FilePage.actionRename"), icon: "edit-pen" },
|
||||||
|
{ key: "move", label: t("FilePage.actionMove"), icon: "arrow-right-double" },
|
||||||
|
];
|
||||||
|
if (file.kind !== "folder") {
|
||||||
|
actions.unshift({ key: "download", label: t("FilePage.actionDownload"), icon: "download" });
|
||||||
|
}
|
||||||
|
actions.push({ key: "delete", label: t("FilePage.actionDelete"), icon: "trash" });
|
||||||
|
return actions;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 行式区:媒体预览/播放(按类型取一)+ 共享/发布(仅文件,目录在旧系统被过滤) */
|
||||||
|
const listActions = computed<ActionDef[]>(() => {
|
||||||
|
const file = props.file;
|
||||||
|
if (!file) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const actions: ActionDef[] = [];
|
||||||
|
// 预览/播放命名(均不超 4 字):图片=相册预览、视频=视频播放、音频=音乐播放
|
||||||
|
if (file.kind === "image") {
|
||||||
|
actions.push({ key: "preview", label: t("FilePage.actionAlbumShow"), icon: "eye" });
|
||||||
|
}
|
||||||
|
else if (file.kind === "video") {
|
||||||
|
actions.push({ key: "play", label: t("FilePage.actionVideoPlay"), icon: "play-circle-fill" });
|
||||||
|
}
|
||||||
|
else if (file.kind === "audio") {
|
||||||
|
actions.push({ key: "play", label: t("FilePage.actionAudioPlay"), icon: "volume" });
|
||||||
|
}
|
||||||
|
if (file.kind !== "folder") {
|
||||||
|
actions.push({ key: "share", label: t("FilePage.actionShare"), icon: "share-fill" });
|
||||||
|
// 发布命名:媒体类型统一「发布专辑」(不超 4 字),文档仍「发布」
|
||||||
|
const isMedia = file.kind === "image" || file.kind === "video" || file.kind === "audio";
|
||||||
|
actions.push({ key: "publish", label: isMedia ? t("FilePage.actionPublishAlbum") : t("FilePage.actionPublish"), icon: "volume-fill" });
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
});
|
||||||
|
|
||||||
|
function onAction(action: ActionDef): void {
|
||||||
|
if (props.file) {
|
||||||
|
emit("action", action.key, props.file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- u-popup 只认 modelValue(v-model),绑 :show 会静默弹不出 -->
|
||||||
|
<u-popup :model-value="visible" mode="bottom" round="24" @close="emit('close')">
|
||||||
|
<view class="file-action-sheet">
|
||||||
|
<!-- 头部:类型图标 + 名称/元信息 + 详情 -->
|
||||||
|
<view class="file-action-sheet__head">
|
||||||
|
<view v-if="file && file.kind === 'folder'" class="file-action-sheet__folder">
|
||||||
|
<view class="file-action-sheet__folder-tab" />
|
||||||
|
<view class="file-action-sheet__folder-body" />
|
||||||
|
</view>
|
||||||
|
<u-icon v-else-if="file" :name="kindVisual[file.kind as Exclude<FileKind, 'folder'>].icon" size="64"
|
||||||
|
:color="kindVisual[file.kind as Exclude<FileKind, 'folder'>].color" />
|
||||||
|
<view class="file-action-sheet__meta">
|
||||||
|
<text class="file-action-sheet__name">
|
||||||
|
{{ file?.name }}
|
||||||
|
</text>
|
||||||
|
<text class="file-action-sheet__sub">
|
||||||
|
{{ file && file.kind !== 'folder' ? `${file.sizeText} · ` : '' }}{{ file?.modifiedAt }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="file-action-sheet__info" @tap="onAction({ key: 'detail', label: '', icon: '' })">
|
||||||
|
<u-icon name="info-circle" size="40" color="#8f959e" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 宫格动作(图1 第一排大按钮) -->
|
||||||
|
<view class="file-action-sheet__grid">
|
||||||
|
<view v-for="action in gridActions" :key="action.key" class="file-action-sheet__grid-btn"
|
||||||
|
@tap="onAction(action)">
|
||||||
|
<u-icon :name="action.icon" size="44" color="#1f2329" />
|
||||||
|
<text class="file-action-sheet__grid-text">
|
||||||
|
{{ action.label }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 行式动作(图1 下方整行菜单) -->
|
||||||
|
<view v-for="action in listActions" :key="action.key" class="file-action-sheet__row"
|
||||||
|
@tap="onAction(action)">
|
||||||
|
<text class="file-action-sheet__row-text">
|
||||||
|
{{ action.label }}
|
||||||
|
</text>
|
||||||
|
<u-icon :name="action.icon" size="38" color="#1f2329" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="file-action-sheet__safe" />
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.file-action-sheet {
|
||||||
|
padding: 40rpx 32rpx 0;
|
||||||
|
background-color: #ffffff;
|
||||||
|
|
||||||
|
&__head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24rpx;
|
||||||
|
padding: 0 8rpx 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文件夹造型与 file-grid-list 同源:主体 + 左上页签 */
|
||||||
|
&__folder {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 84rpx;
|
||||||
|
height: 68rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__folder-tab {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 36rpx;
|
||||||
|
height: 16rpx;
|
||||||
|
border-radius: 8rpx 8rpx 0 0;
|
||||||
|
background-color: #6cb7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__folder-body {
|
||||||
|
position: absolute;
|
||||||
|
top: 12rpx;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
background: linear-gradient(135deg, #7cc0ff, #3d8bfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8rpx;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2329;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__sub {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #8f959e;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__grid {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__grid-btn {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
padding: 28rpx 0;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background-color: #f5f6f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__grid-text {
|
||||||
|
margin-top: 14rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #1f2329;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
padding: 28rpx 32rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background-color: #f5f6f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__row-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #1f2329;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部留白:贴屏安全区(H5 无 env 时也有 32rpx 呼吸) */
|
||||||
|
&__safe {
|
||||||
|
height: calc(32rpx + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -78,7 +78,8 @@ function onCardTap(file: FileItem): void {
|
|||||||
</view>
|
</view>
|
||||||
<view v-for="file in files" :key="file.id" class="fgl-card"
|
<view v-for="file in files" :key="file.id" class="fgl-card"
|
||||||
:class="{ 'fgl-card--selected': selectMode && isSelected(file) }">
|
:class="{ 'fgl-card--selected': selectMode && isSelected(file) }">
|
||||||
<view class="fgl-card__more" @tap.stop="emit('more', file)">
|
<!-- 「更多」与多选勾选圈互斥:多选模式下只留 check 圆,不再同屏两个操作图标 -->
|
||||||
|
<view v-if="!selectMode" class="fgl-card__more" @tap.stop="emit('more', file)">
|
||||||
<u-icon name="more-dot-fill" size="30" color="#b8bcc4" />
|
<u-icon name="more-dot-fill" size="30" color="#b8bcc4" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|||||||
@@ -57,8 +57,10 @@ const emit = defineEmits<{
|
|||||||
<view class="file-tab-filter__tool" @tap="emit('toggle-view')">
|
<view class="file-tab-filter__tool" @tap="emit('toggle-view')">
|
||||||
<u-icon :name="viewMode === 'grid' ? 'list' : 'grid-fill'" size="36" color="#1f2329" />
|
<u-icon :name="viewMode === 'grid' ? 'list' : 'grid-fill'" size="36" color="#1f2329" />
|
||||||
</view>
|
</view>
|
||||||
|
<!-- 多选开关:两个图标互切——未进入给勾选圆,进入后给关闭圆(高亮蓝) -->
|
||||||
<view class="file-tab-filter__tool" @tap="emit('toggle-select')">
|
<view class="file-tab-filter__tool" @tap="emit('toggle-select')">
|
||||||
<u-icon name="checkmark-circle" size="36" :color="selectMode ? '#2b8fff' : '#1f2329'" />
|
<u-icon :name="selectMode ? 'close-circle' : 'checkmark-circle'" size="36"
|
||||||
|
:color="selectMode ? '#2b8fff' : '#1f2329'" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
/**
|
||||||
* 快捷功能入口:横向滚动的图标按钮组(最近访问 / 收藏 / 已下载 / 团队文件 / 共享文件)。
|
* 快捷功能入口:横向滚动的图标按钮组(有家相册 / 音乐厅 / 影视厅 / 共享中心 / 回收站)。
|
||||||
* 单项内容(图标名 + 渐变色 + 文案)全部由页面通过 items 传入,组件只渲染与派发点击。
|
* 单项内容(图标名 + 渐变色 + 文案)全部由页面通过 items 传入,组件只渲染与派发点击。
|
||||||
*/
|
*/
|
||||||
import type { PropType } from "vue";
|
import type { PropType } from "vue";
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ function onClear(): void {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/* 分区卡片风格:外层透明露渐变底,胶囊内部浅白色、留描边定形 */
|
/* 分区卡片风格:外层透明露渐变底,胶囊内部浅白色、留描边定形 */
|
||||||
.search-input {
|
.search-input {
|
||||||
padding: 2rpx 24rpx 0;
|
padding: 20rpx 24rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input__box {
|
.search-input__box {
|
||||||
|
|||||||
@@ -27,11 +27,11 @@
|
|||||||
"FilePage": {
|
"FilePage": {
|
||||||
"title": "Files",
|
"title": "Files",
|
||||||
"searchPlaceholder": "Search",
|
"searchPlaceholder": "Search",
|
||||||
"recent": "Recent",
|
"album": "Family Album",
|
||||||
"favorite": "Favorites",
|
"musicHall": "Music Hall",
|
||||||
"downloaded": "Downloaded",
|
"videoHall": "Video Hall",
|
||||||
"team": "Team Files",
|
"shareCenter": "Share Center",
|
||||||
"shared": "Shared",
|
"trash": "Trash",
|
||||||
"catAll": "All",
|
"catAll": "All",
|
||||||
"catImage": "Images",
|
"catImage": "Images",
|
||||||
"catVideo": "Videos",
|
"catVideo": "Videos",
|
||||||
@@ -42,6 +42,23 @@
|
|||||||
"sortTimeAsc": "Oldest first",
|
"sortTimeAsc": "Oldest first",
|
||||||
"sortName": "Sort by name",
|
"sortName": "Sort by name",
|
||||||
"selectedCount": "{count} selected",
|
"selectedCount": "{count} selected",
|
||||||
|
"selectAll": "Select All",
|
||||||
|
"unselectAll": "Unselect All",
|
||||||
|
"actionRename": "Rename",
|
||||||
|
"actionMove": "Move",
|
||||||
|
"actionDownload": "Download",
|
||||||
|
"actionDelete": "Delete",
|
||||||
|
"actionShare": "Share to Family",
|
||||||
|
"actionShareShort": "Share",
|
||||||
|
"actionPublish": "Publish",
|
||||||
|
"actionPublishAlbum": "Publish Album",
|
||||||
|
"actionPreview": "Preview",
|
||||||
|
"actionAlbumShow": "Album Preview",
|
||||||
|
"actionVideoPlay": "Play Video",
|
||||||
|
"actionAudioPlay": "Play Music",
|
||||||
|
"actionDetail": "Details",
|
||||||
|
"actionCancel": "Cancel",
|
||||||
|
"actionDone": "Done",
|
||||||
"todoTip": "Coming soon",
|
"todoTip": "Coming soon",
|
||||||
"todayLabel": "Today",
|
"todayLabel": "Today",
|
||||||
"myCloud": "My Cloud",
|
"myCloud": "My Cloud",
|
||||||
|
|||||||
@@ -27,11 +27,11 @@
|
|||||||
"FilePage": {
|
"FilePage": {
|
||||||
"title": "文件",
|
"title": "文件",
|
||||||
"searchPlaceholder": "搜索",
|
"searchPlaceholder": "搜索",
|
||||||
"recent": "最近访问",
|
"album": "有家相册",
|
||||||
"favorite": "收藏",
|
"musicHall": "音乐厅",
|
||||||
"downloaded": "已下载",
|
"videoHall": "影视厅",
|
||||||
"team": "团队文件",
|
"shareCenter": "共享中心",
|
||||||
"shared": "共享文件",
|
"trash": "回收站",
|
||||||
"catAll": "全部",
|
"catAll": "全部",
|
||||||
"catImage": "图片",
|
"catImage": "图片",
|
||||||
"catVideo": "视频",
|
"catVideo": "视频",
|
||||||
@@ -42,6 +42,23 @@
|
|||||||
"sortTimeAsc": "按修改时间升序",
|
"sortTimeAsc": "按修改时间升序",
|
||||||
"sortName": "按名称排序",
|
"sortName": "按名称排序",
|
||||||
"selectedCount": "已选 {count} 项",
|
"selectedCount": "已选 {count} 项",
|
||||||
|
"selectAll": "全选",
|
||||||
|
"unselectAll": "取消全选",
|
||||||
|
"actionRename": "重命名",
|
||||||
|
"actionMove": "移动",
|
||||||
|
"actionDownload": "下载",
|
||||||
|
"actionDelete": "删除",
|
||||||
|
"actionShare": "共享家人",
|
||||||
|
"actionShareShort": "共享",
|
||||||
|
"actionPublish": "发布",
|
||||||
|
"actionPublishAlbum": "发布专辑",
|
||||||
|
"actionPreview": "预览",
|
||||||
|
"actionAlbumShow": "相册预览",
|
||||||
|
"actionVideoPlay": "视频播放",
|
||||||
|
"actionAudioPlay": "音乐播放",
|
||||||
|
"actionDetail": "详情",
|
||||||
|
"actionCancel": "取消",
|
||||||
|
"actionDone": "完成",
|
||||||
"todoTip": "功能开发中,敬请期待",
|
"todoTip": "功能开发中,敬请期待",
|
||||||
"todayLabel": "今天",
|
"todayLabel": "今天",
|
||||||
"myCloud": "我的云盘",
|
"myCloud": "我的云盘",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* 目录导航:点文件夹下钻;子目录态隐藏底部 tabbar,返回箭头/面包屑退层级。
|
* 目录导航:点文件夹下钻;子目录态隐藏底部 tabbar,返回箭头/面包屑退层级。
|
||||||
* 权限暂用 UserOperation(见 routes.ts 的 PAGE_PERMISSIONS);useAuthGuard 兜冷启动直达。
|
* 权限暂用 UserOperation(见 routes.ts 的 PAGE_PERMISSIONS);useAuthGuard 兜冷启动直达。
|
||||||
*/
|
*/
|
||||||
import type { FileCategory, FileItem, FileTabItem, QuickEntryItem } from "@/data/custom/fileBrowser";
|
import type { FileCategory, FileItem, FileKind, FileTabItem, QuickEntryItem } from "@/data/custom/fileBrowser";
|
||||||
import { useLocale } from "uview-pro";
|
import { useLocale } from "uview-pro";
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { useAuthGuard, useDmsFiles, useThemeGradient } from "@/composables";
|
import { useAuthGuard, useDmsFiles, useThemeGradient } from "@/composables";
|
||||||
@@ -60,11 +60,11 @@ watch(atRoot, (root) => {
|
|||||||
const navTitle = computed(() => pathStack.value[pathStack.value.length - 1]?.displayName || t("FilePage.title"));
|
const navTitle = computed(() => pathStack.value[pathStack.value.length - 1]?.displayName || t("FilePage.title"));
|
||||||
|
|
||||||
const quickEntries = computed<QuickEntryItem[]>(() => [
|
const quickEntries = computed<QuickEntryItem[]>(() => [
|
||||||
{ key: "recent", label: t("FilePage.recent"), icon: "clock", bgFrom: "#5ee7c1", bgTo: "#22c79f" },
|
{ key: "album", label: t("FilePage.album"), icon: "photo", bgFrom: "#ff9a8b", bgTo: "#f0524a" },
|
||||||
{ key: "favorite", label: t("FilePage.favorite"), icon: "star-fill", bgFrom: "#ff8a8a", bgTo: "#f0413e" },
|
{ key: "musicHall", label: t("FilePage.musicHall"), icon: "volume", bgFrom: "#a78bfa", bgTo: "#6d43e0" },
|
||||||
{ key: "downloaded", label: t("FilePage.downloaded"), icon: "arrow-down", bgFrom: "#a78bfa", bgTo: "#6d43e0" },
|
{ key: "videoHall", label: t("FilePage.videoHall"), icon: "play-circle-fill", bgFrom: "#63b3ff", bgTo: "#1f7ae0" },
|
||||||
{ key: "team", label: t("FilePage.team"), icon: "account", bgFrom: "#63b3ff", bgTo: "#1f7ae0" },
|
{ key: "shareCenter", label: t("FilePage.shareCenter"), icon: "share-fill", bgFrom: "#5ec2ff", bgTo: "#2b8fff" },
|
||||||
{ key: "shared", label: t("FilePage.shared"), icon: "share-fill", bgFrom: "#5ec2ff", bgTo: "#2b8fff" },
|
{ key: "trash", label: t("FilePage.trash"), icon: "trash", bgFrom: "#b6bcc6", bgTo: "#7d848f" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const tabs = computed<FileTabItem[]>(() => [
|
const tabs = computed<FileTabItem[]>(() => [
|
||||||
@@ -179,6 +179,128 @@ function onToggleSelect(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 全选:勾满当前过滤后可见的全部条目(被分类/关键字挡住的看不见就不选) */
|
||||||
|
function onSelectAll(): void {
|
||||||
|
selectedIds.value = visibleFiles.value.map(f => f.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 取消全选:只清勾选,不退出多选模式 */
|
||||||
|
function onClearSelect(): void {
|
||||||
|
selectedIds.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 多选底部操作栏(常驻非模态,不挡继续勾选)=====
|
||||||
|
// 动作集照旧系统 OperatorStation 的多选子集:共享/发布要求无目录且同媒体类型,
|
||||||
|
// 删除恒可用;重命名/移动在旧系统就是 isSingleItem 专属,多选不显示。
|
||||||
|
|
||||||
|
interface MultiAction {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
icon: string;
|
||||||
|
danger?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选中条目从全量 files 取:切换分类后被挡出可见集的已选项也要参与判定 */
|
||||||
|
const selectedFiles = computed(() => files.value.filter(f => selectedIds.value.includes(f.id)));
|
||||||
|
|
||||||
|
/** 多选时是否全部为同一媒体类型(图片/视频/音频),文档与目录不算 */
|
||||||
|
const isSameMediaType = computed(() => {
|
||||||
|
const kinds = selectedFiles.value.map(f => f.kind);
|
||||||
|
if (kinds.length < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const [first] = kinds;
|
||||||
|
return (first === "image" || first === "video" || first === "audio") && kinds.every(k => k === first);
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 旧系统共享/发布条件:!isDirectorySelected && (isSingleItem || isSameMediaTypeSelected) */
|
||||||
|
const canSharePublish = computed(() => {
|
||||||
|
const picked = selectedFiles.value;
|
||||||
|
if (!picked.length || picked.some(f => f.kind === "folder")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return picked.length === 1 || isSameMediaType.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 选中集的统一媒体类型(动态命名用):类型不一致或含目录时为 null */
|
||||||
|
const pickedKind = computed<FileKind | null>(() => {
|
||||||
|
const picked = selectedFiles.value;
|
||||||
|
if (!picked.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const [first] = picked;
|
||||||
|
return picked.every(f => f.kind === first.kind) ? first.kind : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 旧系统 publishTooltip 简化口径:媒体类型统一「发布专辑」(按钮不超 4 字),文档仍「发布」 */
|
||||||
|
const publishLabel = computed(() => {
|
||||||
|
const kind = pickedKind.value;
|
||||||
|
return kind === "image" || kind === "video" || kind === "audio"
|
||||||
|
? t("FilePage.actionPublishAlbum")
|
||||||
|
: t("FilePage.actionPublish");
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 旧系统 previewTooltip 简化口径:图片=相册预览、视频=视频播放、音频=音乐播放(均不超 4 字) */
|
||||||
|
const previewLabel = computed(() => {
|
||||||
|
switch (pickedKind.value) {
|
||||||
|
case "image": return t("FilePage.actionAlbumShow");
|
||||||
|
case "video": return t("FilePage.actionVideoPlay");
|
||||||
|
case "audio": return t("FilePage.actionAudioPlay");
|
||||||
|
default: return t("FilePage.actionPreview");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const previewIcon = computed(() => {
|
||||||
|
if (pickedKind.value === "video") {
|
||||||
|
return "play-circle-fill";
|
||||||
|
}
|
||||||
|
return pickedKind.value === "audio" ? "volume" : "eye";
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 选中含文件即可下载(旧系统 onDownloadSelected 就是滤掉目录逐个循环下载) */
|
||||||
|
const hasFileSelected = computed(() => selectedFiles.value.some(f => f.kind !== "folder"));
|
||||||
|
|
||||||
|
/** 排1:下载(含文件即有)+ 共享/发布(同媒体类型且无目录)+ 删除恒在 */
|
||||||
|
const multiActionsTop = computed<MultiAction[]>(() => {
|
||||||
|
const actions: MultiAction[] = [];
|
||||||
|
if (hasFileSelected.value) {
|
||||||
|
actions.push({ key: "download", label: t("FilePage.actionDownload"), icon: "download" });
|
||||||
|
}
|
||||||
|
if (canSharePublish.value) {
|
||||||
|
actions.push({ key: "share", label: t("FilePage.actionShareShort"), icon: "share-fill" });
|
||||||
|
actions.push({ key: "publish", label: publishLabel.value, icon: "volume-fill" });
|
||||||
|
}
|
||||||
|
actions.push({ key: "delete", label: t("FilePage.actionDelete"), icon: "trash", danger: true });
|
||||||
|
return actions;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 排2:预览——无目录且单选或同媒体类型(多图多选即相册连览);重命名仅单选非目录;
|
||||||
|
* 移动有选即在(目录/混选都能整批挪);取消恒在 */
|
||||||
|
const multiActionsBottom = computed<MultiAction[]>(() => {
|
||||||
|
const actions: MultiAction[] = [];
|
||||||
|
const picked = selectedFiles.value;
|
||||||
|
if (picked.length) {
|
||||||
|
if (canSharePublish.value) {
|
||||||
|
actions.push({ key: "preview", label: previewLabel.value, icon: previewIcon.value });
|
||||||
|
}
|
||||||
|
if (picked.length === 1 && picked[0].kind !== "folder") {
|
||||||
|
actions.push({ key: "rename", label: t("FilePage.actionRename"), icon: "edit-pen" });
|
||||||
|
}
|
||||||
|
actions.push({ key: "move", label: t("FilePage.actionMove"), icon: "arrow-right-double" });
|
||||||
|
}
|
||||||
|
actions.push({ key: "cancel", label: t("FilePage.actionCancel"), icon: "close" });
|
||||||
|
return actions;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 多选动作:取消直接退出多选,其余待真实接口接入后在此分流 */
|
||||||
|
function onMultiAction(key: string): void {
|
||||||
|
if (key === "cancel") {
|
||||||
|
onToggleSelect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast(`${key} · ${t("FilePage.todoTip")}`);
|
||||||
|
}
|
||||||
|
|
||||||
function onToggleSelectItem(id: number): void {
|
function onToggleSelectItem(id: number): void {
|
||||||
const index = selectedIds.value.indexOf(id);
|
const index = selectedIds.value.indexOf(id);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
@@ -199,8 +321,17 @@ async function onOpen(file: FileItem): Promise<void> {
|
|||||||
toast(`${file.name} · ${t("FilePage.todoTip")}`);
|
toast(`${file.name} · ${t("FilePage.todoTip")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 「…」单选操作面板:记目标条目即展开(面板内动作按 kind 派生,见 file-action-sheet)
|
||||||
|
const actionFile = ref<FileItem | null>(null);
|
||||||
|
|
||||||
function onItemMore(file: FileItem): void {
|
function onItemMore(file: FileItem): void {
|
||||||
toast(`${file.name} · ${t("FilePage.todoTip")}`);
|
actionFile.value = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 面板动作回调:真实接口逐个接入后在此分流,当前统一占位提示并收起面板 */
|
||||||
|
function onSheetAction(key: string, file: FileItem): void {
|
||||||
|
actionFile.value = null;
|
||||||
|
toast(`${file.name} · ${key} · ${t("FilePage.todoTip")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAdd(): void {
|
function onAdd(): void {
|
||||||
@@ -209,7 +340,9 @@ function onAdd(): void {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<app-page :hide-nav="true" :show-tabbar="atRoot">
|
<!-- 操作面板/多选操作栏展开时收起 tabbar:tabbar 在页面 fixed 层叠上下文之外,
|
||||||
|
弹层 z-index 再高也压不住它,直接隐藏最干净(主流云盘同款交互) -->
|
||||||
|
<app-page :hide-nav="true" :show-tabbar="atRoot && !actionFile && !selectMode">
|
||||||
<view class="file-page" :style="{ background: themeBackground }">
|
<view class="file-page" :style="{ background: themeBackground }">
|
||||||
<!-- 上半屏整体固定,只有文件列表所在的 scroll-view 内部滚动(整页不滚) -->
|
<!-- 上半屏整体固定,只有文件列表所在的 scroll-view 内部滚动(整页不滚) -->
|
||||||
<view class="file-page__head">
|
<view class="file-page__head">
|
||||||
@@ -232,6 +365,20 @@ function onAdd(): void {
|
|||||||
<text class="file-page__selection-text">
|
<text class="file-page__selection-text">
|
||||||
{{ t("FilePage.selectedCount", { count: selectedIds.length }) }}
|
{{ t("FilePage.selectedCount", { count: selectedIds.length }) }}
|
||||||
</text>
|
</text>
|
||||||
|
<view class="file-page__selection-actions">
|
||||||
|
<view class="file-page__selection-btn file-page__selection-btn--primary" @click="onSelectAll">
|
||||||
|
<u-icon name="checkmark-circle-fill" size="28" color="#2b8fff" />
|
||||||
|
<text class="file-page__selection-btn-text">
|
||||||
|
{{ t("FilePage.selectAll") }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="file-page__selection-btn" @click="onClearSelect">
|
||||||
|
<u-icon name="minus-circle-fill" size="28" color="#5c626b" />
|
||||||
|
<text class="file-page__selection-btn-text">
|
||||||
|
{{ t("FilePage.unselectAll") }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 滚动窗口外层包装:margin/圆角/白底/裁剪放这层(scroll-view 根节点固定
|
<!-- 滚动窗口外层包装:margin/圆角/白底/裁剪放这层(scroll-view 根节点固定
|
||||||
@@ -261,7 +408,32 @@ function onAdd(): void {
|
|||||||
<view class="file-page__tabbar-space" :class="{ 'file-page__tabbar-space--slim': !atRoot }" />
|
<view class="file-page__tabbar-space" :class="{ 'file-page__tabbar-space--slim': !atRoot }" />
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
<float-add-btn @click="onAdd" />
|
<!-- 多选态加号让位底部操作栏,避免两个悬浮物打架 -->
|
||||||
|
<float-add-btn v-if="!selectMode" @click="onAdd" />
|
||||||
|
<file-action-sheet :visible="!!actionFile" :file="actionFile" @close="actionFile = null"
|
||||||
|
@action="onSheetAction" />
|
||||||
|
<!-- 多选底部操作栏:常驻非模态(无遮罩),勾选与动作可交替进行;
|
||||||
|
动作集是旧系统多选子集——共享/发布按同媒体类型条件,重命名/移动仅单选 -->
|
||||||
|
<view v-if="selectMode" class="file-page__actions">
|
||||||
|
<view class="file-page__actions-row">
|
||||||
|
<view v-for="action in multiActionsTop" :key="action.key" class="file-page__action-btn"
|
||||||
|
:class="{ 'file-page__action-btn--danger': action.danger }" @click="onMultiAction(action.key)">
|
||||||
|
<u-icon :name="action.icon" size="32" :color="action.danger ? '#f0413e' : '#1f2329'" />
|
||||||
|
<text class="file-page__action-text">
|
||||||
|
{{ action.label }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="file-page__actions-row">
|
||||||
|
<view v-for="action in multiActionsBottom" :key="action.key" class="file-page__action-btn"
|
||||||
|
@click="onMultiAction(action.key)">
|
||||||
|
<u-icon :name="action.icon" size="32" color="#1f2329" />
|
||||||
|
<text class="file-page__action-text">
|
||||||
|
{{ action.label }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</app-page>
|
</app-page>
|
||||||
</template>
|
</template>
|
||||||
@@ -322,6 +494,9 @@ function onAdd(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-page__selection {
|
.file-page__selection {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
margin: 20rpx 24rpx 0;
|
margin: 20rpx 24rpx 0;
|
||||||
padding: 16rpx 32rpx;
|
padding: 16rpx 32rpx;
|
||||||
border-radius: 24rpx;
|
border-radius: 24rpx;
|
||||||
@@ -333,6 +508,82 @@ function onAdd(): void {
|
|||||||
color: #2b8fff;
|
color: #2b8fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 全选 / 取消全选:贴右并排的图标胶囊按钮 */
|
||||||
|
.file-page__selection-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-page__selection-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
padding: 10rpx 24rpx;
|
||||||
|
border: 2rpx solid #e6e7ea;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
|
||||||
|
&--primary {
|
||||||
|
border-color: #bcdcff;
|
||||||
|
background-color: #eaf4ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-page__selection-btn-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #5c626b;
|
||||||
|
|
||||||
|
.file-page__selection-btn--primary & {
|
||||||
|
color: #2b8fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 多选底部操作栏:贴底常驻白底板,两排等分胶囊按钮 */
|
||||||
|
.file-page__actions {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 9;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16rpx;
|
||||||
|
padding: 24rpx 24rpx calc(24rpx + env(safe-area-inset-bottom));
|
||||||
|
border-radius: 32rpx 32rpx 0 0;
|
||||||
|
background-color: #ffffff;
|
||||||
|
box-shadow: 0 -4rpx 24rpx rgba(31, 35, 41, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-page__actions-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-page__action-btn {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10rpx;
|
||||||
|
padding: 22rpx 0;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background-color: #f5f6f8;
|
||||||
|
|
||||||
|
&--danger {
|
||||||
|
background-color: #fdecec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-page__action-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #1f2329;
|
||||||
|
|
||||||
|
.file-page__action-btn--danger & {
|
||||||
|
color: #f0413e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.file-page__state {
|
.file-page__state {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in new issue
Block a user