还原了搜索栏布局、样式;

还原了文件夹管理布局、样式;
还原了头部布局、样式;
This commit is contained in:
User
2025-10-10 15:01:36 +08:00
parent e40288e8ef
commit f68cd4e0fd
10 changed files with 469 additions and 420 deletions

View File

@@ -1,221 +1,77 @@
<template>
<div
@click="onPress"
class="folder-item-container"
:class="{ 'folder-item-selected': isSelected, 'folder-item-pressed': isPressed }"
@mousedown="handleMouseDown"
@mouseup="handleMouseUp"
@mouseleave="handleMouseLeave"
>
<div class="folder-item-content">
<!-- Hidden folder icon (matches native implementation) -->
<img
v-if="folderIconSrc"
:src="folderIconSrc"
class="folder-item-icon"
alt="folder icon"
style="visibility: hidden;"
/>
<ion-icon
v-else
:icon="folderIcon"
class="folder-item-icon"
style="visibility: hidden;"
></ion-icon>
<!-- Checkbox for selection (visible when isSelected is true) -->
<img
v-if="isSelected"
src="/assets/icons/drawable-xxhdpi/icon_folder_checked.png"
class="folder-item-checkbox"
alt="selected"
/>
<img
v-else
src="/assets/icons/drawable-xxhdpi/icon_folder_unchecked.png"
class="folder-item-checkbox"
alt="not selected"
/>
<div class="folder-item-text-container">
<div class="folder-item-name">
{{ name }}
</div>
<div class="folder-item-count">
{{ noteCount }} 条便签
</div>
</div>
<!-- Selected indicator (visible when isSelected is true) -->
<img
v-if="isSelected"
src="/assets/icons/drawable-xxhdpi/folder_selected.png"
class="folder-item-selected-icon"
alt="selected"
/>
</div>
<div @click="onPress" class="code-fun-flex-row code-fun-items-center code-fun-relative folder-item">
<img class="folder-icon" :src="iconSrc" />
<span class="folder-name">{{ name }}</span>
<span class="folder-count">{{ noteCount }}</span>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { folder, star, trash, document } from 'ionicons/icons';
import { computed } from 'vue'
const props = defineProps({
id: {
type: String,
required: true
required: true,
},
name: {
type: String,
required: true
required: true,
},
noteCount: {
type: Number,
required: true
required: true,
},
onPress: {
type: Function,
required: true
required: true,
},
isSelected: {
type: Boolean,
default: false
}
});
default: false,
},
})
const isPressed = ref(false);
const folderIcon = computed(() => {
const iconSrc = computed(() => {
switch (props.id) {
case 'all':
return folder;
return 'assets/icons/drawable-xxhdpi/icon_folder_all.png'
case 'starred':
return star;
return 'assets/icons/drawable-xxhdpi/icon_folder_favorite.png'
case 'trash':
return trash;
return 'assets/icons/drawable-xxhdpi/icon_folder_trash.png'
case 'archive':
return 'assets/icons/drawable-xxhdpi/icon_folder_document.png'
default:
return document;
return 'assets/icons/drawable-xxhdpi/icon_folder_document.png'
}
});
const folderIconSrc = computed(() => {
switch (props.id) {
case 'all':
return '/assets/icons/drawable-xxhdpi/sidebar_folder_icon_all.png';
case 'starred':
return '/assets/icons/drawable-xxhdpi/sidebar_folder_icon_favorite.png';
case 'trash':
return '/assets/icons/drawable-xxhdpi/sidebar_folder_icon_trash.png';
default:
return '/assets/icons/drawable-xxhdpi/sidebar_folder_icon_document.png';
}
});
const handleMouseDown = () => {
isPressed.value = true;
};
const handleMouseUp = () => {
isPressed.value = false;
};
const handleMouseLeave = () => {
isPressed.value = false;
};
})
</script>
<style scoped>
.folder-item-container {
position: relative;
min-height: 44px;
background-color: var(--background-card);
cursor: pointer;
overflow: hidden;
.folder-item {
padding: 0.3rem 0;
background-color: #00000000;
}
.folder-item-container.folder-item-selected {
background-color: var(--folder-item-selected);
}
.folder-item-container.folder-item-pressed {
background-color: var(--black-05);
transform: scale(0.99);
}
.folder-item-container.folder-item-pressed::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('/assets/icons/drawable-xxhdpi/folder_item_pressed_bg.png') repeat;
background-size: 100% 100%;
opacity: 0.5;
pointer-events: none;
transition: opacity 0.2s ease;
}
.folder-item-content {
display: flex;
align-items: center;
width: 100%;
height: 44px;
padding: 0 16px;
position: relative;
}
.folder-item-icon {
width: 30px;
height: 30px;
color: var(--folder-name);
.folder-icon {
width: 1.8rem;
height: 1.8rem;
flex-shrink: 0;
margin-left: 1px;
padding-left: 11px;
margin-inline: 0.3rem;
}
.folder-item-icon[src] {
width: 30px;
height: 30px;
object-fit: contain;
.folder-name {
font-size: 0.9rem;
line-height: 1.52rem;
color: #9b9b9b;
}
.folder-item-checkbox {
width: 30px;
height: 30px;
flex-shrink: 0;
margin-left: 10px;
.folder-count {
font-size: 0.8rem;
line-height: 1.16rem;
margin-left: 0.7rem;
margin-top: 0.2rem;
color: #b8b8b8;
}
.folder-item-selected-icon {
width: 30px;
height: 30px;
margin-left: auto;
flex-shrink: 0;
margin-right: 0;
}
.folder-item-text-container {
flex: 1;
margin-left: 10px;
margin-right: 6px;
}
.folder-item-name {
font-size: 15px;
font-weight: normal;
color: var(--folder-name);
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.folder-item-count {
font-size: 12px;
color: var(--folder-count);
line-height: 1.2;
margin-top: 2px;
}
</style>
</style>

View File

@@ -0,0 +1,135 @@
<template>
<div class="code-fun-flex-col page">
<!-- 全部便签文件夹项 -->
<FolderItem id="all" name="全部便签" :noteCount="allCount" :isSelected="selectedFolder === 'all'" :onPress="handleAllClick" />
<!-- 加星便签文件夹项 -->
<FolderItem id="starred" name="加星便签" :noteCount="starredCount" :isSelected="selectedFolder === 'starred'" :onPress="handleStarredClick" />
<!-- 回收站文件夹项 -->
<FolderItem id="trash" name="回收站" :noteCount="trashCount" :isSelected="selectedFolder === 'trash'" :onPress="handleTrashClick" />
<!-- 同步信息区域 -->
<div class="code-fun-flex-col code-fun-justify-start section_7">
<div class="code-fun-flex-row code-fun-justify-between code-fun-items-center section_8">
<div class="code-fun-flex-row code-fun-items-center">
<img class="code-fun-shrink-0 image_7" :src="`assets/icons/drawable-xxhdpi/btn_edit_folder.png`" />
<span class="text_11 code-fun-ml-10">上次同步:{{ lastSyncTime }}</span>
</div>
<div class="code-fun-flex-row code-fun-items-center">
<img class="image_8 code-fun-ml-12" :src="`assets/icons/drawable-xxhdpi/btn_add_folder.png`" @click="handleAddFolder" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import FolderItem from './FolderItem.vue'
const props = defineProps({
allCount: {
type: Number,
default: 0,
},
starredCount: {
type: Number,
default: 0,
},
trashCount: {
type: Number,
default: 0,
},
archiveCount: {
type: Number,
default: 0,
},
selectedFolder: {
type: String,
default: '',
},
lastSyncTime: {
type: String,
default: '10/10上午9:28',
},
onAllClick: {
type: Function,
default: null,
},
onStarredClick: {
type: Function,
default: null,
},
onTrashClick: {
type: Function,
default: null,
},
onArchiveClick: {
type: Function,
default: null,
},
onAddFolder: {
type: Function,
default: null,
},
})
const handleAllClick = () => {
if (props.onAllClick) {
props.onAllClick()
}
}
const handleStarredClick = () => {
if (props.onStarredClick) {
props.onStarredClick()
}
}
const handleTrashClick = () => {
if (props.onTrashClick) {
props.onTrashClick()
}
}
const handleArchiveClick = () => {
if (props.onArchiveClick) {
props.onArchiveClick()
}
}
const handleAddFolder = event => {
// 阻止事件冒泡到父元素
event.stopPropagation()
if (props.onAddFolder) {
props.onAddFolder()
}
}
</script>
<style lang="less" scoped>
.page {
.section_7 {
margin-top: 2rem;
background-color: #00000000;
.section_8 {
padding: 0.29rem 0.92rem;
background-color: #f4f4f4;
border: solid 0.063rem #f0ece7;
.image_7,
.image_8 {
border-radius: 0.63rem;
width: 2rem;
height: 2rem;
object-fit: contain;
}
.text_11 {
color: #cacaca;
font-size: 0.7rem;
line-height: 1.16rem;
}
}
}
}
</style>

View File

@@ -1,22 +1,18 @@
<template>
<div class="code-fun-flex-row code-fun-items-center component">
<!-- 左侧图标 -->
<img v-if="leftType == 'settings'" class="left-icon" src="/assets/icons/drawable-xxhdpi/btn_settings.png" @click="handleLeftAction" />
<img v-else class="left-icon" src="/assets/icons/drawable-xxhdpi/btn_back.png" @click="handleLeftAction" />
<img class="left-icon" :src="leftIconSource" @click="handleLeftAction" />
<!-- 标题区域 -->
<div class="title-container" @click="handleTitlePress" v-if="leftType == 'settings'">
<div class="title-container" @click="handleTitlePress">
<span class="text">{{ title }}</span>
<!-- 文件夹展开图标 -->
<img class="folder-icon" :src="folderExpanded ? '/assets/icons/drawable-xxhdpi/folder_title_arrow_pressed.png' : '/assets/icons/drawable-xxhdpi/folder_title_arrow_normal.png'" @click.stop="handleFolderToggle" />
</div>
<div class="title-container" v-else>
<span class="text">{{ title }}</span>
<img v-if="showFolderIcon" class="folder-icon" :src="folderExpanded ? '/assets/icons/drawable-xxhdpi/folder_title_arrow_pressed.png' : '/assets/icons/drawable-xxhdpi/folder_title_arrow_normal.png'" @click.stop="handleFolderToggle" />
</div>
<!-- 右侧操作按钮 -->
<img v-if="actionIcon === 'create'" class="image_4" src="/assets/icons/drawable-xxhdpi/btn_create.png" @click="handleAction" />
<i class="image_4" v-else></i>
<div v-else class="image_4-placeholder"></div>
</div>
</template>
@@ -77,33 +73,14 @@ const folderExpanded = computed(() => {
return props.isFolderExpanded !== undefined ? props.isFolderExpanded : localFolderExpanded.value
})
const actionIconSource = computed(() => {
// 根据actionIcon属性返回对应的图标路径
switch (props.actionIcon) {
case 'settings':
return '/assets/icons/drawable-xxhdpi/btn_settings.png'
case 'create':
return '/assets/icons/drawable-xxhdpi/btn_create.png'
default:
return null
}
const showFolderIcon = computed(() => {
// 只有当leftType为settings且有onFolderToggle回调时才显示文件夹图标
return props.leftType === 'settings' && props.onFolderToggle
})
const leftIconSource = computed(() => {
// 根据leftIcon属性返回对应的图标路径
switch (props.leftIcon) {
case 'folder':
// 文件夹图标根据展开状态切换
return folderExpanded.value ? '/assets/icons/drawable-xxhdpi/folder_title_arrow_pressed.png' : '/assets/icons/drawable-xxhdpi/folder_title_arrow_normal.png'
case 'back':
// 返回图标
return '/assets/icons/drawable-xxhdpi/btn_back.png'
case 'settings':
// 设置图标
return '/assets/icons/drawable-xxhdpi/btn_settings.png'
default:
return null
}
// 根据leftType属性返回对应的图标路径
return props.leftType === 'settings' ? '/assets/icons/drawable-xxhdpi/btn_settings.png' : '/assets/icons/drawable-xxhdpi/btn_back.png'
})
const handleFolderToggle = () => {
@@ -146,10 +123,10 @@ const handleTitlePress = () => {
<style scoped lang="less">
.component {
padding: 2rem 0.72rem 0.5rem 0.72rem;
padding: 2rem 0.72rem 0.5rem;
background-color: #00000000;
background-image: url(https://codefun-proj-user-res-1256085488.cos.ap-guangzhou.myqcloud.com/686f20ecd54496f19f54e801/68e862ab9520a30011f388ff/17600644443142372133.png);
background-size: cover;
background-image: url(assets/icons/drawable-xxhdpi/action_bar_default.png);
background-size: 100% 100%;
background-position: 0% 0%;
display: flex;
justify-content: space-between;
@@ -162,6 +139,11 @@ const handleTitlePress = () => {
cursor: pointer;
}
.image_4-placeholder {
width: 1.7rem;
height: 1.7rem;
}
.title-container {
display: flex;
align-items: center;

View File

@@ -0,0 +1,104 @@
<template>
<div class="code-fun-flex-col code-fun-justify-start section_3">
<div class="view">
<div class="image-wrapper">
<!-- 搜索图标 -->
<img class="image_7" :src="`assets/icons/drawable-xxhdpi/search_bar_left_icon.png`" />
<!-- 搜索输入框 -->
<input ref="searchInput" type="input" placeholder="搜索便签..." :value="modelValue" @input="handleInput" @keydown.enter="handleSearch" @focus="handleFocus" @blur="handleBlur" />
<!-- 清除按钮 -->
<img v-if="modelValue && modelValue.length > 0" class="clear-button" :src="`assets/icons/drawable-xxhdpi/search_clear_normal.png`" @click="handleClear" />
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
modelValue: {
type: String,
default: '',
},
})
const emit = defineEmits(['update:modelValue', 'search', 'clear', 'focus', 'blur'])
const searchInput = ref(null)
const handleInput = event => {
emit('update:modelValue', event.target.value)
}
const handleSearch = event => {
emit('search', event.target.value)
}
const handleClear = () => {
emit('update:modelValue', '')
emit('clear')
if (searchInput.value) {
searchInput.value.focus()
}
}
const handleFocus = event => {
emit('focus', event)
}
const handleBlur = event => {
emit('blur', event)
}
// 暴露方法给父组件
defineExpose({
focus: () => {
if (searchInput.value) {
searchInput.value.focus()
}
},
blur: () => {
if (searchInput.value) {
searchInput.value.blur()
}
},
})
</script>
<style lang="less" scoped>
.section_3 {
.view {
.image-wrapper {
position: relative;
padding: 0.25rem 0.5rem;
border-radius: 2rem;
background: #f6f9fa;
border: 1px solid #ccb8a3;
display: flex;
flex-direction: row;
align-items: center;
.image_7,
.clear-button {
width: 1.2rem;
height: 1.2rem;
object-fit: contain;
margin-right: 0.1rem;
}
input {
flex: 1;
height: 1.2rem;
line-height: 1.2rem;
background: transparent;
border: none;
outline: none;
font-size: 0.75rem;
color: #333;
padding: 0;
}
}
}
}
</style>