108108 <!-- 页面内容 -->
109109 <!-- 有数据 -->
110110 <div class =" subs-list-wrapper" >
111- <div v-if =" hasFiles" >
112- <draggable
113- v-model =" files"
114- item-key =" name"
115- :scroll-sensitivity =" 200"
116- :force-fallback =" true"
117- :scroll-speed =" 8"
118- :scroll =" true"
119- v-bind =" {
120- animation: 200,
121- disabled: false,
122- delay: 200,
123- chosenClass: 'chosensub',
124- handle: 'div',
125- }"
126- @change =" changeSort('files', files)"
127- @start =" handleDragStart(files)"
128- @end =" handleDragEnd(files)"
129- >
130- <template #item =" { element } " >
131- <div :key =" element.name" class =" draggable-item" >
132- <FileListItem
133- :file =" element"
134- type =" file"
135- :disabled =" swipeDisabled"
136- @share =" handleShare"
137- />
138- </div >
139- </template >
140- </draggable >
111+ <div v-if =" tags && tags.length > 0" ref =" radioWrapperRef" class =" radio-wrapper" >
112+ <!-- <nut-radiogroup v-model="tag" direction="horizontal"> -->
113+ <!-- <nut-radio v-for="i in tags" shape="button" :label="String(i.value)">{{ i.label }}</nut-radio> -->
114+ <span v-for =" i in tags" class =" tag" :class =" { 'current': i.value === tag }" @click =" setTag(i.value)" >{{ i.label }}</span >
115+ <!-- </nut-radiogroup> -->
116+ </div >
117+ <div class =" subs-list-container" :style =" { paddingTop: `${radioWrapperHeight}px` }" >
118+ <div v-if =" hasFiles" >
119+ <draggable
120+ v-model =" files"
121+ item-key =" name"
122+ :scroll-sensitivity =" 200"
123+ :force-fallback =" true"
124+ :scroll-speed =" 8"
125+ :scroll =" true"
126+ v-bind =" {
127+ animation: 200,
128+ disabled: false,
129+ delay: 200,
130+ chosenClass: 'chosensub',
131+ handle: 'div',
132+ }"
133+ @change =" changeSort('files', files)"
134+ @start =" handleDragStart(files)"
135+ @end =" handleDragEnd(files)"
136+ >
137+ <template #item =" { element } " >
138+ <div v-show =" shouldShowElement(element)" :key =" element.name" class =" draggable-item" >
139+ <FileListItem
140+ :file =" element"
141+ type =" file"
142+ :disabled =" swipeDisabled"
143+ @share =" handleShare"
144+ />
145+ </div >
146+ </template >
147+ </draggable >
148+ </div >
141149 </div >
142150 </div >
143151 <!-- 没有数据 -->
194202
195203<script lang="ts" setup>
196204import { storeToRefs } from " pinia" ;
197- import { ref , toRaw , onMounted } from " vue" ;
205+ import { ref , toRaw , onMounted , computed , watch , nextTick } from " vue" ;
198206import draggable from " vuedraggable" ;
199207import SharePopup from " ./share/SharePopup.vue" ;
200208
@@ -234,9 +242,6 @@ const editFile = () => {
234242 addSubBtnIsVisible .value = true ;
235243};
236244
237- onMounted (() => {
238- methodStore .registerMethod (" addFile" , editFile );
239- });
240245const { env } = useBackend ();
241246const { showNotify } = useAppNotifyStore ();
242247const subApi = useSubsApi ();
@@ -263,6 +268,69 @@ const swipeDisabled = ref(false);
263268const touchStartY = ref (null );
264269const touchStartX = ref (null );
265270const sortFailed = ref (false );
271+ const hasUntagged = ref (false );
272+ const getTag = () => {
273+ return localStorage .getItem (' file-tag' ) || ' all' ;
274+ };
275+ const tag = ref (getTag ());
276+ const tags = computed (() => {
277+ if (! hasFiles .value ) return [];
278+ const set = new Set ();
279+ files .value .forEach (sub => {
280+
281+ if (Array .isArray (sub .tag ) && sub .tag .length > 0 ) {
282+ sub .tag .forEach (i => {
283+ set .add (i );
284+ });
285+ } else {
286+ hasUntagged .value = true ;
287+ }
288+ });
289+
290+ let tags: any [] = Array .from (set );
291+ // if(tags.length === 0) return [];
292+ tags = tags .map (i => ({ label: i , value: i }));
293+
294+ const result = [{ label: t (" specificWord.all" ), value: " all" }, ... tags ];
295+ if (tags .length > 0 && hasUntagged .value ) result .push ({ label: t (" specificWord.untagged" ), value: " untagged" });
296+
297+ if (! result .find (i => i .value === tag .value )) {
298+ tag .value = ' all' ;
299+ }
300+ return result ;
301+ });
302+ const radioWrapperRef = ref (null );
303+ const radioWrapperHeight = ref (0 );
304+
305+ // 更新标签栏高度
306+ const updateRadioWrapperHeight = () => {
307+ nextTick (() => {
308+ if (radioWrapperRef .value ) {
309+ radioWrapperHeight .value = radioWrapperRef .value .offsetHeight ;
310+ } else {
311+ radioWrapperHeight .value = 0 ;
312+ }
313+ });
314+ };
315+ const isPWA = ref (
316+ (window .matchMedia (" (display-mode: standalone)" ).matches &&
317+ ! / Android/ .test (navigator .userAgent )) ||
318+ false
319+ );
320+ const navBarHeight = computed (() => {
321+ return isPWA .value ? ` ${44 + 32 + bottomSafeArea .value }px ` : ` ${44 + 12 + bottomSafeArea .value }px ` ;
322+ });
323+
324+ watch (tag , () => {
325+ updateRadioWrapperHeight ();
326+ });
327+
328+ watch (() => tags .value , () => {
329+ updateRadioWrapperHeight ();
330+ }, { deep: true , immediate: true });
331+ onMounted (() => {
332+ methodStore .registerMethod (" addFile" , editFile );
333+ });
266334const onTouchStart = (event : TouchEvent ) => {
267335 touchStartY .value = Math .abs (event .touches [0 ].clientY );
268336 touchStartX .value = Math .abs (event .touches [0 ].clientX );
@@ -402,6 +470,30 @@ const importTips = () => {
402470 lockScroll: false ,
403471 });
404472};
473+ const scrollToTop = () => {
474+ const scrollPosition = 0 ;
475+
476+ window .scrollTo ({
477+ top: scrollPosition ,
478+ behavior: " smooth"
479+ });
480+ };
481+
482+ const setTag = (current ) => {
483+ tag .value = current ;
484+ if (current === ' all' ) {
485+ localStorage .removeItem (' file-tag' );
486+ } else {
487+ localStorage .setItem (' file-tag' , current );
488+ }
489+ // 增加滚动到顶部
490+ scrollToTop ();
491+ };
492+ const shouldShowElement = (element ) => {
493+ if (tag .value === ' all' ) return true ;
494+ if (tag .value === ' untagged' ) return ! Array .isArray (element .tag ) || element .tag .length === 0 ;
495+ return element .tag ?.includes (tag .value );
496+ };
405497 </script >
406498
407499<style lang="scss" scoped>
@@ -595,5 +687,37 @@ const importTips = () => {
595687 width : calc (100% - 1.5rem );
596688 margin-left : auto ;
597689 margin-right : auto ;
690+ .radio-wrapper {
691+ box-sizing : border-box ;
692+ width : 100% ;
693+ display : flex ;
694+ flex-wrap : wrap ;
695+ position : fixed ;
696+ padding : 10px ;
697+ top : v-bind (navBarHeight );
698+ z-index : 10 ;
699+ backdrop-filter : blur (var (--nav-bar-blur ));
700+ -webkit-backdrop-filter : blur (var (--nav-bar-blur ));
701+ background : var (--nav-bar-color );
702+ @include centered-fixed-container ;
703+ @media screen and (min-width : 768px ) {
704+ border-radius : var (--item-card-radios );
705+ overflow : hidden ;
706+ }
707+ .tag {
708+ font-size : 12px ;
709+ color : var (--second-text-color );
710+ margin : 0px 5px ;
711+ padding : 7.5px 2.5px 4px ;
712+ cursor : pointer ;
713+ -webkit-user-select : none ;
714+ user-select : none ;
715+ border-bottom : 1px solid transparent ;
716+ }
717+ .current {
718+ border-bottom : 1px solid var (--primary-color );
719+ color : var (--primary-color );
720+ }
721+ }
598722}
599723 </style >
0 commit comments