This commit is contained in:
owenchen committed 2026-10-16 11:57:29 +08:00
1 parent b858386e86
commit 3b2ee8523b
28 files changed
+2285 -1496

No files matched your search

+47 -7
View File
@@ -2,7 +2,7 @@
/**
* 底部导航「文件」页:按截图拆解为独立组件(components/ 下各自一个 vue 文件),
* 页面只持有状态与数据,经 props 单向下发、事件(emit)上报,组件之间不互相引用:
* page-navbar 顶部导航(居中标题;仅子目录态显示返回箭头)
* page-navbar 顶部导航(左对齐标题 + 右对齐操作图标;仅子目录态显示返回箭头)
* file-breadcrumb 目录面包屑(仅子目录态:左起排列,放不下折成 根+…+尾部段)
* search-input 搜索框(v-model 即时过滤,确认键触发服务端搜索)
* quick-entry 快捷功能入口(横向滚动图标组)
@@ -32,6 +32,7 @@ import dmsFileService from "@/service/master/dmsFileService";
import publishAudioService from "@/service/master/publishAudioService";
import publishPhotoService from "@/service/master/publishPhotoService";
import publishVideoService from "@/service/master/publishVideoService";
import { useMediaIntentStore } from "@/stores/mediaIntent";
import { useUploadQueueStore } from "@/stores/uploadQueue";
import { chooseFiles } from "@/utils/chooseFiles";
import { fetchFileBytes, saveFileBytes, toPlayableUrl } from "@/utils/dmsBytes";
@@ -40,13 +41,16 @@ import { transferGlyph } from "@/utils/transferGlyph";
const { t } = useLocale();
// 页面底渐变跟随主题(派生逻辑在 composables/useThemeGradient,多页共用)
const { themeBackground } = useThemeGradient();
// 页面底跟随主题(派生逻辑在 composables/useThemeGradient,多页共用):
// 顶部主题色光晕 → 自然过渡到近白内容区,替代整页铺色的灰浊感
const { themeGlowBackground } = useThemeGradient();
const { files, records, loading, errorText, pathStack, atRoot, init, openTrail, retry, openFolder, back, goTo, search } = useDmsFiles();
// 上传队列(全局 store):入队在本页、胶囊读它的进行中计数、守卫回调消费它的面包屑导航请求
const uploadQueue = useUploadQueueStore();
// 跨 tab 意图通道:发布面板「新建相册」→ reLaunch 去媒体页自动进创建态
const mediaIntent = useMediaIntentStore();
// 首次显示拉根目录,之后每次回到页面只刷新当前目录(重进 tab 不该丢位置重新拉全链);
// 回页末尾消费传输页的面包屑导航请求(reLaunch 重建后恢复到目标目录)
@@ -675,6 +679,41 @@ const publishTitle = computed(() => {
return publishKind.value === "audio" ? t("FilePage.publishPickAudio") : t("FilePage.publishPickPhoto");
});
/** 确认键按类型叫「发布相册/发布音乐专辑/发布视频专辑」,不再笼统叫「创建」 */
const publishConfirmText = computed(() => {
if (publishKind.value === "video") {
return t("FilePage.publishConfirmVideo");
}
return publishKind.value === "audio" ? t("FilePage.publishConfirmAudio") : t("FilePage.publishConfirmPhoto");
});
/** 附加「新建」行文案:与确认键同类型口径 */
const publishCreateText = computed(() => {
if (publishKind.value === "video") {
return t("FilePage.publishCreateVideo");
}
return publishKind.value === "audio" ? t("FilePage.publishCreateAudio") : t("FilePage.publishCreatePhoto");
});
/** 专辑行首图标:与媒体页分段图标同口径 */
const publishRowIcon = computed(() =>
publishKind.value === "video" ? "play-circle-fill" : publishKind.value === "audio" ? "volume" : "photo",
);
/** 「新建相册」行:关弹层 → 登记创建意图 → 切到媒体 tab(onShow 消费后进创建态) */
function goCreateAlbum(): void {
publishVisible.value = false;
const kindMap: Record<"image" | "video" | "audio", "photo" | "video" | "audio"> = {
image: "photo",
video: "video",
audio: "audio",
};
mediaIntent.requestCreate(kindMap[publishKind.value]);
// 项目无原生 tabBar(自定义 app-tabbar),switchTab 会报 can not switch to no-tabBar page,
// 切 tab 与 app-tabbar.onChange 同套路用 reLaunch;意图由媒体页首显回调消费
uni.reLaunch({ url: pageUrl("mediaTab") });
}
async function actionPublish(targets: FileItem[]): Promise<void> {
const picked = targets.filter(f => f.kind !== "folder");
if (!picked.length) {
@@ -735,7 +774,7 @@ async function actionPublish(targets: FileItem[]): Promise<void> {
if (hasName) {
parts.push(code);
}
return { key: code, label, sub: parts.join(" · ") };
return { key: code, label, sub: parts.join(" · "), icon: publishRowIcon.value };
});
}
catch (error: any) {
@@ -886,7 +925,7 @@ const transferIconWhite = transferGlyph("#ffffff");
弹层 z-index 再高也压不住它,直接隐藏最干净(主流云盘同款交互) -->
<app-page :hide-nav="true"
:show-tabbar="atRoot && !actionFile && !selectMode && !folderDialogVisible && !renameVisible && !moveVisible && !shareVisible && !publishVisible">
<view class="file-page" :style="{ background: themeBackground }">
<view class="file-page" :style="{ background: themeGlowBackground }">
<!-- 上半屏整体固定,只有文件列表所在的 scroll-view 内部滚动(整页不滚) -->
<view class="file-page__head">
<page-navbar :title="navTitle" :show-back="!atRoot" :search-on="showSearch" :quick-on="showQuick"
@@ -976,9 +1015,10 @@ const transferIconWhite = transferGlyph("#ffffff");
<!-- 共享家人:共享大厅式抽屉(群组卡→逐级下钻→「共享到此」),多选文件一次批量共享 -->
<folder-share-picker :visible="shareVisible" :selected-count="shareTargets.length" @close="shareVisible = false"
@confirm="onShareConfirm" />
<!-- 发布:选专辑 -->
<!-- 发布:选专辑(确认键按类型叫「发布相册」等;附「新建相册」行跳媒体页创建态) -->
<option-picker :visible="publishVisible" :title="publishTitle" :items="publishItems" :loading="publishLoading"
:empty-text="t('FilePage.pickerEmpty')" @close="publishVisible = false" @confirm="onPublishConfirm" />
:empty-text="t('FilePage.pickerEmpty')" :confirm-text="publishConfirmText" :create-text="publishCreateText"
@close="publishVisible = false" @create="goCreateAlbum" @confirm="onPublishConfirm" />
<!-- 多选底部操作栏:常驻非模态(无遮罩),勾选与动作可交替进行;
动作集是旧系统多选子集——共享/发布按同媒体类型条件,重命名/移动仅单选 -->
<view v-if="selectMode" class="file-page__actions">
File diff suppressed because it is too large. Load diff
+312 -203
View File
@@ -12,11 +12,19 @@
import type { FamilyGroupEntity } from "@/data/auto/familyGroupEntity";
import { useLocale } from "uview-pro";
import { computed, ref } from "vue";
import { useAuthGuard, useFamilyGroups, useThemeGradient } from "@/composables";
import { useAuthGuard, useFamilyGroups, useThemeGradient, useThemeTokens } from "@/composables";
import { useLoginProfileStore } from "@/stores";
const { t } = useLocale();
const { themeBackground } = useThemeGradient();
// 主题派生色经 CSS 变量下发(与媒体页同套):头卡/徽章/主按钮/链接全部跟主题
const { primary, accentGradient, accentSoft, accentShadow } = useThemeTokens();
const themeVars = computed(() => ({
"--fg-primary": primary.value,
"--fg-gradient": accentGradient.value,
"--fg-soft": accentSoft.value,
"--fg-shadow": accentShadow.value,
}));
const profile = useLoginProfileStore();
const {
@@ -74,6 +82,20 @@ const navTitle = computed(() => {
return t("SharePage.title");
});
/** 详情区分区卡折叠态(与媒体页同款交互):默认全展开,点标题行切换 */
type FoldKey = "info" | "folders" | "requests" | "members" | "permissions";
const folded = ref<Record<FoldKey, boolean>>({
info: false,
folders: false,
requests: false,
members: false,
permissions: false,
});
function toggleFold(key: FoldKey): void {
folded.value[key] = !folded.value[key];
}
/** 申请加入入口:非新建、非管理员、非成员,且群开启了「可申请」 */
const canApply = computed(() =>
!creating.value
@@ -291,7 +313,7 @@ function groupDesc(group: FamilyGroupEntity): string {
<template>
<!-- 列表态显示 tabbar,进详情(或弹层展开)收起 -->
<app-page :hide-nav="true" :show-tabbar="!inDetail && !dialogVisible">
<view class="share-page" :style="{ background: themeBackground }">
<view class="share-page" :style="[{ background: themeBackground }, themeVars]">
<!-- ===== 列表态:公开群 + 我的群 ===== -->
<view v-if="!inDetail" class="share-page__body">
<view class="share-page__head">
@@ -365,42 +387,50 @@ function groupDesc(group: FamilyGroupEntity): string {
<!-- ===== 详情态:群管理 ===== -->
<view v-else class="share-page__body">
<view class="share-page__head">
<page-navbar :title="navTitle" :show-back="true" :show-transfer="false" :show-quick-toggle="false"
:show-search-toggle="false" @back="onBack" />
<hero-navbar :title="navTitle" @back="onBack" />
</view>
<view class="share-page__scroll-wrap">
<scroll-view scroll-y class="share-page__scroll">
<!-- 群信息头卡 -->
<!-- 群信息头卡(与媒体页同套):白底 + 主题浅底图标 + 编号灰字独立行 -->
<view class="detail-hero">
<view class="detail-hero__avatar">
<u-icon name="account-fill" size="56" color="#ffffff" />
<u-icon name="account-fill" size="64" :color="primary" />
</view>
<view class="detail-hero__main">
<text class="detail-hero__name">
{{ detail.FamilyGroup.FamilyLocalName || t("SharePage.unnamed") }}
</text>
<text class="detail-hero__code">
{{ detail.FamilyGroup.FamilyName }}
</text>
<view class="detail-hero__chips">
<text class="group-card__chip group-card__chip--code">
{{ detail.FamilyGroup.FamilyName }}
</text>
<text v-if="detail.IsOpen" class="group-card__chip">
<text v-if="detail.IsOpen" class="detail-hero__chip">
{{ t("SharePage.chipPublic") }}
</text>
<text v-if="detail.IsAdd" class="group-card__chip">
<text v-if="detail.IsAdd" class="detail-hero__chip">
{{ t("SharePage.chipJoinable") }}
</text>
<text v-if="isManager" class="group-card__chip group-card__chip--admin">
<text v-if="isManager" class="detail-hero__chip detail-hero__chip--admin">
{{ t("SharePage.chipManager") }}
</text>
<text v-else-if="isMember" class="group-card__chip">
<text v-else-if="isMember" class="detail-hero__chip detail-hero__chip--member">
{{ t("SharePage.chipMember") }}
</text>
</view>
</view>
<!-- 修改 / 解散:右上角圆形图标动作(与媒体页同套) -->
<view v-if="!isEdit && isManager" class="detail-hero__acts">
<view class="detail-hero__act" @tap="onAmend">
<u-icon name="edit-pen" size="34" :color="primary" />
</view>
<view class="detail-hero__act detail-hero__act--danger" @tap="onDissolve">
<u-icon name="trash" size="34" color="#f0413e" />
</view>
</view>
</view>
<!-- 动作栏:按角色/态显示 -->
<view class="detail-actions">
<!-- 动作栏:编辑态取消/保存;非成员可申请加入(修改/解散已升为头卡图标钮) -->
<view v-if="isEdit || canApply" class="detail-actions">
<template v-if="isEdit">
<view class="detail-actions__btn" @tap="onCancelEdit">
<text>{{ t("SharePage.cancel") }}</text>
@@ -409,178 +439,198 @@ function groupDesc(group: FamilyGroupEntity): string {
<text>{{ t("SharePage.save") }}</text>
</view>
</template>
<template v-else>
<view v-if="isManager" class="detail-actions__btn detail-actions__btn--primary"
@tap="onAmend">
<text>{{ t("SharePage.amend") }}</text>
</view>
<view v-if="canApply" class="detail-actions__btn detail-actions__btn--join"
@tap="onApplyJoin">
<text>{{ t("SharePage.applyJoin") }}</text>
</view>
<view v-if="isManager" class="detail-actions__btn detail-actions__btn--danger"
@tap="onDissolve">
<text>{{ t("SharePage.dissolve") }}</text>
</view>
</template>
</view>
<!-- 群组信息卡 -->
<view class="detail-card">
<text class="detail-card__title">
{{ t("SharePage.groupInfo") }}
</text>
<view class="detail-field">
<text class="detail-field__label">
{{ t("SharePage.groupName") }}
</text>
<input v-model="detail.FamilyGroup.FamilyLocalName" class="detail-field__input"
:disabled="!isEdit" :maxlength="50" :placeholder="t('SharePage.nameRequired')">
</view>
<view class="detail-field">
<text class="detail-field__label">
{{ t("SharePage.groupDesc") }}
</text>
<textarea v-model="detail.FamilyGroup.Notice" class="detail-field__textarea"
:disabled="!isEdit" :maxlength="256" :placeholder="t('SharePage.noDesc')" />
</view>
<view class="detail-field detail-field--row">
<text class="detail-field__label">
{{ t("SharePage.publicSetting") }}
</text>
<u-switch :model-value="detail.IsOpen" :disabled="!isEdit"
@change="(v: boolean) => detail.IsOpen = v" />
</view>
<view class="detail-field detail-field--row">
<text class="detail-field__label">
{{ t("SharePage.joinApply") }}
</text>
<u-switch :model-value="detail.IsAdd" :disabled="!isEdit"
@change="(v: boolean) => detail.IsAdd = v" />
<view v-else-if="canApply" class="detail-actions__btn detail-actions__btn--join"
@tap="onApplyJoin">
<text>{{ t("SharePage.applyJoin") }}</text>
</view>
</view>
<!-- 共享目录卡 -->
<!-- 群组信息卡:点标题行折叠/展开 -->
<view class="detail-card">
<view class="detail-card__head">
<view class="detail-card__head" @tap="toggleFold('info')">
<text class="detail-card__title">
{{ t("SharePage.groupInfo") }}
</text>
<u-icon :name="folded.info ? 'arrow-right' : 'arrow-down'" size="26" color="#8f959e" />
</view>
<view v-show="!folded.info">
<view class="detail-field">
<text class="detail-field__label">
{{ t("SharePage.groupName") }}
</text>
<input v-model="detail.FamilyGroup.FamilyLocalName" class="detail-field__input"
:disabled="!isEdit" :maxlength="50" :placeholder="t('SharePage.nameRequired')">
</view>
<view class="detail-field">
<text class="detail-field__label">
{{ t("SharePage.groupDesc") }}
</text>
<textarea v-model="detail.FamilyGroup.Notice" class="detail-field__textarea"
:disabled="!isEdit" :maxlength="256" :placeholder="t('SharePage.noDesc')" />
</view>
<view class="detail-field detail-field--row">
<text class="detail-field__label">
{{ t("SharePage.publicSetting") }}
</text>
<u-switch :model-value="detail.IsOpen" :disabled="!isEdit" :active-color="primary"
@change="(v: boolean) => detail.IsOpen = v" />
</view>
<view class="detail-field detail-field--row">
<text class="detail-field__label">
{{ t("SharePage.joinApply") }}
</text>
<u-switch :model-value="detail.IsAdd" :disabled="!isEdit" :active-color="primary"
@change="(v: boolean) => detail.IsAdd = v" />
</view>
</view>
</view>
<!-- 共享目录卡:点标题行折叠/展开,「新建」钮不触发折叠 -->
<view class="detail-card">
<view class="detail-card__head" @tap="toggleFold('folders')">
<text class="detail-card__title">
{{ t("SharePage.folders") }}
</text>
<text v-if="isEdit" class="detail-card__add" @tap="onNewFolder">
+ {{ t("SharePage.newFolder") }}
</text>
</view>
<view v-if="!detail.FamilyGroupFolders.length" class="detail-empty">
<text>{{ t("SharePage.noFolders") }}</text>
</view>
<view v-for="(folder, index) in detail.FamilyGroupFolders" :key="folder.FolderId || index"
class="detail-item">
<view class="detail-item__row">
<text class="detail-item__index">
{{ index + 1 }}
<view class="detail-card__head-acts">
<text v-if="isEdit" class="detail-card__add" @tap.stop="onNewFolder">
+ {{ t("SharePage.newFolder") }}
</text>
<u-icon :name="folded.folders ? 'arrow-right' : 'arrow-down'" size="26"
color="#8f959e" />
</view>
</view>
<view v-show="!folded.folders">
<view v-if="!detail.FamilyGroupFolders.length" class="detail-empty">
<text>{{ t("SharePage.noFolders") }}</text>
</view>
<view v-for="(folder, index) in detail.FamilyGroupFolders"
:key="folder.FolderId || index" class="detail-item">
<view class="detail-item__row">
<text class="detail-item__index">
{{ index + 1 }}
</text>
<input v-if="isEdit" v-model="folder.FolderLocalName" class="detail-item__input"
:maxlength="50" :placeholder="t('SharePage.folderPh')">
<text v-else class="detail-item__name">
{{ folder.FolderLocalName || t("SharePage.unnamed") }}
</text>
<view class="detail-item__acts">
<text v-if="!isEdit && folder.IsSharePermission" class="detail-item__link"
@tap="onAssignPermission(folder.FolderName)">
{{ t("SharePage.assign") }}
</text>
<text v-if="isEdit" class="detail-item__del" @tap="
onRemoveFolder(folder.FolderId, folder.FolderName)
">
{{ t("SharePage.delete") }}
</text>
</view>
</view>
<input v-if="isEdit" v-model="folder.Notice" class="detail-item__sub"
:maxlength="50" :placeholder="t('SharePage.notePh')">
<text v-else-if="folder.Notice" class="detail-item__sub detail-item__sub--text">
{{ folder.Notice }}
</text>
<input v-if="isEdit" v-model="folder.FolderLocalName" class="detail-item__input"
:maxlength="50" :placeholder="t('SharePage.folderPh')">
<text v-else class="detail-item__name">
{{ folder.FolderLocalName || t("SharePage.unnamed") }}
</text>
<view class="detail-item__acts">
<text v-if="!isEdit && folder.IsSharePermission" class="detail-item__link"
@tap="onAssignPermission(folder.FolderName)">
{{ t("SharePage.assign") }}
</text>
<text v-if="isEdit" class="detail-item__del" @tap="
onRemoveFolder(folder.FolderId, folder.FolderName)
">
{{ t("SharePage.delete") }}
</text>
</view>
</view>
<input v-if="isEdit" v-model="folder.Notice" class="detail-item__sub" :maxlength="50"
:placeholder="t('SharePage.notePh')">
<text v-else-if="folder.Notice" class="detail-item__sub detail-item__sub--text">
{{ folder.Notice }}
</text>
</view>
</view>
<!-- 待审成员(仅管理员)-->
<!-- 待审成员(仅管理员):点标题行折叠/展开 -->
<view v-if="isManager && detail.FamilyGroupRequests.length" class="detail-card">
<text class="detail-card__title">
{{ t("SharePage.requests") }}
</text>
<view v-for="(req, index) in detail.FamilyGroupRequests" :key="req.UserName || index"
class="detail-item">
<view class="detail-item__row">
<text class="detail-item__index">
{{ index + 1 }}
</text>
<text class="detail-item__name">
{{ req.UserLocalName || req.UserName }}
</text>
<view class="detail-item__acts">
<text class="detail-item__link" @tap="onApprove(req.UserName)">
{{ t("SharePage.approve") }}
</text>
<text class="detail-item__del" @tap="onReject(req.UserName)">
{{ t("SharePage.reject") }}
</text>
</view>
</view>
<text v-if="req.Notice" class="detail-item__sub detail-item__sub--text">
{{ req.Notice }}
<view class="detail-card__head" @tap="toggleFold('requests')">
<text class="detail-card__title">
{{ t("SharePage.requests") }}
</text>
<u-icon :name="folded.requests ? 'arrow-right' : 'arrow-down'" size="26"
color="#8f959e" />
</view>
<view v-show="!folded.requests">
<view v-for="(req, index) in detail.FamilyGroupRequests" :key="req.UserName || index"
class="detail-item">
<view class="detail-item__row">
<text class="detail-item__index">
{{ index + 1 }}
</text>
<text class="detail-item__name">
{{ req.UserLocalName || req.UserName }}
</text>
<view class="detail-item__acts">
<text class="detail-item__link" @tap="onApprove(req.UserName)">
{{ t("SharePage.approve") }}
</text>
<text class="detail-item__del" @tap="onReject(req.UserName)">
{{ t("SharePage.reject") }}
</text>
</view>
</view>
<text v-if="req.Notice" class="detail-item__sub detail-item__sub--text">
{{ req.Notice }}
</text>
</view>
</view>
</view>
<!-- 正式成员 -->
<!-- 正式成员:点标题行折叠/展开 -->
<view v-if="detail.FamilyGroupManagers.length" class="detail-card">
<text class="detail-card__title">
{{ t("SharePage.members") }}
</text>
<view v-for="(mem, index) in detail.FamilyGroupManagers" :key="mem.UserName || index"
class="detail-item">
<view class="detail-item__row">
<text class="detail-item__index">
{{ index + 1 }}
</text>
<text class="detail-item__name">
{{ mem.UserLocalName || mem.UserName }}
</text>
<view class="detail-item__acts">
<u-switch v-if="isEdit" :model-value="mem.IsManager" size="40"
:disabled="mem.UserName === detail.FamilyGroup.CreatedBy"
@change="() => toggleManager(mem.UserName)" />
<text v-else-if="mem.IsManager"
class="group-card__chip group-card__chip--admin">
{{ t("SharePage.chipManager") }}
<view class="detail-card__head" @tap="toggleFold('members')">
<text class="detail-card__title">
{{ t("SharePage.members") }}
</text>
<u-icon :name="folded.members ? 'arrow-right' : 'arrow-down'" size="26"
color="#8f959e" />
</view>
<view v-show="!folded.members">
<view v-for="(mem, index) in detail.FamilyGroupManagers" :key="mem.UserName || index"
class="detail-item">
<view class="detail-item__row">
<text class="detail-item__index">
{{ index + 1 }}
</text>
<text v-if="isEdit && mem.UserName !== detail.FamilyGroup.CreatedBy"
class="detail-item__del" @tap="onRemoveMember(mem.UserName)">
{{ t("SharePage.removeMember") }}
<text class="detail-item__name">
{{ mem.UserLocalName || mem.UserName }}
</text>
<view class="detail-item__acts">
<u-switch v-if="isEdit" :model-value="mem.IsManager" size="40"
:active-color="primary"
:disabled="mem.UserName === detail.FamilyGroup.CreatedBy"
@change="() => toggleManager(mem.UserName)" />
<text v-else-if="mem.IsManager"
class="detail-hero__chip detail-hero__chip--admin">
{{ t("SharePage.chipManager") }}
</text>
<text v-if="isEdit && mem.UserName !== detail.FamilyGroup.CreatedBy"
class="detail-item__del" @tap="onRemoveMember(mem.UserName)">
{{ t("SharePage.removeMember") }}
</text>
</view>
</view>
</view>
</view>
</view>
<!-- 目录权限(仅管理员)-->
<!-- 目录权限(仅管理员):点标题行折叠/展开 -->
<view v-if="isManager && detail.FamilyGroupPermissions.length" class="detail-card">
<text class="detail-card__title">
{{ t("SharePage.permissions") }}
</text>
<view v-for="(perm, index) in detail.FamilyGroupPermissions"
:key="`${perm.FolderName}-${perm.UserName}-${index}`" class="detail-item">
<view class="detail-item__row">
<text class="detail-item__index">
{{ index + 1 }}
</text>
<text class="detail-item__name">
{{ perm.FolderLocalName || perm.FolderName }}
</text>
<text class="detail-item__sub detail-item__sub--inline">
{{ perm.UserLocalName || perm.UserName }}
</text>
<view class="detail-card__head" @tap="toggleFold('permissions')">
<text class="detail-card__title">
{{ t("SharePage.permissions") }}
</text>
<u-icon :name="folded.permissions ? 'arrow-right' : 'arrow-down'" size="26"
color="#8f959e" />
</view>
<view v-show="!folded.permissions">
<view v-for="(perm, index) in detail.FamilyGroupPermissions"
:key="`${perm.FolderName}-${perm.UserName}-${index}`" class="detail-item">
<view class="detail-item__row">
<text class="detail-item__index">
{{ index + 1 }}
</text>
<text class="detail-item__name">
{{ perm.FolderLocalName || perm.FolderName }}
</text>
<text class="detail-item__sub detail-item__sub--inline">
{{ perm.UserLocalName || perm.UserName }}
</text>
</view>
</view>
</view>
</view>
@@ -663,8 +713,8 @@ function groupDesc(group: FamilyGroupEntity): string {
padding: 8rpx 28rpx;
border-radius: 999rpx;
font-size: 26rpx;
color: #2b8fff;
background-color: #eaf4ff;
color: var(--fg-primary, #2b8fff);
background-color: var(--fg-soft, #eaf4ff);
}
/* ===== 列表卡片 ===== */
@@ -686,7 +736,7 @@ function groupDesc(group: FamilyGroupEntity): string {
width: 88rpx;
height: 88rpx;
border-radius: 20rpx;
background: linear-gradient(135deg, #4a9dff, #2b8fff);
background: var(--fg-gradient, linear-gradient(135deg, #4a9dff, #2b8fff));
flex-shrink: 0;
}
@@ -731,8 +781,8 @@ function groupDesc(group: FamilyGroupEntity): string {
}
.group-card__chip--code {
color: #2b8fff;
background-color: #eaf4ff;
color: var(--fg-primary, #2b8fff);
background-color: var(--fg-soft, #eaf4ff);
}
.group-card__chip--admin {
@@ -746,25 +796,50 @@ function groupDesc(group: FamilyGroupEntity): string {
color: #bfc3c9;
}
/* ===== 详情头卡 + 动作栏 ===== */
/* ===== 详情头卡 + 动作栏(与媒体页同套:白底卡 + 主题点缀) ===== */
.detail-hero {
position: relative;
display: flex;
align-items: center;
gap: 24rpx;
gap: 28rpx;
margin: 0 4rpx 20rpx;
padding: 32rpx 28rpx;
border-radius: 24rpx;
background: linear-gradient(135deg, #4a9dff, #2b8fff);
padding: 40rpx 32rpx;
border-radius: 32rpx;
background-color: #ffffff;
}
/* 头卡右上角圆形动作钮(同媒体页):修改=浅灰底主题图标,解散=浅红底红图标 */
.detail-hero__acts {
position: absolute;
top: 28rpx;
right: 28rpx;
display: flex;
align-items: center;
gap: 20rpx;
}
.detail-hero__act {
display: flex;
align-items: center;
justify-content: center;
width: 76rpx;
height: 76rpx;
border-radius: 50%;
background-color: #f2f3f5;
&--danger {
background-color: #ffeceb;
}
}
.detail-hero__avatar {
display: flex;
align-items: center;
justify-content: center;
width: 96rpx;
height: 96rpx;
border-radius: 24rpx;
background-color: rgba(255, 255, 255, 0.24);
width: 140rpx;
height: 140rpx;
border-radius: 32rpx;
background-color: var(--fg-soft, #eaf4ff);
flex-shrink: 0;
}
@@ -773,22 +848,48 @@ function groupDesc(group: FamilyGroupEntity): string {
flex: 1;
min-width: 0;
flex-direction: column;
gap: 12rpx;
gap: 10rpx;
}
.detail-hero__name {
font-size: 36rpx;
font-weight: 600;
color: #ffffff;
font-size: 40rpx;
font-weight: 700;
color: #1f2329;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* 群编号独立成行:灰字说明(同媒体页专辑编号) */
.detail-hero__code {
font-size: 26rpx;
color: #8f959e;
}
.detail-hero__chips {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
gap: 12rpx;
margin-top: 6rpx;
}
/* 头卡胶囊:公开/可申请=主题浅底主题字,管理员=黄底橙字,成员=浅灰 */
.detail-hero__chip {
padding: 6rpx 22rpx;
border-radius: 12rpx;
font-size: 24rpx;
color: var(--fg-primary, #2b8fff);
background-color: var(--fg-soft, #eaf4ff);
&--admin {
color: #ff9500;
background-color: #fff4e0;
}
&--member {
color: #5c626b;
background-color: #f2f3f5;
}
}
.detail-actions {
@@ -797,31 +898,32 @@ function groupDesc(group: FamilyGroupEntity): string {
margin: 0 4rpx 20rpx;
}
/* 动作钮统一规格(同媒体页/option-picker):等高圆角矩形 + 细描边,主钮纯色跟主题 */
.detail-actions__btn {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
height: 80rpx;
border-radius: 999rpx;
font-size: 28rpx;
height: 96rpx;
border: 2rpx solid #e5e6eb;
border-radius: 24rpx;
font-size: 32rpx;
color: #5c626b;
background-color: #ffffff;
&--primary {
border-color: transparent;
font-weight: 600;
color: #ffffff;
background-color: #2b8fff;
background-color: var(--fg-primary, #2b8fff);
}
&--join {
border-color: transparent;
font-weight: 600;
color: #ffffff;
background-color: #21b169;
}
&--danger {
color: #f0413e;
background-color: #ffeceb;
}
}
/* ===== 分区卡片 ===== */
@@ -838,15 +940,22 @@ function groupDesc(group: FamilyGroupEntity): string {
justify-content: space-between;
}
/* 标题行右侧一组:新建钮 + 折叠箭头 */
.detail-card__head-acts {
display: flex;
align-items: center;
gap: 28rpx;
}
.detail-card__title {
font-size: 30rpx;
font-weight: 600;
font-size: 34rpx;
font-weight: 700;
color: #1f2329;
}
.detail-card__add {
font-size: 26rpx;
color: #2b8fff;
color: var(--fg-primary, #2b8fff);
}
.detail-field {
@@ -871,25 +980,25 @@ function groupDesc(group: FamilyGroupEntity): string {
.detail-item__input,
.detail-item__sub {
box-sizing: border-box;
padding: 0 20rpx;
height: 68rpx;
padding: 0 24rpx;
height: 88rpx;
border: 2rpx solid #e6e7ea;
border-radius: 14rpx;
font-size: 26rpx;
border-radius: 16rpx;
font-size: 28rpx;
color: #1f2329;
background-color: #f7f8fa;
background-color: #ffffff;
}
.detail-field__textarea {
box-sizing: border-box;
width: 100%;
min-height: 120rpx;
padding: 16rpx 20rpx;
min-height: 200rpx;
padding: 20rpx 24rpx;
border: 2rpx solid #e6e7ea;
border-radius: 14rpx;
font-size: 26rpx;
border-radius: 16rpx;
font-size: 28rpx;
color: #1f2329;
background-color: #f7f8fa;
background-color: #ffffff;
}
.detail-empty {
@@ -951,7 +1060,7 @@ function groupDesc(group: FamilyGroupEntity): string {
.detail-item__link {
font-size: 24rpx;
color: #2b8fff;
color: var(--fg-primary, #2b8fff);
}
.detail-item__del {