feat(页面组件): 重构收藏和计划书页面的标签栏为 NutTabs
- 将 favorites 和 plan 页面的 FilterTabs 组件替换为 NutTabs 实现自定义标签栏 - 统一在 knowledge-base、material-list、favorites 和 plan 页面的标签容器中添加 width: 100% 样式 - 为收藏和计划书列表项添加进场动画效果 - 重构数据逻辑,使用 tabsData 统一管理分类数据,支持动态数据分配 - 优化标签切换时的列表重渲染体验,避免内容闪烁
Showing
4 changed files
with
330 additions
and
47 deletions
| 1 | +<!-- | ||
| 2 | + * @Date: 2026-01-31 | ||
| 3 | + * @Description: 我的收藏 - 已改造为 NutTabs 版本 | ||
| 4 | +--> | ||
| 1 | <template> | 5 | <template> |
| 2 | <view class="h-screen bg-gray-50 flex flex-col"> | 6 | <view class="h-screen bg-gray-50 flex flex-col"> |
| 3 | - <view class="bg-gray-50"> | 7 | + <view class="bg-gray-50 z-10"> |
| 4 | <NavHeader title="我的收藏" /> | 8 | <NavHeader title="我的收藏" /> |
| 5 | 9 | ||
| 6 | - <view class="bg-white mt-[2rpx] px-[24rpx] py-[20rpx]"> | 10 | + <!-- Tabs Container --> |
| 7 | - <FilterTabs | 11 | + <view class="bg-white mt-[2rpx]"> |
| 8 | - v-model="activeTab" | 12 | + <nut-tabs v-model="activeTabId"> |
| 9 | - :tabs="tabs" | 13 | + <!-- 自定义标签栏 --> |
| 10 | - label-key="title" | 14 | + <template #titles> |
| 11 | - value-key="key" | 15 | + <view class="filter-tabs-wrapper"> |
| 12 | - /> | 16 | + <view |
| 17 | + v-for="item in tabsData" | ||
| 18 | + :key="item.id" | ||
| 19 | + :class="[ | ||
| 20 | + 'filter-tab-item', | ||
| 21 | + activeTabId === item.id ? 'filter-tab-active' : 'filter-tab-inactive' | ||
| 22 | + ]" | ||
| 23 | + @tap="onTabClick(item.id)" | ||
| 24 | + > | ||
| 25 | + <text class="filter-tab-text">{{ item.name }}</text> | ||
| 26 | + </view> | ||
| 27 | + </view> | ||
| 28 | + </template> | ||
| 29 | + </nut-tabs> | ||
| 13 | </view> | 30 | </view> |
| 14 | </view> | 31 | </view> |
| 15 | 32 | ||
| 16 | - <view class="flex-1 overflow-y-auto px-[24rpx] py-[24rpx] pb-[200rpx]"> | 33 | + <view |
| 34 | + v-if="listVisible" | ||
| 35 | + :key="listRenderKey" | ||
| 36 | + class="flex-1 min-h-0 overflow-y-auto px-[24rpx] py-[24rpx] pb-[200rpx]" | ||
| 37 | + > | ||
| 17 | <view v-for="(item, index) in filteredList" :key="index" | 38 | <view v-for="(item, index) in filteredList" :key="index" |
| 18 | - class="bg-white rounded-[24rpx] p-[24rpx] mb-[24rpx] shadow-sm"> | 39 | + class="bg-white rounded-[24rpx] p-[24rpx] mb-[24rpx] shadow-sm favorite-item" |
| 40 | + :style="{ animationDelay: `${index * 50}ms` }"> | ||
| 19 | 41 | ||
| 20 | <!-- Header with Icon --> | 42 | <!-- Header with Icon --> |
| 21 | <view class="flex gap-[24rpx] mb-[12rpx]"> | 43 | <view class="flex gap-[24rpx] mb-[12rpx]"> |
| ... | @@ -62,33 +84,38 @@ | ... | @@ -62,33 +84,38 @@ |
| 62 | </template> | 84 | </template> |
| 63 | 85 | ||
| 64 | <script setup> | 86 | <script setup> |
| 65 | -import { ref, computed } from 'vue' | 87 | +import { ref, computed, nextTick } from 'vue' |
| 66 | import Taro from '@tarojs/taro' | 88 | import Taro from '@tarojs/taro' |
| 67 | import { useGo } from '@/hooks/useGo' | 89 | import { useGo } from '@/hooks/useGo' |
| 68 | import { useFileOperation } from '@/composables/useFileOperation' | 90 | import { useFileOperation } from '@/composables/useFileOperation' |
| 69 | import { getDocumentIcon } from '@/utils/documentIcons' | 91 | import { getDocumentIcon } from '@/utils/documentIcons' |
| 70 | import IconFont from '@/components/IconFont.vue' | 92 | import IconFont from '@/components/IconFont.vue' |
| 71 | -import FilterTabs from '@/components/FilterTabs.vue' | ||
| 72 | import NavHeader from '@/components/NavHeader.vue' | 93 | import NavHeader from '@/components/NavHeader.vue' |
| 73 | import ListItemActions from '@/components/ListItemActions/index.vue' | 94 | import ListItemActions from '@/components/ListItemActions/index.vue' |
| 74 | 95 | ||
| 75 | const go = useGo() | 96 | const go = useGo() |
| 76 | const { viewFile } = useFileOperation() | 97 | const { viewFile } = useFileOperation() |
| 77 | -const activeTab = ref('all') | 98 | +const activeTabId = ref('all') |
| 99 | +const listVisible = ref(true) | ||
| 100 | +const listRenderKey = ref(0) | ||
| 78 | 101 | ||
| 79 | -const tabs = [ | 102 | +/** |
| 80 | - { title: '全部', key: 'all' }, | 103 | + * Tab 数据源 |
| 81 | - { title: '入职培训', key: 'onboarding' }, | 104 | + * @description 包含分类信息和对应的收藏列表 |
| 82 | - { title: '签单相关', key: 'signing' }, | 105 | + */ |
| 83 | - { title: '产品知识', key: 'product' } | 106 | +const tabsData = ref([ |
| 84 | -] | 107 | + { id: 'all', name: '全部', list: [] }, |
| 108 | + { id: 'onboarding', name: '入职培训', list: [] }, | ||
| 109 | + { id: 'signing', name: '签单相关', list: [] }, | ||
| 110 | + { id: 'product', name: '产品知识', list: [] } | ||
| 111 | +]) | ||
| 85 | 112 | ||
| 86 | /** | 113 | /** |
| 87 | * Mock 数据:收藏列表 | 114 | * Mock 数据:收藏列表 |
| 88 | * | 115 | * |
| 89 | * @description 包含不同类型的文档文件 | 116 | * @description 包含不同类型的文档文件 |
| 90 | */ | 117 | */ |
| 91 | -const list = ref([ | 118 | +const allList = ref([ |
| 92 | { | 119 | { |
| 93 | id: 1, | 120 | id: 1, |
| 94 | title: '新员工入职培训手册.pdf', | 121 | title: '新员工入职培训手册.pdf', |
| ... | @@ -139,12 +166,44 @@ const list = ref([ | ... | @@ -139,12 +166,44 @@ const list = ref([ |
| 139 | } | 166 | } |
| 140 | ]) | 167 | ]) |
| 141 | 168 | ||
| 169 | +/** | ||
| 170 | + * 初始化数据分布 | ||
| 171 | + * @description 根据分类规则将 allList 中的数据分配到各个 tab 中 | ||
| 172 | + */ | ||
| 173 | +const initTabsData = () => { | ||
| 174 | + tabsData.value.forEach((tab) => { | ||
| 175 | + if (tab.id === 'all') { | ||
| 176 | + tab.list = [...allList.value] | ||
| 177 | + } else if (tab.id === 'onboarding') { | ||
| 178 | + // 入职培训:type 为 onboarding 或 other | ||
| 179 | + tab.list = allList.value.filter(item => item.type === 'onboarding' || item.type === 'other') | ||
| 180 | + } else { | ||
| 181 | + tab.list = allList.value.filter(item => item.type === tab.id) | ||
| 182 | + } | ||
| 183 | + }) | ||
| 184 | +} | ||
| 185 | + | ||
| 142 | const filteredList = computed(() => { | 186 | const filteredList = computed(() => { |
| 143 | - if (activeTab.value === 'all') return list.value | 187 | + // 找到当前选中的 tab |
| 144 | - return list.value.filter(item => item.type === activeTab.value) | 188 | + const currentTab = tabsData.value.find(tab => tab.id === activeTabId.value) |
| 189 | + if (!currentTab) return [] | ||
| 190 | + | ||
| 191 | + return currentTab.list | ||
| 145 | }) | 192 | }) |
| 146 | 193 | ||
| 147 | /** | 194 | /** |
| 195 | + * Tab 点击处理 | ||
| 196 | + */ | ||
| 197 | +const onTabClick = (id) => { | ||
| 198 | + activeTabId.value = id | ||
| 199 | + listVisible.value = false | ||
| 200 | + nextTick(() => { | ||
| 201 | + listRenderKey.value += 1 | ||
| 202 | + listVisible.value = true | ||
| 203 | + }) | ||
| 204 | +} | ||
| 205 | + | ||
| 206 | +/** | ||
| 148 | * 删除收藏 | 207 | * 删除收藏 |
| 149 | */ | 208 | */ |
| 150 | const onDelete = (item) => { | 209 | const onDelete = (item) => { |
| ... | @@ -153,10 +212,93 @@ const onDelete = (item) => { | ... | @@ -153,10 +212,93 @@ const onDelete = (item) => { |
| 153 | content: '确定要删除该收藏吗?', | 212 | content: '确定要删除该收藏吗?', |
| 154 | success: (res) => { | 213 | success: (res) => { |
| 155 | if (res.confirm) { | 214 | if (res.confirm) { |
| 156 | - list.value = list.value.filter(i => i.id !== item.id) | 215 | + // 从 allList 中删除 |
| 157 | - Taro.showToast({ title: '已删除', icon: 'success' }) | 216 | + const index = allList.value.findIndex(i => i.id === item.id) |
| 217 | + if (index !== -1) { | ||
| 218 | + allList.value.splice(index, 1) | ||
| 219 | + // 重新初始化 tabsData | ||
| 220 | + initTabsData() | ||
| 221 | + Taro.showToast({ title: '已删除', icon: 'success' }) | ||
| 222 | + } | ||
| 158 | } | 223 | } |
| 159 | } | 224 | } |
| 160 | }) | 225 | }) |
| 161 | } | 226 | } |
| 227 | + | ||
| 228 | +// 初始化数据 | ||
| 229 | +initTabsData() | ||
| 162 | </script> | 230 | </script> |
| 231 | + | ||
| 232 | +<style lang="less"> | ||
| 233 | +@keyframes slideIn { | ||
| 234 | + from { | ||
| 235 | + opacity: 0; | ||
| 236 | + transform: translateY(20rpx); | ||
| 237 | + } | ||
| 238 | + | ||
| 239 | + to { | ||
| 240 | + opacity: 1; | ||
| 241 | + transform: translateY(0); | ||
| 242 | + } | ||
| 243 | +} | ||
| 244 | + | ||
| 245 | +.favorite-item { | ||
| 246 | + animation: slideIn 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) backwards; | ||
| 247 | +} | ||
| 248 | + | ||
| 249 | +// FilterTabs 风格的标签栏 | ||
| 250 | +.filter-tabs-wrapper { | ||
| 251 | + display: flex; | ||
| 252 | + overflow-x: auto; | ||
| 253 | + padding: 24rpx 24rpx; | ||
| 254 | + gap: 24rpx; | ||
| 255 | + transition: all 0.3s ease; | ||
| 256 | + background-color: #fff; | ||
| 257 | + width: 100%; | ||
| 258 | + | ||
| 259 | + // 隐藏滚动条 | ||
| 260 | + &::-webkit-scrollbar { | ||
| 261 | + display: none; | ||
| 262 | + width: 0; | ||
| 263 | + height: 0; | ||
| 264 | + } | ||
| 265 | + | ||
| 266 | + -ms-overflow-style: none; | ||
| 267 | + scrollbar-width: none; | ||
| 268 | +} | ||
| 269 | + | ||
| 270 | +.filter-tab-item { | ||
| 271 | + display: flex; | ||
| 272 | + align-items: center; | ||
| 273 | + justify-content: center; | ||
| 274 | + padding: 0 32rpx; | ||
| 275 | + border-radius: 9999rpx; | ||
| 276 | + white-space: nowrap; | ||
| 277 | + transition: all 0.3s ease; | ||
| 278 | + flex-shrink: 0; | ||
| 279 | +} | ||
| 280 | + | ||
| 281 | +.filter-tab-active { | ||
| 282 | + background-color: #2563EB; // 蓝色背景 | ||
| 283 | + color: #fff; | ||
| 284 | +} | ||
| 285 | + | ||
| 286 | +.filter-tab-inactive { | ||
| 287 | + background-color: #F3F4F6; // 灰色背景 | ||
| 288 | + color: #6B7280; | ||
| 289 | +} | ||
| 290 | + | ||
| 291 | +.filter-tab-text { | ||
| 292 | + font-size: 28rpx; | ||
| 293 | + font-weight: 500; | ||
| 294 | +} | ||
| 295 | + | ||
| 296 | +// 覆盖 NutUI Tabs 默认样式,隐藏原有的头部和内容(因为我们使用自定义头部和外部列表) | ||
| 297 | +:deep(.nut-tabs__titles) { | ||
| 298 | + display: none; | ||
| 299 | +} | ||
| 300 | + | ||
| 301 | +:deep(.nut-tabs__content) { | ||
| 302 | + display: none; | ||
| 303 | +} | ||
| 304 | +</style> | ... | ... |
| ... | @@ -245,6 +245,7 @@ initTabsData() | ... | @@ -245,6 +245,7 @@ initTabsData() |
| 245 | gap: 24rpx; | 245 | gap: 24rpx; |
| 246 | transition: all 0.3s ease; | 246 | transition: all 0.3s ease; |
| 247 | background-color: #F9FAFB; | 247 | background-color: #F9FAFB; |
| 248 | + width: 100%; | ||
| 248 | 249 | ||
| 249 | // 隐藏滚动条 | 250 | // 隐藏滚动条 |
| 250 | &::-webkit-scrollbar { | 251 | &::-webkit-scrollbar { | ... | ... |
| ... | @@ -465,6 +465,7 @@ const onDelete = (item) => { | ... | @@ -465,6 +465,7 @@ const onDelete = (item) => { |
| 465 | gap: 24rpx; | 465 | gap: 24rpx; |
| 466 | transition: all 0.3s ease; | 466 | transition: all 0.3s ease; |
| 467 | background-color: #F9FAFB; | 467 | background-color: #F9FAFB; |
| 468 | + width: 100%; | ||
| 468 | 469 | ||
| 469 | // 隐藏滚动条 | 470 | // 隐藏滚动条 |
| 470 | &::-webkit-scrollbar { | 471 | &::-webkit-scrollbar { | ... | ... |
| 1 | +<!-- | ||
| 2 | + * @Date: 2026-01-31 | ||
| 3 | + * @Description: 我的计划书 - 已改造为 NutTabs 版本 | ||
| 4 | +--> | ||
| 1 | <template> | 5 | <template> |
| 2 | <view class="h-screen bg-gray-50 flex flex-col"> | 6 | <view class="h-screen bg-gray-50 flex flex-col"> |
| 3 | - <view class="bg-gray-50"> | 7 | + <view class="bg-gray-50 z-10"> |
| 4 | <!-- Navigation Header --> | 8 | <!-- Navigation Header --> |
| 5 | <NavHeader title="我的计划书" /> | 9 | <NavHeader title="我的计划书" /> |
| 6 | 10 | ||
| ... | @@ -14,16 +18,38 @@ | ... | @@ -14,16 +18,38 @@ |
| 14 | /> | 18 | /> |
| 15 | </view> | 19 | </view> |
| 16 | 20 | ||
| 17 | - <!-- Tabs --> | 21 | + <!-- Tabs Container --> |
| 18 | - <view class="bg-white mt-[2rpx] px-[24rpx] py-[20rpx]"> | 22 | + <view class="bg-white mt-[2rpx]"> |
| 19 | - <FilterTabs v-model="activeTab" :tabs="tabs" label-key="title" /> | 23 | + <nut-tabs v-model="activeTabId"> |
| 24 | + <!-- 自定义标签栏 --> | ||
| 25 | + <template #titles> | ||
| 26 | + <view class="filter-tabs-wrapper"> | ||
| 27 | + <view | ||
| 28 | + v-for="item in tabsData" | ||
| 29 | + :key="item.id" | ||
| 30 | + :class="[ | ||
| 31 | + 'filter-tab-item', | ||
| 32 | + activeTabId === item.id ? 'filter-tab-active' : 'filter-tab-inactive' | ||
| 33 | + ]" | ||
| 34 | + @tap="onTabClick(item.id)" | ||
| 35 | + > | ||
| 36 | + <text class="filter-tab-text">{{ item.name }}</text> | ||
| 37 | + </view> | ||
| 38 | + </view> | ||
| 39 | + </template> | ||
| 40 | + </nut-tabs> | ||
| 20 | </view> | 41 | </view> |
| 21 | </view> | 42 | </view> |
| 22 | 43 | ||
| 23 | <!-- Plan List --> | 44 | <!-- Plan List --> |
| 24 | - <view class="flex-1 overflow-y-auto px-[24rpx] py-[24rpx] pb-[200rpx]"> | 45 | + <view |
| 46 | + v-if="listVisible" | ||
| 47 | + :key="listRenderKey" | ||
| 48 | + class="flex-1 min-h-0 overflow-y-auto px-[24rpx] py-[24rpx] pb-[200rpx]" | ||
| 49 | + > | ||
| 25 | <view v-for="(item, index) in filteredList" :key="index" | 50 | <view v-for="(item, index) in filteredList" :key="index" |
| 26 | - class="bg-white rounded-[24rpx] p-[24rpx] mb-[24rpx] shadow-sm"> | 51 | + class="bg-white rounded-[24rpx] p-[24rpx] mb-[24rpx] shadow-sm plan-item" |
| 52 | + :style="{ animationDelay: `${index * 50}ms` }"> | ||
| 27 | <!-- Header --> | 53 | <!-- Header --> |
| 28 | <view class="flex justify-between items-start mb-[16rpx]"> | 54 | <view class="flex justify-between items-start mb-[16rpx]"> |
| 29 | <view class="flex-1"> | 55 | <view class="flex-1"> |
| ... | @@ -71,10 +97,9 @@ | ... | @@ -71,10 +97,9 @@ |
| 71 | </template> | 97 | </template> |
| 72 | 98 | ||
| 73 | <script setup> | 99 | <script setup> |
| 74 | -import { ref, computed } from 'vue' | 100 | +import { ref, computed, nextTick } from 'vue' |
| 75 | import { useFileOperation } from '@/composables/useFileOperation' | 101 | import { useFileOperation } from '@/composables/useFileOperation' |
| 76 | import IconFont from '@/components/IconFont.vue' | 102 | import IconFont from '@/components/IconFont.vue' |
| 77 | -import FilterTabs from '@/components/FilterTabs.vue' | ||
| 78 | import NavHeader from '@/components/NavHeader.vue' | 103 | import NavHeader from '@/components/NavHeader.vue' |
| 79 | import ListItemActions from '@/components/ListItemActions/index.vue' | 104 | import ListItemActions from '@/components/ListItemActions/index.vue' |
| 80 | import SearchBar from '@/components/SearchBar.vue' | 105 | import SearchBar from '@/components/SearchBar.vue' |
| ... | @@ -83,12 +108,19 @@ import Taro from '@tarojs/taro' | ... | @@ -83,12 +108,19 @@ import Taro from '@tarojs/taro' |
| 83 | const { viewFile } = useFileOperation() | 108 | const { viewFile } = useFileOperation() |
| 84 | 109 | ||
| 85 | const searchValue = ref('') | 110 | const searchValue = ref('') |
| 86 | -const activeTab = ref(0) | 111 | +const activeTabId = ref('') |
| 87 | -const tabs = [ | 112 | +const listVisible = ref(true) |
| 88 | - { title: '全部', key: 'all' }, | 113 | +const listRenderKey = ref(0) |
| 89 | - { title: '生成中', key: 'processing' }, | 114 | + |
| 90 | - { title: '已生成', key: 'generated' } | 115 | +/** |
| 91 | -] | 116 | + * Tab 数据源 |
| 117 | + * @description 包含分类信息和对应的计划书列表 | ||
| 118 | + */ | ||
| 119 | +const tabsData = ref([ | ||
| 120 | + { id: '', name: '全部', list: [] }, | ||
| 121 | + { id: 'processing', name: '生成中', list: [] }, | ||
| 122 | + { id: 'generated', name: '已生成', list: [] }, | ||
| 123 | +]) | ||
| 92 | 124 | ||
| 93 | /** | 125 | /** |
| 94 | * Mock 数据:计划书列表 | 126 | * Mock 数据:计划书列表 |
| ... | @@ -96,7 +128,7 @@ const tabs = [ | ... | @@ -96,7 +128,7 @@ const tabs = [ |
| 96 | * @description 使用真实 PDF 文件进行测试 | 128 | * @description 使用真实 PDF 文件进行测试 |
| 97 | * downloadUrl 使用 Mozilla 的公开 PDF 测试文件 | 129 | * downloadUrl 使用 Mozilla 的公开 PDF 测试文件 |
| 98 | */ | 130 | */ |
| 99 | -const list = ref([ | 131 | +const allList = ref([ |
| 100 | { | 132 | { |
| 101 | id: 1, | 133 | id: 1, |
| 102 | title: '家庭财富传承保障计划(分红)', | 134 | title: '家庭财富传承保障计划(分红)', |
| ... | @@ -224,16 +256,28 @@ const list = ref([ | ... | @@ -224,16 +256,28 @@ const list = ref([ |
| 224 | ]) | 256 | ]) |
| 225 | 257 | ||
| 226 | /** | 258 | /** |
| 259 | + * 初始化数据分布 | ||
| 260 | + * @description 根据分类规则将 allList 中的数据分配到各个 tab 中 | ||
| 261 | + */ | ||
| 262 | +const initTabsData = () => { | ||
| 263 | + tabsData.value.forEach((tab) => { | ||
| 264 | + if (tab.id === '') { | ||
| 265 | + tab.list = [...allList.value] | ||
| 266 | + } else { | ||
| 267 | + tab.list = allList.value.filter(item => item.status === tab.id) | ||
| 268 | + } | ||
| 269 | + }) | ||
| 270 | +} | ||
| 271 | + | ||
| 272 | +/** | ||
| 227 | * 根据标签页和搜索关键词过滤列表 | 273 | * 根据标签页和搜索关键词过滤列表 |
| 228 | */ | 274 | */ |
| 229 | const filteredList = computed(() => { | 275 | const filteredList = computed(() => { |
| 230 | - let result = list.value | 276 | + // 找到当前选中的 tab |
| 277 | + const currentTab = tabsData.value.find(tab => tab.id === activeTabId.value) | ||
| 278 | + if (!currentTab) return [] | ||
| 231 | 279 | ||
| 232 | - // Filter by Tab | 280 | + let result = currentTab.list |
| 233 | - const currentKey = tabs[activeTab.value].key | ||
| 234 | - if (currentKey !== 'all') { | ||
| 235 | - result = result.filter(item => item.status === currentKey) | ||
| 236 | - } | ||
| 237 | 281 | ||
| 238 | // Filter by Search | 282 | // Filter by Search |
| 239 | if (searchValue.value) { | 283 | if (searchValue.value) { |
| ... | @@ -248,6 +292,18 @@ const filteredList = computed(() => { | ... | @@ -248,6 +292,18 @@ const filteredList = computed(() => { |
| 248 | }) | 292 | }) |
| 249 | 293 | ||
| 250 | /** | 294 | /** |
| 295 | + * Tab 点击处理 | ||
| 296 | + */ | ||
| 297 | +const onTabClick = (id) => { | ||
| 298 | + activeTabId.value = id | ||
| 299 | + listVisible.value = false | ||
| 300 | + nextTick(() => { | ||
| 301 | + listRenderKey.value += 1 | ||
| 302 | + listVisible.value = true | ||
| 303 | + }) | ||
| 304 | +} | ||
| 305 | + | ||
| 306 | +/** | ||
| 251 | * 搜索处理 | 307 | * 搜索处理 |
| 252 | */ | 308 | */ |
| 253 | const onSearch = (val) => { | 309 | const onSearch = (val) => { |
| ... | @@ -286,10 +342,93 @@ const onDelete = (item) => { | ... | @@ -286,10 +342,93 @@ const onDelete = (item) => { |
| 286 | content: '确定要删除该计划书吗?', | 342 | content: '确定要删除该计划书吗?', |
| 287 | success: (res) => { | 343 | success: (res) => { |
| 288 | if (res.confirm) { | 344 | if (res.confirm) { |
| 289 | - list.value = list.value.filter(i => i.id !== item.id) | 345 | + // 从 allList 中删除 |
| 290 | - Taro.showToast({ title: '已删除', icon: 'success' }) | 346 | + const index = allList.value.findIndex(i => i.id === item.id) |
| 347 | + if (index !== -1) { | ||
| 348 | + allList.value.splice(index, 1) | ||
| 349 | + // 重新初始化 tabsData | ||
| 350 | + initTabsData() | ||
| 351 | + Taro.showToast({ title: '已删除', icon: 'success' }) | ||
| 352 | + } | ||
| 291 | } | 353 | } |
| 292 | } | 354 | } |
| 293 | }) | 355 | }) |
| 294 | } | 356 | } |
| 357 | + | ||
| 358 | +// 初始化数据 | ||
| 359 | +initTabsData() | ||
| 295 | </script> | 360 | </script> |
| 361 | + | ||
| 362 | +<style lang="less"> | ||
| 363 | +@keyframes slideIn { | ||
| 364 | + from { | ||
| 365 | + opacity: 0; | ||
| 366 | + transform: translateY(20rpx); | ||
| 367 | + } | ||
| 368 | + | ||
| 369 | + to { | ||
| 370 | + opacity: 1; | ||
| 371 | + transform: translateY(0); | ||
| 372 | + } | ||
| 373 | +} | ||
| 374 | + | ||
| 375 | +.plan-item { | ||
| 376 | + animation: slideIn 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) backwards; | ||
| 377 | +} | ||
| 378 | + | ||
| 379 | +// FilterTabs 风格的标签栏 | ||
| 380 | +.filter-tabs-wrapper { | ||
| 381 | + display: flex; | ||
| 382 | + overflow-x: auto; | ||
| 383 | + padding: 24rpx 24rpx; | ||
| 384 | + gap: 24rpx; | ||
| 385 | + transition: all 0.3s ease; | ||
| 386 | + background-color: #fff; | ||
| 387 | + width: 100%; | ||
| 388 | + | ||
| 389 | + // 隐藏滚动条 | ||
| 390 | + &::-webkit-scrollbar { | ||
| 391 | + display: none; | ||
| 392 | + width: 0; | ||
| 393 | + height: 0; | ||
| 394 | + } | ||
| 395 | + | ||
| 396 | + -ms-overflow-style: none; | ||
| 397 | + scrollbar-width: none; | ||
| 398 | +} | ||
| 399 | + | ||
| 400 | +.filter-tab-item { | ||
| 401 | + display: flex; | ||
| 402 | + align-items: center; | ||
| 403 | + justify-content: center; | ||
| 404 | + padding: 0 32rpx; | ||
| 405 | + border-radius: 9999rpx; | ||
| 406 | + white-space: nowrap; | ||
| 407 | + transition: all 0.3s ease; | ||
| 408 | + flex-shrink: 0; | ||
| 409 | +} | ||
| 410 | + | ||
| 411 | +.filter-tab-active { | ||
| 412 | + background-color: #2563EB; // 蓝色背景 | ||
| 413 | + color: #fff; | ||
| 414 | +} | ||
| 415 | + | ||
| 416 | +.filter-tab-inactive { | ||
| 417 | + background-color: #F3F4F6; // 灰色背景 | ||
| 418 | + color: #6B7280; | ||
| 419 | +} | ||
| 420 | + | ||
| 421 | +.filter-tab-text { | ||
| 422 | + font-size: 28rpx; | ||
| 423 | + font-weight: 500; | ||
| 424 | +} | ||
| 425 | + | ||
| 426 | +// 覆盖 NutUI Tabs 默认样式,隐藏原有的头部和内容(因为我们使用自定义头部和外部列表) | ||
| 427 | +:deep(.nut-tabs__titles) { | ||
| 428 | + display: none; | ||
| 429 | +} | ||
| 430 | + | ||
| 431 | +:deep(.nut-tabs__content) { | ||
| 432 | + display: none; | ||
| 433 | +} | ||
| 434 | +</style> | ... | ... |
-
Please register or login to post a comment