Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ce7440e

Browse files
committed
feat: 文件支持标签
1 parent 994c903 commit ce7440e

File tree

5 files changed

+287
-56
lines changed

5 files changed

+287
-56
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store-front-end",
3-
"version": "2.15.66",
3+
"version": "2.15.68",
44
"private": true,
55
"scripts": {
66
"dev": "vite --host",

src/components/FileListItem.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,15 @@
4747
<div class="sub-item-title-wrapper">
4848
<h3 v-if="!appearanceSetting.isSimpleMode" class="sub-item-title">
4949
{{ displayName || name }}
50+
<span v-for="i in tag" :key="i" class="tag">
51+
<nut-tag>{{ i }}</nut-tag>
52+
</span>
5053
</h3>
5154
<h3 v-else class="sub-item-title" style="color: var(--primary-text-color); font-size: 16px">
5255
{{ displayName || name }}
56+
<span v-for="i in tag" :key="i" class="tag">
57+
<nut-tag>{{ i }}</nut-tag>
58+
</span>
5359
</h3>
5460

5561
<!-- onClickCopyLink 拷贝 -->
@@ -289,6 +295,7 @@
289295
props[props.type].displayName || props[props.type]['display-name'];
290296
291297
const name = props[props.type].name;
298+
const tag = props[props.type].tag;
292299
const remark = props[props.type].remark;
293300
const remarkText = computed(() => {
294301
if (remark) {
@@ -587,6 +594,10 @@
587594
overflow: hidden;
588595
font-size: 16px;
589596
color: var(--primary-text-color);
597+
vertical-align: middle;
598+
}
599+
.tag {
600+
margin: 0 2px;
590601
}
591602
.compare-sub-link,
592603
.share-sub-link,

src/components/TagPopup.vue

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ import { useI18n } from "vue-i18n";
8585
8686
const { t } = useI18n();
8787
const subsStore = useSubsStore();
88-
const { hasSubs, hasCollections, subs, collections } = storeToRefs(subsStore);
88+
const { hasSubs, hasCollections, subs, collections, hasFiles, files } = storeToRefs(subsStore);
8989
const hasUntagged = ref(false);
9090
const props = defineProps({
9191
visible: {
@@ -96,6 +96,10 @@ const props = defineProps({
9696
type: String,
9797
default: '',
9898
},
99+
type: {
100+
type: String,
101+
default: 'subCol',
102+
},
99103
});
100104
const isVisible = ref(props.visible);
101105
const keyword = ref("");
@@ -135,27 +139,40 @@ const allTagsList = computed(() => {
135139
return allTags.value.filter(i => i.label.indexOf(keyword.value) !== -1)
136140
})
137141
const getTags = () => {
138-
if(!hasSubs.value && !hasCollections.value) return []
139-
// 从 subs 和 collections 中获取所有的 tag, 去重
142+
if(props.type === 'file' && !hasFiles.value) return []
143+
if(props.type === 'subCol' && !hasSubs.value && !hasCollections.value) return []
140144
const set = new Set()
141-
subs.value.forEach(sub => {
142-
if (Array.isArray(sub.tag) && sub.tag.length > 0) {
143-
sub.tag.forEach(i => {
144-
set.add(i)
145-
});
146-
} else {
147-
hasUntagged.value = true
148-
}
149-
})
150-
collections.value.forEach(col => {
151-
if (Array.isArray(col.tag) && col.tag.length > 0) {
152-
col.tag.forEach(i => {
153-
set.add(i)
154-
});
155-
} else {
156-
hasUntagged.value = true
157-
}
158-
})
145+
if (props.type === 'subCol') {
146+
// 从 subs 和 collections 中获取所有的 tag, 去重
147+
subs.value.forEach(sub => {
148+
if (Array.isArray(sub.tag) && sub.tag.length > 0) {
149+
sub.tag.forEach(i => {
150+
set.add(i)
151+
});
152+
} else {
153+
hasUntagged.value = true
154+
}
155+
})
156+
collections.value.forEach(col => {
157+
if (Array.isArray(col.tag) && col.tag.length > 0) {
158+
col.tag.forEach(i => {
159+
set.add(i)
160+
});
161+
} else {
162+
hasUntagged.value = true
163+
}
164+
})
165+
} else if (props.type === 'file') {
166+
files.value.forEach(file => {
167+
if (Array.isArray(file.tag) && file.tag.length > 0) {
168+
file.tag.forEach(i => {
169+
set.add(i)
170+
});
171+
} else {
172+
hasUntagged.value = true
173+
}
174+
})
175+
}
159176
160177
let tags: any[] = Array.from(set)
161178
if(tags.length === 0) return []

src/views/File.vue

Lines changed: 158 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -108,36 +108,44 @@
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
<!-- 没有数据 -->
@@ -194,7 +202,7 @@
194202

195203
<script lang="ts" setup>
196204
import { storeToRefs } from "pinia";
197-
import { ref, toRaw, onMounted } from "vue";
205+
import { ref, toRaw, onMounted, computed, watch, nextTick } from "vue";
198206
import draggable from "vuedraggable";
199207
import 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-
});
240245
const { env } = useBackend();
241246
const { showNotify } = useAppNotifyStore();
242247
const subApi = useSubsApi();
@@ -263,6 +268,69 @@ const swipeDisabled = ref(false);
263268
const touchStartY = ref(null);
264269
const touchStartX = ref(null);
265270
const 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+
});
266334
const 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

Comments
 (0)