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

@@ -84,11 +84,24 @@ function onCardTap(file: FileItem): void {
</view>
<view class="fgl-card__body" @tap="onCardTap(file)">
<!-- 文件夹:纯 CSS 造型(图标库没有 folder),主体 + 左上页签 -->
<view v-if="file.kind === 'folder'" class="fgl-card__folder">
<view class="fgl-card__folder-tab" />
<!-- 图标区包装:共享域的权限/收藏角标叠加在图标右上角(旧 MyComputerFamily 同款) -->
<view class="fgl-card__icon-wrap">
<!-- 文件夹:纯 CSS 造型(图标库没有 folder),主体 + 左上页签 -->
<view v-if="file.kind === 'folder'" class="fgl-card__folder">
<view class="fgl-card__folder-tab" />
</view>
<u-icon v-else :name="visualOf(file.kind)!.icon" size="88" :color="visualOf(file.kind)!.color" />
<!-- 锁仅目录显示(旧系统:文件不挂锁):绿开锁=有权限,灰闭锁=无权限;
蓝分享标=已被收藏共享,两者文件/目录都可显示;
已共享的目录不再叠绿开锁(开锁是有权限的子集,两个图标同屏冗余) -->
<view v-if="(file.kind === 'folder' && file.permission && !file.shared) || file.shared"
class="fgl-card__badges">
<u-icon v-if="file.kind === 'folder' && file.permission && !file.shared"
:name="file.permission === 'Y' ? 'lock-open' : 'lock'" size="28"
:color="file.permission === 'Y' ? '#52c41a' : '#8c8c8c'" />
<u-icon v-if="file.shared" name="share-fill" size="28" color="#1890ff" />
</view>
</view>
<u-icon v-else :name="visualOf(file.kind)!.icon" size="88" :color="visualOf(file.kind)!.color" />
<view class="fgl-card__info">
<text class="fgl-card__name">
@@ -165,6 +178,23 @@ function onCardTap(file: FileItem): void {
padding: 36rpx 12rpx 0;
}
/* 图标区包装:角标 absolute 定位的参照系 */
.fgl-card__icon-wrap {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
/* 共享角标组:压在图标右上角(旧 MyComputerFamily badge 同位) */
.fgl-card__badges {
position: absolute;
top: -8rpx;
right: -44rpx;
display: flex;
gap: 4rpx;
}
/* ---- 纯 CSS 文件夹:蓝色渐变圆角主体 + 左上页签 ---- */
.fgl-card__folder {
position: relative;
@@ -263,6 +293,12 @@ function onCardTap(file: FileItem): void {
border-radius: 6rpx 6rpx 0 0;
}
/* 列表态图标小(72rpx),角标组贴紧一点不出格 */
.file-grid-list--list .fgl-card__badges {
top: -12rpx;
right: -8rpx;
}
.file-grid-list--list .fgl-card__info {
flex: 1;
min-width: 0;
@@ -36,6 +36,11 @@ defineProps({
type: Boolean,
default: true,
},
/** 多选开关按钮:无批量动作的页面(如共享大厅)传 false */
showSelect: {
type: Boolean,
default: true,
},
});
const emit = defineEmits<{
@@ -77,8 +82,8 @@ const sortIconDark = sortGlyph("#1f2329");
<view class="file-tab-filter__tool" @tap="emit('toggle-view')">
<u-icon :name="viewMode === 'grid' ? 'list' : 'grid-fill'" size="36" color="#1f2329" />
</view>
<!-- 多选开关:未进入给自绘方框箭头字形,进入后给关闭圆(高亮蓝) -->
<view class="file-tab-filter__tool" @tap="emit('toggle-select')">
<!-- 多选开关:未进入给自绘方框箭头字形,进入后给关闭圆(高亮蓝);无批量动作的页面隐藏 -->
<view v-if="showSelect" class="file-tab-filter__tool" @tap="emit('toggle-select')">
<image v-if="!selectMode" class="file-tab-filter__tool-img" :src="selectIconDark" mode="aspectFit" />
<u-icon v-else name="close-circle" size="36" color="#2b8fff" />
</view>
@@ -34,6 +34,11 @@ const props = defineProps({
type: String,
default: "",
},
/** 输入框占位文案:缺省沿用「新建文件夹」语义,群描述/申请原因等场景由页面覆写 */
placeholder: {
type: String,
default: "",
},
});
const emit = defineEmits<{
@@ -75,8 +80,9 @@ function onConfirm(): void {
<text class="folder-name-dialog__title">
{{ props.title || t("FilePage.addFolder") }}
</text>
<input v-model="name" class="folder-name-dialog__input" :placeholder="t('FilePage.folderPlaceholder')"
:maxlength="50" :disabled="props.submitting" confirm-type="done" @confirm="onConfirm">
<input v-model="name" class="folder-name-dialog__input"
:placeholder="props.placeholder || t('FilePage.folderPlaceholder')" :maxlength="50" :disabled="props.submitting"
confirm-type="done" @confirm="onConfirm">
<view class="folder-name-dialog__actions">
<view class="folder-name-dialog__btn" @click="emit('close')">
<text class="folder-name-dialog__btn-text">
@@ -0,0 +1,392 @@
<script setup lang="ts">
/**
* 共享家人目标选择器(底部抽屉,共享大厅同款两态):顶层列家人群组卡片
* (图2 样式),点群组进组后逐级浏览目录,「共享到此」提交当前所在层。
* 替代旧的收藏夹单选列表——目标不再限定收藏过的目录,群组根与任何有权限
* 的目录都能接收共享(后端 CreateFamilyGroupFolder 纯插入不校验目录)。
* 数据链与共享大厅同口径:FindFamilyDmsFolders 拉群组,FindFamilyBySearchCriteria
* 挂 [family_name + file_path] 两段(组根层标识 = 群组编号,子层 = 目录系统名)。
* 组件不发共享请求:点「共享到此」把当前层三元组 confirm 给页面,
* 页面按每个待共享文件组装 FamilyGroupFolderEntity 批量提交(多选文件一次发)。
*/
import type { PathNode } from "@/data/custom/fileBrowser";
import { useLocale } from "uview-pro";
import { computed, ref, watch } from "vue";
import { DmsFileSearchCriteria } from "@/data/auto/dmsFileSearchCriteria";
import { FamilyDmsFileViewDisplayData } from "@/data/auto/familyDmsFileViewDisplayData";
import { SearchFilter } from "@/data/auto/searchFilter";
import dmsFileService from "@/service/master/dmsFileService";
/** 共享目标层:组 + 顶层目录系统名(旧系统取消共享按它查权限)+ 当前目录标识 */
export interface ShareTarget {
familyName: string;
topFolderName: string;
filePath: string;
}
/** 浏览路径的一段:value = 群组编号(组根)或目录系统名(子层) */
interface CrumbRow {
value: string;
displayName: string;
}
const props = defineProps({
/** 抽屉是否展开 */
visible: {
type: Boolean,
default: false,
},
/** 已选待共享文件数(顶部「已选择 N 项」提示) */
selectedCount: {
type: Number,
default: 0,
},
});
const emit = defineEmits<{
(e: "close"): void;
/** 点「共享到此」,带上当前所在层的目标三元组 */
(e: "confirm", target: ShareTarget): void;
}>();
const { t } = useLocale();
const loading = ref(false);
/** 大厅顶层:群组卡列表(value = 群组编号) */
const groups = ref<Array<{ value: string; label: string }>>([]);
/** 组内浏览路径栈:[0] 恒为组根(群组编号),末项 = 当前所在目录 */
const trail = ref<CrumbRow[]>([]);
/** 当前群组编号;空串 = 停在大厅顶层 */
const familyName = ref("");
/** 当前层子目录行(只列目录,文件不能当共享目标) */
const dirs = ref<Array<{ value: string; label: string; permitted: boolean }>>([]);
const inGroup = computed(() => !!trail.value.length);
/** 「共享到此」只在进入下一层目录后出现:群组名那一层(trail 只有组根一段)不允许直接共享 */
const canShareHere = computed(() => trail.value.length >= 2);
/** 「共享到此」的目标三元组:组根 TopFolderName = 群组编号,子层 = 路径首段目录 */
const currentTarget = computed<ShareTarget | null>(() => {
if (!inGroup.value) {
return null;
}
return {
familyName: familyName.value,
topFolderName: trail.value[0].value,
filePath: trail.value[trail.value.length - 1].value,
};
});
// 每次展开重拉群组并回大厅顶层:群组可能被别的端改过,缓存会弹进已消失的坑
watch(
() => props.visible,
async (on) => {
if (on) {
trail.value = [];
familyName.value = "";
await loadGroups();
}
},
);
async function loadGroups(): Promise<void> {
loading.value = true;
try {
const options = (await dmsFileService.FindFamilyDmsFolders()) ?? [];
groups.value = options.map(opt => ({ value: opt.BindValue, label: opt.DisplayName || opt.BindValue }));
}
finally {
loading.value = false;
}
}
/** 查当前层子目录:两段过滤与共享大厅 loadFolder 同口径(组根标识=群组编号照挂) */
async function loadDirs(): Promise<void> {
loading.value = true;
try {
const criteria = new DmsFileSearchCriteria();
const familyFilter = new SearchFilter();
familyFilter.Column = FamilyDmsFileViewDisplayData.DB_FAMILY_NAME;
familyFilter.Type = "SingleSelection";
familyFilter.Operator = "single";
familyFilter.Value = familyName.value;
const pathFilter = new SearchFilter();
pathFilter.Column = FamilyDmsFileViewDisplayData.DB_FILE_PATH;
pathFilter.Type = "SingleSelection";
pathFilter.Operator = "single";
pathFilter.Value = trail.value[trail.value.length - 1].value;
criteria.Filter = [familyFilter, pathFilter];
criteria.limit = 999999;
criteria.page = 1;
criteria.GridColumns = [];
const data = await dmsFileService.FindFamilyBySearchCriteria(criteria);
dirs.value = (data?.Families ?? [])
.filter(r => r.IsDirectoryIndc === "Y")
.map(r => ({
value: r.FileName,
label: r.OrgFileName || r.FileName,
permitted: r.PermissionIndc === "Y",
}))
.sort((a, b) => a.label.localeCompare(b.label, "zh-CN"));
}
finally {
loading.value = false;
}
}
function enterGroup(group: { value: string; label: string }): void {
familyName.value = group.value;
trail.value = [{ value: group.value, displayName: group.label }];
dirs.value = [];
void loadDirs();
}
/** 下钻子目录;无权限的目录进不去(与共享大厅 PermissionIndc 口径一致) */
function enterDir(row: { value: string; label: string; permitted: boolean }): void {
if (!row.permitted) {
uni.showToast({ title: t("ShareHallPage.noPermission"), icon: "none" });
return;
}
trail.value = [...trail.value, { value: row.value, displayName: row.label }];
void loadDirs();
}
/** 组内面包屑:首段=群组名,单击回该层(当前段组件内自行不响应) */
const crumbItems = computed<PathNode[]>(() => trail.value.map(row => ({
value: row.value,
displayName: row.displayName,
})));
function onJump(index: number): void {
trail.value = trail.value.slice(0, index + 1);
void loadDirs();
}
function onShareHere(): void {
const target = currentTarget.value;
if (target) {
emit("confirm", target);
}
}
</script>
<template>
<u-popup :model-value="props.visible" mode="bottom" round="24" @close="emit('close')">
<view class="folder-share-picker">
<!-- 头部:大厅层显示标题,组内显示面包屑(单击回层、超长自动折叠)+ 关闭 -->
<view class="folder-share-picker__head">
<text v-if="!inGroup" class="folder-share-picker__title">
{{ t("FilePage.shareTitle") }}
</text>
<view v-else class="folder-share-picker__crumb">
<file-breadcrumb :items="crumbItems" :show-single="true" @nav="onJump" />
</view>
<view class="folder-share-picker__close" @tap="emit('close')">
<u-icon name="close" size="36" color="#8f959e" />
</view>
</view>
<text v-if="props.selectedCount" class="folder-share-picker__picked">
{{ t("FilePage.moveSelected").replace("{count}", String(props.selectedCount)) }}
</text>
<scroll-view scroll-y class="folder-share-picker__list">
<view v-if="loading" class="folder-share-picker__empty">
<u-loading mode="circle" />
</view>
<template v-else>
<!-- 大厅顶层:群组卡片(共享大厅图2 同款:浅灰圆卡 + 蓝色文件夹) -->
<template v-if="!inGroup">
<view v-if="!groups.length" class="folder-share-picker__empty">
<text class="folder-share-picker__empty-text">
{{ t("FilePage.pickerEmpty") }}
</text>
</view>
<view v-for="g in groups" :key="g.value" class="folder-share-picker__group"
@tap="enterGroup(g)">
<view class="folder-share-picker__folder" />
<text class="folder-share-picker__group-text">
{{ g.label }}
</text>
</view>
</template>
<!-- 组内:子目录行(点行进下层,「共享到此」提交当前层) -->
<template v-else>
<view v-if="!dirs.length" class="folder-share-picker__empty">
<text class="folder-share-picker__empty-text">
{{ t("FilePage.moveNoSub") }}
</text>
</view>
<view v-for="row in dirs" :key="row.value" class="folder-share-picker__row"
@tap="enterDir(row)">
<view class="folder-share-picker__folder" />
<text class="folder-share-picker__row-text"
:class="{ 'folder-share-picker__row-text--dim': !row.permitted }">
{{ row.label }}
</text>
<u-icon v-if="!row.permitted" name="lock" size="26" color="#8c8c8c" />
<u-icon v-else name="arrow-right" size="26" color="#c0c4cc" />
</view>
</template>
</template>
</scroll-view>
<!-- 底部:通栏「共享到此」提交当前层(群组名那一层不显示——必须进到下一层目录才能共享) -->
<view v-if="canShareHere" class="folder-share-picker__footer">
<view class="folder-share-picker__btn" @tap="onShareHere">
<text class="folder-share-picker__btn-text">
{{ t("FilePage.shareHere") }}
</text>
</view>
</view>
<view class="folder-share-picker__safe" />
</view>
</u-popup>
</template>
<style lang="scss" scoped>
.folder-share-picker {
padding: 8rpx 32rpx 0;
background-color: #ffffff;
&__head {
display: flex;
align-items: center;
height: 108rpx;
}
&__title {
flex: 1;
font-size: 32rpx;
font-weight: 600;
color: #1f2329;
text-align: center;
}
/* 面包屑占满剩余宽度(自身可收缩,超长时组件内折叠出 …) */
&__crumb {
flex: 1;
min-width: 0;
overflow: hidden;
}
&__close {
display: flex;
flex-shrink: 0;
padding: 12rpx;
}
&__picked {
display: block;
padding: 0 8rpx 16rpx;
font-size: 26rpx;
color: #5c626b;
}
&__list {
max-height: 640rpx;
min-height: 240rpx;
}
&__empty {
display: flex;
align-items: center;
justify-content: center;
padding: 80rpx 0;
}
&__empty-text {
font-size: 26rpx;
color: #8f959e;
}
/* 群组卡片:共享大厅顶层同款浅灰圆底行 */
&__group {
display: flex;
align-items: center;
gap: 24rpx;
margin-bottom: 16rpx;
padding: 28rpx 24rpx;
border-radius: 16rpx;
background-color: #f7f8fa;
}
&__group-text {
flex: 1;
overflow: hidden;
font-size: 30rpx;
font-weight: 500;
color: #1f2329;
text-overflow: ellipsis;
white-space: nowrap;
}
&__row {
display: flex;
align-items: center;
gap: 20rpx;
padding: 24rpx 12rpx;
border-radius: 16rpx;
}
&__row-text {
display: flex;
flex: 1;
overflow: hidden;
font-size: 30rpx;
color: #1f2329;
text-overflow: ellipsis;
white-space: nowrap;
&--dim {
color: #9aa0a6;
}
}
/* CSS 画文件夹(图标库无 folder 字形,同 folder-move-picker / 图2 蓝色系) */
&__folder {
position: relative;
flex-shrink: 0;
width: 56rpx;
height: 44rpx;
border-radius: 10rpx;
background: linear-gradient(180deg, #8ecbff, #4aa3ff);
&::before {
position: absolute;
top: -10rpx;
left: 0;
width: 24rpx;
height: 12rpx;
border-radius: 6rpx 6rpx 0 0;
background: linear-gradient(180deg, #8ecbff, #4aa3ff);
content: "";
}
}
&__footer {
display: flex;
align-items: center;
padding-top: 24rpx;
border-top: 1rpx solid #f0f1f3;
}
&__btn {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
height: 76rpx;
border-radius: 999rpx;
background-color: #2b8fff;
}
&__btn-text {
font-size: 28rpx;
font-weight: 600;
color: #ffffff;
}
&__safe {
height: calc(24rpx + env(safe-area-inset-bottom));
}
}
</style>
@@ -47,6 +47,11 @@ const props = defineProps({
type: Boolean,
default: true,
},
/** 搜索图标开关:无搜索场景的页面/层级传 false(如共享大厅顶层群组卡列表) */
showSearchToggle: {
type: Boolean,
default: true,
},
});
const emit = defineEmits<{
@@ -78,8 +83,8 @@ const transferIcon = computed(() => transferGlyph(props.transferring ? TRANSFER_
{{ props.transferring > 99 ? "99+" : props.transferring }}
</text>
</view>
<!-- 切换搜索框显隐 -->
<view class="page-navbar__btn" @tap="emit('toggle-search')">
<!-- 切换搜索框显隐;无搜索场景的层级隐藏 -->
<view v-if="props.showSearchToggle" class="page-navbar__btn" @tap="emit('toggle-search')">
<u-icon name="search" size="38" :color="props.searchOn ? '#2b8fff' : '#8f959e'" />
</view>
<!-- 切换快捷入口区显隐;无快捷入口的页面隐藏 -->
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff