Files
Docker/uniapp/youjiala/src/components/item-action-sheet/item-action-sheet.vue
T
2026-10-21 15:05:55 +08:00

220 lines
6.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
/**
* 通用条目动作面板:头部文件卡(类型图标 + 名称/元信息)+ 宫格动作 + 取消通栏。
* file-action-sheet 的通用版——动作集由调用方按业务喂进来(共享中心的下载/取消共享等),
* 组件只渲染与派发,不认识 service;视觉与网盘动作面板保持一致,替代系统 showActionSheet。
*/
import type { PropType } from "vue";
import type { FileItem, FileKind } from "@/data/custom/fileBrowser";
import { useLocale } from "uview-pro";
import { useThemeTokens } from "@/composables";
export interface ItemSheetAction {
key: string;
label: string;
icon: string;
/** 危险动作(取消共享/删除):图标与文字标红 */
danger?: boolean;
}
const props = defineProps({
/** 面板是否展开 */
visible: {
type: Boolean,
default: false,
},
/** 目标条目;null 时渲染空面板(正常情况下 visible=true 必有值) */
file: {
type: Object as PropType<FileItem | null>,
default: null,
},
/** 本次条目可用的动作集(调用方按类型派生) */
actions: {
type: Array as PropType<ItemSheetAction[]>,
default: () => [],
},
});
const emit = defineEmits<{
/** 面板关闭(遮罩/取消按钮) */
(e: "close"): void;
/** 点了某个动作,原样带上目标条目 */
(e: "action", key: string, file: FileItem): void;
}>();
const { t } = useLocale();
// 文件夹图形随主题(与 file-action-sheet 同一强调渐变)
const { accentGradient, accentSoft } = useThemeTokens();
// 类型 -> 头部图标与配色,与 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" },
};
function onAction(action: ItemSheetAction): 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="item-action-sheet">
<!-- 头部白卡:类型图标 + 名称/元信息 -->
<view class="item-action-sheet__head">
<view v-if="file && file.kind === 'folder'" class="item-action-sheet__folder">
<view class="item-action-sheet__folder-tab" :style="{ background: accentSoft }" />
<view class="item-action-sheet__folder-body" :style="{ background: accentGradient }" />
</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="item-action-sheet__meta">
<text class="item-action-sheet__name">
{{ file?.name }}
</text>
<text class="item-action-sheet__sub">
{{ file && file.kind !== 'folder' ? `${file.sizeText} · ` : '' }}{{ file?.modifiedAt }}
</text>
</view>
</view>
<!-- 宫格动作:白卡上图标下文字,危险动作标红 -->
<view class="item-action-sheet__grid">
<view v-for="action in actions" :key="action.key" class="item-action-sheet__grid-btn"
@tap="onAction(action)">
<u-icon :name="action.icon" size="44" :color="action.danger ? '#f0413e' : '#1f2329'" />
<text class="item-action-sheet__grid-text"
:style="action.danger ? { color: '#f0413e' } : undefined">
{{ action.label }}
</text>
</view>
</view>
<!-- 取消通栏:退出锚点 -->
<view class="item-action-sheet__cancel" @tap="emit('close')">
<text class="item-action-sheet__cancel-text">
{{ t("FilePage.folderCancel") }}
</text>
</view>
<view class="item-action-sheet__safe" />
</view>
</u-popup>
</template>
<style lang="scss" scoped>
/* 底部抽屉统一风格(对齐 file-action-sheet):浅灰面板底 + 白色圆角卡分块 */
.item-action-sheet {
padding: 32rpx 24rpx 0;
background-color: #f2f3f5;
&__head {
display: flex;
align-items: center;
gap: 24rpx;
margin-bottom: 20rpx;
padding: 28rpx 32rpx;
border-radius: 20rpx;
background-color: #ffffff;
}
/* 文件夹造型与 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;
}
&__folder-body {
position: absolute;
top: 12rpx;
left: 0;
right: 0;
bottom: 0;
border-radius: 10rpx;
}
&__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;
}
&__grid {
display: flex;
gap: 16rpx;
margin-bottom: 20rpx;
}
/* 宫格钮:白卡、上图标下文字居中,动作数 ≤4 时平分一行 */
&__grid-btn {
display: flex;
flex: 1;
flex-direction: column;
align-items: center;
padding: 28rpx 0;
border-radius: 20rpx;
background-color: #ffffff;
}
&__grid-text {
margin-top: 14rpx;
font-size: 24rpx;
color: #1f2329;
}
/* 取消通栏:与内容隔一段灰底收尾,防误点动作区 */
&__cancel {
display: flex;
align-items: center;
justify-content: center;
margin-top: 28rpx;
padding: 26rpx 0;
border-radius: 20rpx;
background-color: #ffffff;
}
&__cancel-text {
font-size: 28rpx;
color: #5c626b;
}
/* 底部留白:贴屏安全区(H5 无 env 时也有 32rpx 呼吸) */
&__safe {
height: calc(32rpx + env(safe-area-inset-bottom));
}
}
</style>