ow
This commit is contained in:
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
Reference in new issue
Block a user