This commit is contained in:
owenchen committed 2026-10-15 13:08:28 +08:00
1 parent ceb3e7beec
commit f6f3735ab3
29 files changed
+8376 -140

No files matched your search

@@ -0,0 +1,77 @@
<script setup lang="ts">
/**
* 音乐厅页(对齐旧 WebVue views/master/PreviewAudio.vue):
* 黑胶唱机播放器外壳。入参经 query 传:mode=list&key=<目录路径> 播放当前目录音频;
* 或 key=<专辑编号> 播放某音乐专辑;无 key 走默认专辑。取数逻辑见 composables/useMediaShow。
* 隐藏控制区时(v-model:controls)本壳顺带隐掉导航/系统栏做整页沉浸(H5 元素全屏在组件内处理)。
*/
import type { VinylTrack } from "@/components/vinyl-audio-player/vinyl-audio-player.vue";
import { onLoad } from "@dcloudio/uni-app";
import { ref } from "vue";
import { useMediaShow } from "@/composables";
const { loadTracks } = useMediaShow();
const tracks = ref<VinylTrack[]>([]);
const controls = ref(true);
const loading = ref(true);
const errorText = ref("");
function closeMe(): void {
if (getCurrentPages().length > 1) {
uni.navigateBack();
return;
}
uni.switchTab({
url: "/pages/tab/media",
fail: () => uni.reLaunch({ url: "/pages/tab/media" }),
});
}
onLoad(async (query) => {
const mode = String(query?.mode || "");
const key = decodeURIComponent(String(query?.key || ""));
try {
const items = await loadTracks(mode, key);
tracks.value = items.map(t => ({
name: t.name || t.title || "",
author: "有家相册",
url: t.url,
cover: t.pic || undefined,
lrc: t.lrc || undefined,
}));
} catch (error: any) {
errorText.value = error?.message || "加载失败";
} finally {
loading.value = false;
}
});
</script>
<template>
<app-page :hide-nav="true" :show-tabbar="false">
<view v-if="loading" class="show-loading">
<text class="show-loading__text">加载中…</text>
</view>
<view v-else-if="errorText" class="show-loading" @tap="closeMe">
<text class="show-loading__text">{{ errorText }}</text>
</view>
<vinyl-audio-player v-else v-model:controls="controls" :tracks="tracks" @close="closeMe" />
</app-page>
</template>
<style lang="scss" scoped>
.show-loading {
display: flex;
position: fixed;
inset: 0;
align-items: center;
justify-content: center;
background: #0b0d10;
}
.show-loading__text {
font-size: 28rpx;
color: #9aa0a6;
}
</style>
@@ -0,0 +1,70 @@
<script setup lang="ts">
/**
* 相册展览页(对齐旧 WebVue views/master/Preview.vue):
* 全屏幻灯播放器外壳。入参经 query 传:mode=list&key=<目录路径> 展览当前目录图片;
* 或 key=<专辑编号> 展览某相册;无 key 走默认相册。取数逻辑见 composables/useMediaShow。
* 隐藏控制区时(v-model:controls)本壳顺带隐掉导航/系统栏做整页沉浸(H5 元素全屏在组件内处理)。
*/
import type { SlideItem } from "@/composables";
import { onLoad } from "@dcloudio/uni-app";
import { ref } from "vue";
import { useMediaShow } from "@/composables";
const { loadPhotos } = useMediaShow();
const items = ref<SlideItem[]>([]);
const controls = ref(true);
const loading = ref(true);
const errorText = ref("");
function closeMe(): void {
if (getCurrentPages().length > 1) {
uni.navigateBack();
return;
}
uni.switchTab({
url: "/pages/tab/media",
fail: () => uni.reLaunch({ url: "/pages/tab/media" }),
});
}
onLoad(async (query) => {
const mode = String(query?.mode || "");
const key = decodeURIComponent(String(query?.key || ""));
try {
items.value = await loadPhotos(mode, key);
} catch (error: any) {
errorText.value = error?.message || "加载失败";
} finally {
loading.value = false;
}
});
</script>
<template>
<app-page :hide-nav="true" :show-tabbar="false">
<view v-if="loading" class="show-loading">
<text class="show-loading__text">加载中…</text>
</view>
<view v-else-if="errorText" class="show-loading" @tap="closeMe">
<text class="show-loading__text">{{ errorText }}</text>
</view>
<photo-slide-player v-else v-model:controls="controls" :items="items" @close="closeMe" />
</app-page>
</template>
<style lang="scss" scoped>
.show-loading {
display: flex;
position: fixed;
inset: 0;
align-items: center;
justify-content: center;
background: #000;
}
.show-loading__text {
font-size: 28rpx;
color: #9aa0a6;
}
</style>
@@ -0,0 +1,76 @@
<script setup lang="ts">
/**
* 影视厅页(对齐旧 WebVue views/master/PreviewVideo.vue):
* 自绘控制播放器外壳。入参经 query 传:mode=list&key=<目录路径> 播放当前目录视频;
* 或 key=<专辑编号> 播放某影视专辑;无 key 走默认专辑。取数逻辑见 composables/useMediaShow。
* 隐藏控制区时(v-model:controls)本壳顺带隐掉导航/系统栏做整页沉浸。
*/
import type { VideoSource } from "@/components/video-player/video-player.vue";
import { onLoad } from "@dcloudio/uni-app";
import { ref } from "vue";
import { useMediaShow } from "@/composables";
const { loadVideos } = useMediaShow();
const videos = ref<VideoSource[]>([]);
const controls = ref(true);
const loading = ref(true);
const errorText = ref("");
function closeMe(): void {
if (getCurrentPages().length > 1) {
uni.navigateBack();
return;
}
uni.switchTab({
url: "/pages/tab/media",
fail: () => uni.reLaunch({ url: "/pages/tab/media" }),
});
}
onLoad(async (query) => {
const mode = String(query?.mode || "");
const key = decodeURIComponent(String(query?.key || ""));
try {
const items = await loadVideos(mode, key);
videos.value = items.map(v => ({
name: v.name || v.title || "",
url: v.url,
m3u8: v.m3u8,
pic: v.pic,
}));
} catch (error: any) {
errorText.value = error?.message || "加载失败";
} finally {
loading.value = false;
}
});
</script>
<template>
<app-page :hide-nav="true" :show-tabbar="false">
<view v-if="loading" class="show-loading">
<text class="show-loading__text">加载中…</text>
</view>
<view v-else-if="errorText" class="show-loading" @tap="closeMe">
<text class="show-loading__text">{{ errorText }}</text>
</view>
<video-player v-else v-model:controls="controls" :videos="videos" @close="closeMe" />
</app-page>
</template>
<style lang="scss" scoped>
.show-loading {
display: flex;
position: fixed;
inset: 0;
align-items: center;
justify-content: center;
background: #0b0d10;
}
.show-loading__text {
font-size: 28rpx;
color: #9aa0a6;
}
</style>
@@ -0,0 +1,485 @@
<script setup lang="ts">
/**
* 共享大厅页(短地址 /share-hall,从文件页快捷入口「共享大厅」跳入):
* 两态结构——顶层大厅只列家人群组(每张卡=一个共享目录,无任何写操作),
* 点群组进组后才出现搜索/分类/排序/新建文件夹(旧系统 FamilyGroupShare 语义:
* 大厅顶层不建目录,进组才挂 CreateFamilyDmsFolder)。
* 组内条目动作按类型分流:目录下钻(无权限提示)、图片预览、视频/音频播放、
* 下载、取消共享(DeleteFamilyDmsFolder 只摘映射不动原文件)。
* 数据链在 composables/useShareHall;权限沿用 DmsFile(routes.ts),useAuthGuard 兜冷启动直达。
*/
import type { FileCategory, FileItem, FileTabItem } from "@/data/custom/fileBrowser";
import { useLocale } from "uview-pro";
import { computed, ref } from "vue";
import { useAuthGuard, useShareHall, useThemeGradient } from "@/composables";
import { pageUrl } from "@/router/routes";
import { fetchFileBytes, saveFileBytes, toPlayableUrl } from "@/utils/dmsBytes";
import { clearPlayback, setPlayback } from "@/utils/playbackBus";
const { t } = useLocale();
// 页面底渐变跟随主题(与文件页同一派生逻辑)
const { themeBackground } = useThemeGradient();
const {
groups,
files,
records,
loading,
errorText,
pathStack,
inGroup,
init,
enterGroup,
exitGroup,
openFolder,
back,
goTo,
loadFolder,
createFolder,
unshare,
} = useShareHall();
// 每次回到页面重拉:大厅群组与组内目录都可能被别的端改过
useAuthGuard(async () => {
if (inGroup.value) {
await loadFolder(pathStack.value[pathStack.value.length - 1]?.value ?? "");
}
else {
await init();
}
});
// ===== 页面状态 =====
const keyword = ref("");
const category = ref<FileCategory>("all");
const viewMode = ref<"grid" | "list">("list");
/** 排序模式:0 时间降序 / 1 升序 / 2 名称(与文件页同套) */
const sortMode = ref(0);
const showSearch = ref(true);
/** 新建文件夹对话框(folder-name-dialog 复用文件页组件) */
const folderDialogVisible = ref(false);
const folderSubmitting = ref(false);
/** 动作提交中标记:showLoading 期间防连点 */
const busy = ref(false);
/** 导航标题:大厅顶层「共享大厅」,进组后跟随当前层名(组根=群组名) */
const navTitle = computed(() =>
inGroup.value
? pathStack.value[pathStack.value.length - 1]?.displayName || t("ShareHallPage.title")
: t("ShareHallPage.title"),
);
const tabs = computed<FileTabItem[]>(() => [
{ key: "all", label: t("FilePage.catAll") },
{ key: "image", label: t("FilePage.catImage") },
{ key: "audio", label: t("FilePage.catAudio") },
{ key: "video", label: t("FilePage.catVideo") },
{ key: "document", label: t("FilePage.catDocument") },
]);
/** 分类/关键字本地过滤(组内一次拉全,切标签不打接口) */
const visibleFiles = computed<FileItem[]>(() => {
const kw = keyword.value.trim().toLowerCase();
const list = (inGroup.value ? files.value : groups.value).filter(
f => (category.value === "all" || f.kind === category.value)
&& (!kw || f.name.toLowerCase().includes(kw)),
);
return [...list].sort((a, b) => {
if (sortMode.value === 2) {
return a.name.localeCompare(b.name, "zh-CN");
}
return sortMode.value === 1 ? a.sortTime - b.sortTime : b.sortTime - a.sortTime;
});
});
function toast(title: string): void {
uni.showToast({ title, icon: "none" });
}
/** showModal 包 Promise(取消共享确认) */
function confirmDialog(title: string, content: string): Promise<boolean> {
return new Promise((resolve) => {
uni.showModal({
title,
content,
success: res => resolve(!!res.confirm),
fail: () => resolve(false),
});
});
}
/** 动作防重 + loading 包裹(与文件页 withBusy 同套路) */
async function withBusy(task: () => Promise<void>, tip?: string): Promise<void> {
if (busy.value) {
return;
}
busy.value = true;
uni.showLoading({ title: tip || t("FilePage.pleaseWait"), mask: true });
try {
await task();
}
catch (error: any) {
toast(error?.message || t("FilePage.actionFailed"));
}
finally {
uni.hideLoading();
busy.value = false;
}
}
// ===== 导航 / 搜索 / 分类 =====
async function onBack(): Promise<void> {
// 组内:退一层目录,组根再退回大厅;顶层:退出页面
if (inGroup.value) {
keyword.value = "";
if (!(await back())) {
await exitGroup();
}
return;
}
// 直链直达 /share-hall 时没有返回栈:兜回文件页
if (getCurrentPages().length > 1) {
uni.navigateBack();
return;
}
uni.reLaunch({ url: pageUrl("fileTab") });
}
function onToggleSearch(): void {
showSearch.value = !showSearch.value;
}
/** 关键字确认键走服务端 contain 重查(仅组内;大厅顶层本地过滤群组名) */
async function onSearch(): Promise<void> {
if (inGroup.value) {
await loadFolder(pathStack.value[pathStack.value.length - 1]?.value ?? "", keyword.value);
}
}
/** 失败态重试:组内重查当前层,顶层重拉群组列表 */
async function onRetry(): Promise<void> {
if (inGroup.value) {
await onSearch();
return;
}
await init();
}
function onCategoryChange(key: FileCategory): void {
category.value = key;
}
function onSort(): void {
sortMode.value = (sortMode.value + 1) % 3;
}
function onToggleView(): void {
viewMode.value = viewMode.value === "grid" ? "list" : "grid";
}
/** 面包屑点某段:截断到该层(组根段回组根而非出组,出组走返回箭头) */
async function onCrumbNav(index: number): Promise<void> {
keyword.value = "";
await goTo(index);
}
// ===== 条目动作层 =====
/** 点目录:有权限下钻,无权限由 openFolder 抛文案 toast */
async function enterFolder(file: FileItem): Promise<void> {
try {
await openFolder(file);
keyword.value = "";
}
catch (error: any) {
toast(error?.message || t("FilePage.actionFailed"));
}
}
async function doPreview(file: FileItem): Promise<void> {
const record = records.value.get(file.id);
if (!record) {
return;
}
await withBusy(async () => {
const urls = [toPlayableUrl(await fetchFileBytes(record))];
uni.previewImage({ urls, current: urls[0] });
});
}
async function doDownload(file: FileItem): Promise<void> {
const record = records.value.get(file.id);
if (!record) {
return;
}
await withBusy(async () => {
await saveFileBytes(await fetchFileBytes(record));
toast(t("FilePage.downloadDone"));
}, t("FilePage.downloading"));
}
/** 视频/音频播放:Url 直链直接进播放页,否则字节拼整后本地播(与文件页同策略) */
async function doPlay(file: FileItem): Promise<void> {
const record = records.value.get(file.id);
if (!record) {
return;
}
const kind = file.kind === "video" ? "video" : "audio";
if (kind === "video" && record.Url) {
goPlay(kind, file.name, record.Url);
return;
}
await withBusy(async () => {
const payload = await fetchFileBytes(record);
goPlay(kind, file.name, toPlayableUrl(payload));
}, t("FilePage.loadingMedia"));
}
function goPlay(kind: "video" | "audio", name: string, url: string): void {
// 播放地址走模块级总线传递,query 只带类型与展示名(文件页同款:blob 地址经 query 二次编码会跳转失败)
setPlayback({ type: kind, name, url });
uni.navigateTo({
url: `${pageUrl("mediaPlay")}?type=${kind}&name=${encodeURIComponent(name)}`,
fail: (err: any) => {
clearPlayback();
toast(err?.errMsg || t("FilePage.actionFailed"));
},
});
}
async function doUnshare(file: FileItem): Promise<void> {
const ok = await confirmDialog(
t("ShareHallPage.unshare"),
t("ShareHallPage.unshareConfirmText").replace("{name}", file.name),
);
if (!ok) {
return;
}
await withBusy(async () => {
await unshare(file);
toast(t("ShareHallPage.unshareDone"));
});
}
/** 按条目类型拼动作集(系统动作面板),取消共享任何类型都有 */
function sheetActions(file: FileItem): Array<{ key: string; label: string }> {
const actions: Array<{ key: string; label: string }> = [];
if (file.kind === "folder") {
actions.push({ key: "enter", label: t("ShareHallPage.enter") });
}
if (file.kind === "image") {
actions.push({ key: "preview", label: t("ShareHallPage.preview") });
}
if (file.kind === "video" || file.kind === "audio") {
actions.push({ key: "play", label: t("ShareHallPage.play") });
}
if (file.kind !== "folder") {
actions.push({ key: "download", label: t("ShareHallPage.download") });
}
// 大厅顶层的群组卡没有共享记录,不给取消共享
if (inGroup.value) {
actions.push({ key: "unshare", label: t("ShareHallPage.unshare") });
}
return actions;
}
function openSheet(file: FileItem): void {
const actions = sheetActions(file);
uni.showActionSheet({
itemList: actions.map(a => a.label),
success: (res) => {
const action = actions[res.tapIndex];
if (action.key === "enter") {
// 顶层群组卡的「打开」是进组,组内目录卡的「打开」才是下钻
if (inGroup.value) {
enterFolder(file);
}
else {
enterTopGroup(file);
}
}
else if (action.key === "preview") {
doPreview(file);
}
else if (action.key === "play") {
doPlay(file);
}
else if (action.key === "download") {
doDownload(file);
}
else if (action.key === "unshare") {
doUnshare(file);
}
},
});
}
/** 单击条目:目录进组/下钻,图片预览,音视频播放,文档走动作面板(无预览能力) */
function onOpen(file: FileItem): void {
if (!inGroup.value) {
enterTopGroup(file);
return;
}
if (file.kind === "folder") {
enterFolder(file);
}
else if (file.kind === "image") {
doPreview(file);
}
else if (file.kind === "video" || file.kind === "audio") {
doPlay(file);
}
else {
openSheet(file);
}
}
/** 大厅顶层点群组卡:进组 */
async function enterTopGroup(group: FileItem): Promise<void> {
await withBusy(async () => {
await enterGroup(group);
category.value = "all";
keyword.value = "";
});
}
// ===== 新建文件夹(仅组内) =====
function onNewFolder(): void {
folderDialogVisible.value = true;
}
async function onCreateFolder(name: string): Promise<void> {
folderSubmitting.value = true;
try {
await createFolder(name);
toast(t("ShareHallPage.createDone"));
folderDialogVisible.value = false;
}
catch (error: any) {
toast(error?.message || t("FilePage.actionFailed"));
}
finally {
folderSubmitting.value = false;
}
}
</script>
<template>
<!-- 二级页不带 tabbar(app-page 默认关) -->
<app-page :hide-nav="true">
<view class="hall-page" :style="{ background: themeBackground }">
<view class="hall-page__head">
<!-- 导航:标题随层级变化;返回箭头常显(顶层退出回文件页,组内退层);搜索图标仅组内;传输/快捷入口开关关闭 -->
<page-navbar :title="navTitle" :show-back="true" :search-on="showSearch" :show-transfer="false"
:show-quick-toggle="false" :show-search-toggle="inGroup" @back="onBack"
@toggle-search="onToggleSearch" />
<!-- 组内才有的路径条:两层以上显示(组根不显示,与文件页惯例一致) -->
<view v-if="inGroup && pathStack.length > 1" class="hall-page__crumb">
<file-breadcrumb :items="pathStack" @nav="onCrumbNav" />
</view>
<search-input v-if="inGroup && showSearch" v-model="keyword"
:placeholder="t('FilePage.searchPlaceholder')" @search="onSearch" />
<!-- 分类栏:新建文件夹仅组内出现(大厅顶层无写操作),共享大厅不做批量多选 -->
<file-tab-filter v-if="inGroup" :tabs="tabs" :active="category" :view-mode="viewMode"
:show-new-folder="true" :show-select="false" @change="onCategoryChange" @sort="onSort"
@toggle-view="onToggleView" @new-folder="onNewFolder" />
</view>
<!-- 滚动窗口外层包装=卡片本体:margin/圆角/白底/裁剪在这层(与文件页同因) -->
<view class="hall-page__scroll-wrap">
<scroll-view scroll-y class="hall-page__scroll">
<view class="hall-page__card">
<view v-if="loading" class="hall-page__state">
<text class="hall-page__state-text">
{{ t("FilePage.loading") }}
</text>
</view>
<view v-else-if="errorText" class="hall-page__state">
<text class="hall-page__state-text hall-page__state-text--error">
{{ errorText }}
</text>
<text class="hall-page__retry" @click="onRetry()">
{{ t("FilePage.retryBut") }}
</text>
</view>
<file-grid-list v-else :files="visibleFiles" :view-mode="viewMode" :selected-ids="[]"
:empty-text="inGroup ? t('ShareHallPage.emptyText') : t('ShareHallPage.emptyGroups')"
@open="onOpen" @more="openSheet" />
</view>
<view class="hall-page__bottom-space" />
</scroll-view>
</view>
<!-- 新建文件夹:复用文件页命名对话框(默认文案就是新建) -->
<folder-name-dialog :visible="folderDialogVisible" :submitting="folderSubmitting"
@close="folderDialogVisible = false" @confirm="onCreateFolder" />
</view>
</app-page>
</template>
<style lang="scss" scoped>
/* 骨架样式与文件页/回收站同套(fixed 全屏 + head/scroll-wrap 卡片分区),前缀换 hall-page */
.hall-page {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
overflow: hidden;
/* 渐变底由 script 的 themeBackground 按主题 primary 动态计算(内联 style 下发) */
}
.hall-page__head {
flex-shrink: 0;
}
/* 面包屑分区:与文件页同节奏的间隔(面包屑自身就是白卡片) */
.hall-page__crumb {
padding: 20rpx 24rpx 0;
}
/* 滚动窗口外层包装=卡片本体 */
.hall-page__scroll-wrap {
flex: 1;
min-height: 0;
margin: 20rpx 20rpx 0;
border-radius: 32rpx 32rpx 0 0;
background-color: #ffffff;
overflow: hidden;
}
/* scroll-view 只当纯滚动容器垫满 wrap,滚动内容被 wrap 的圆角窗口裁剪 */
.hall-page__scroll {
height: 100%;
}
.hall-page__bottom-space {
height: 48rpx;
}
.hall-page__state {
display: flex;
align-items: center;
justify-content: center;
gap: 24rpx;
padding: 64rpx 32rpx;
}
.hall-page__state-text {
font-size: 26rpx;
color: #9aa0a6;
}
.hall-page__state-text--error {
color: #f0413e;
}
.hall-page__retry {
padding: 8rpx 28rpx;
border-radius: 999rpx;
font-size: 26rpx;
color: #2b8fff;
background-color: #eaf4ff;
}
</style>
+56 -50
View File
@@ -15,6 +15,7 @@
* 权限暂用 UserOperation(见 routes.ts 的 PAGE_PERMISSIONS);useAuthGuard 兜冷启动直达。
*/
import type { FolderRow } from "@/components/folder-move-picker/folder-move-picker.vue";
import type { ShareTarget } from "@/components/folder-share-picker/folder-share-picker.vue";
import type { PickerOption } from "@/components/option-picker/option-picker.vue";
import type { FileCategory, FileItem, FileKind, FileTabItem, QuickEntryItem } from "@/data/custom/fileBrowser";
import { useLocale } from "uview-pro";
@@ -22,14 +23,12 @@ import { computed, ref, watch } from "vue";
import { useAuthGuard, useDmsFiles, useThemeGradient } from "@/composables";
import { DmsFileEntity } from "@/data/auto/dmsFileEntity";
import { FamilyGroupFolderEntity } from "@/data/auto/familyGroupFolderEntity";
import { FamilyGroupManagerUserSearchCriteria } from "@/data/auto/familyGroupManagerUserSearchCriteria";
import { PublishAudioSearchCriteria } from "@/data/auto/publishAudioSearchCriteria";
import { PublishData } from "@/data/auto/publishData";
import { PublishPhotoSearchCriteria } from "@/data/auto/publishPhotoSearchCriteria";
import { PublishVideoSearchCriteria } from "@/data/auto/publishVideoSearchCriteria";
import { pageUrl } from "@/router/routes";
import dmsFileService from "@/service/master/dmsFileService";
import familyGroupService from "@/service/master/familyGroupService";
import publishAudioService from "@/service/master/publishAudioService";
import publishPhotoService from "@/service/master/publishPhotoService";
import publishVideoService from "@/service/master/publishVideoService";
@@ -180,12 +179,37 @@ function onSearch(value: string): void {
search(value);
}
/** 展览/播放三入口:以当前目录为上下文,mode=list + key=目录路径跳对应播放器页 */
function goShowWithFolder(name: "photoShow" | "audioHall" | "videoHall"): void {
const folder = pathStack.value[pathStack.value.length - 1]?.value ?? "";
uni.navigateTo({
url: `${pageUrl(name)}?mode=list&key=${encodeURIComponent(folder)}`,
fail: (err: any) => toast(err?.errMsg || t("FilePage.actionFailed")),
});
}
function onQuickClick(key: string): void {
// 回收站已落地(pages/trash);其余入口仍是占位提示
// 回收站/共享大厅(pages/trash、pages/share-hall);相册/音乐厅/影视厅展览当前目录
if (key === "trash") {
uni.navigateTo({ url: pageUrl("trash") });
return;
}
if (key === "shareCenter") {
uni.navigateTo({ url: pageUrl("shareHall") });
return;
}
if (key === "album") {
goShowWithFolder("photoShow");
return;
}
if (key === "musicHall") {
goShowWithFolder("audioHall");
return;
}
if (key === "videoHall") {
goShowWithFolder("videoHall");
return;
}
const entry = quickEntries.value.find(e => e.key === key);
toast(entry ? `${entry.label} · ${t("FilePage.todoTip")}` : t("FilePage.todoTip"));
}
@@ -600,15 +624,12 @@ function goPlay(type: "video" | "audio", name: string, url: string): void {
});
}
// ----- 共享家人:选收藏夹里的家人目录,组装 FamilyGroupFolderEntity 批量建(旧 handleSelectShare)-----
// ----- 共享家人:共享大厅式抽屉选目标目录(群组卡→逐级下钻→共享到此),
// 多选文件一次批量建(CreateFamilyGroupFolder 数组入参,旧 handleSelectShare 同口径)-----
const shareVisible = ref(false);
const shareLoading = ref(false);
const shareItems = ref<PickerOption[]>([]);
/** 展示用 items 之外的组装字段(FamilyName/TopFolderName 提交才需要) */
const shareFolders = ref<Array<{ key: string; familyName: string; topFolderName: string }>>([]);
const shareTargets = ref<FileItem[]>([]);
async function actionShare(targets: FileItem[]): Promise<void> {
function actionShare(targets: FileItem[]): void {
const picked = targets.filter(f => f.kind !== "folder");
if (!picked.length) {
toast(t("FilePage.shareNoFile"));
@@ -616,41 +637,11 @@ async function actionShare(targets: FileItem[]): Promise<void> {
}
shareTargets.value = picked;
shareVisible.value = true;
shareLoading.value = true;
try {
const criteria = new FamilyGroupManagerUserSearchCriteria();
criteria.Filter = [];
criteria.limit = 999;
criteria.page = 1;
criteria.GridColumns = [];
const data = await familyGroupService.FamilyFolderFavoriteBySearchCriteria(criteria);
const favorites = data?.Favorites ?? [];
shareFolders.value = favorites.map(f => ({
key: f.FolderName,
familyName: f.FamilyName,
topFolderName: f.TopFolderName,
}));
shareItems.value = favorites.map(f => ({
key: f.FolderName,
label: `${f.FamilyLocalName} / ${f.FolderLocalName}`,
sub: f.Notice,
}));
}
catch (error: any) {
toast(error?.message || t("FilePage.actionFailed"));
}
finally {
shareLoading.value = false;
}
}
async function onShareConfirm(key: string): Promise<void> {
const target = shareFolders.value.find(f => f.key === key);
shareVisible.value = false;
if (!target) {
return;
}
async function onShareConfirm(target: ShareTarget): Promise<void> {
const filesToShare = shareTargets.value;
shareVisible.value = false;
await withBusy(async () => {
const rows = filesToShare.map((file) => {
const record = recordOf(file);
@@ -660,7 +651,7 @@ async function onShareConfirm(key: string): Promise<void> {
// 旧系统组装:FolderName 文件系统名、FolderLocalName 原始名、FilePath 目标共享目录
fgp.FolderName = record?.FileName ?? file.path;
fgp.FolderLocalName = record?.OrgFileName ?? file.name;
fgp.FilePath = target.key;
fgp.FilePath = target.filePath;
return fgp;
});
await dmsFileService.CreateFamilyGroupFolder(rows);
@@ -701,7 +692,8 @@ async function actionPublish(targets: FileItem[]): Promise<void> {
publishVisible.value = true;
publishLoading.value = true;
try {
let names: string[] = [];
// 拉到的专辑行含名称(PublishLocalName)/描述(Notice)/编号(PublishName),不能只留编号
let rows: Array<{ PublishName?: string; PublishLocalName?: string; Notice?: string }> = [];
if (first.kind === "image") {
const criteria = new PublishPhotoSearchCriteria();
criteria.Filter = [];
@@ -709,7 +701,7 @@ async function actionPublish(targets: FileItem[]): Promise<void> {
criteria.page = 1;
criteria.GridColumns = [];
const data = await publishPhotoService.FindBySearchCriteria(criteria);
names = (data?.Publishs ?? []).map(p => p.PublishName);
rows = data?.Publishs ?? [];
}
else if (first.kind === "video") {
const criteria = new PublishVideoSearchCriteria();
@@ -718,7 +710,7 @@ async function actionPublish(targets: FileItem[]): Promise<void> {
criteria.page = 1;
criteria.GridColumns = [];
const data = await publishVideoService.FindBySearchCriteria(criteria);
names = (data?.Datas ?? []).map(p => p.PublishName);
rows = data?.Datas ?? [];
}
else {
const criteria = new PublishAudioSearchCriteria();
@@ -727,9 +719,24 @@ async function actionPublish(targets: FileItem[]): Promise<void> {
criteria.page = 1;
criteria.GridColumns = [];
const data = await publishAudioService.FindBySearchCriteria(criteria);
names = (data?.Datas ?? []).map(p => p.PublishName);
rows = data?.Datas ?? [];
}
publishItems.value = names.filter(Boolean).map(n => ({ key: n, label: n }));
// 主行显示专辑名称(无名称回落编号),副行显示描述 + 编号;key 仍用编号供 onPublishConfirm 发布
publishItems.value = rows
.filter(p => p?.PublishName)
.map((p) => {
const code = p.PublishName || "";
const hasName = !!p.PublishLocalName;
const label = p.PublishLocalName || code;
const parts: string[] = [];
if (p.Notice) {
parts.push(p.Notice);
}
if (hasName) {
parts.push(code);
}
return { key: code, label, sub: parts.join(" · ") };
});
}
catch (error: any) {
toast(error?.message || t("FilePage.actionFailed"));
@@ -966,9 +973,8 @@ const transferIconWhite = transferGlyph("#ffffff");
<!-- 移动:网盘式逐级下钻选择器(进层浏览,「移动到此」提交当前层后直接调移动接口) -->
<folder-move-picker :visible="moveVisible" :exclude-name="moveExcludeName" :source-path="moveSourcePath"
:selected-count="moveTargets.length" @close="moveVisible = false" @confirm="onMovePick" />
<!-- 共享家人:选家人目录 -->
<option-picker :visible="shareVisible" :title="t('FilePage.shareTitle')" :items="shareItems"
:loading="shareLoading" :empty-text="t('FilePage.pickerEmpty')" @close="shareVisible = false"
<!-- 共享家人:共享大厅式抽屉(群组卡→逐级下钻→「共享到此」),多选文件一次批量共享 -->
<folder-share-picker :visible="shareVisible" :selected-count="shareTargets.length" @close="shareVisible = false"
@confirm="onShareConfirm" />
<!-- 发布:选专辑 -->
<option-picker :visible="publishVisible" :title="publishTitle" :items="publishItems" :loading="publishLoading"
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff