hookehuyr

feat(list): 新增资料列表操作组件并统一页面样式

- 新增 ListItemActions 组件,支持查看、收藏、删除操作
- 修复资料列表页布局问题(图标和文字水平排列)
- 统一首页和资料列表页的资料项样式和交互
- 更新收藏页、知识库页、计划页使用新组件
- 添加完整的组件文档和类型定义

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
......@@ -10,14 +10,10 @@ declare module 'vue' {
DocumentPreview: typeof import('./src/components/DocumentPreview/index.vue')['default']
IconFont: typeof import('./src/components/IconFont.vue')['default']
IndexNav: typeof import('./src/components/indexNav.vue')['default']
ListItemActions: typeof import('./src/components/ListItemActions/index.vue')['default']
NavHeader: typeof import('./src/components/NavHeader.vue')['default']
NutAvatar: typeof import('@nutui/nutui-taro')['Avatar']
NutButton: typeof import('@nutui/nutui-taro')['Button']
NutCheckbox: typeof import('@nutui/nutui-taro')['Checkbox']
NutCheckboxGroup: typeof import('@nutui/nutui-taro')['CheckboxGroup']
NutForm: typeof import('@nutui/nutui-taro')['Form']
NutFormItem: typeof import('@nutui/nutui-taro')['FormItem']
NutIcon: typeof import('@nutui/nutui-taro')['Icon']
NutInput: typeof import('@nutui/nutui-taro')['Input']
NutPicker: typeof import('@nutui/nutui-taro')['Picker']
NutPopup: typeof import('@nutui/nutui-taro')['Popup']
......@@ -26,7 +22,6 @@ declare module 'vue' {
NutSearchbar: typeof import('@nutui/nutui-taro')['Searchbar']
NutTabPane: typeof import('@nutui/nutui-taro')['TabPane']
NutTabs: typeof import('@nutui/nutui-taro')['Tabs']
NutTextarea: typeof import('@nutui/nutui-taro')['Textarea']
NutUploader: typeof import('@nutui/nutui-taro')['Uploader']
OfficeViewer: typeof import('./src/components/OfficeViewer.vue')['default']
PdfPreview: typeof import('./src/components/PdfPreview.vue')['default']
......
<!--
列表项操作按钮组件
@description 统一的列表项操作按钮组件,支持查看、收藏、删除三种操作
@example
<ListItemActions
:viewable="true"
:collectable="true"
:deletable="true"
:collected="item.collected"
@view="onView(item)"
@collect="onCollect(item)"
@delete="onDelete(item)"
/>
-->
<template>
<view class="flex justify-end gap-[24rpx]">
<!-- 查看按钮 -->
<view v-if="viewable" class="flex items-center text-blue-600" @tap="handleView">
<IconFont name="eye" size="14" class="mr-[8rpx]" />
<text class="text-[24rpx]">查看</text>
</view>
<!-- 收藏按钮 -->
<view v-if="collectable" class="flex items-center" :class="isCollected ? 'text-red-500' : 'text-gray-400'" @tap="handleCollect">
<IconFont :name="isCollected ? 'heart-fill' : 'heart'" size="14" class="mr-[8rpx]" />
<text class="text-[24rpx]">{{ isCollected ? '已收藏' : '收藏' }}</text>
</view>
<!-- 删除按钮 -->
<view v-if="deletable" class="flex items-center text-red-500" @tap="handleDelete">
<IconFont name="del" size="14" class="mr-[8rpx]" />
<text class="text-[24rpx]">删除</text>
</view>
</view>
</template>
<script setup>
import { computed } from 'vue'
import IconFont from '@/components/IconFont.vue'
/**
* 组件属性
*/
const props = defineProps({
/**
* 是否显示查看按钮
* @type {boolean}
* @default true
*/
viewable: {
type: Boolean,
default: true
},
/**
* 是否显示收藏按钮
* @type {boolean}
* @default false
*/
collectable: {
type: Boolean,
default: false
},
/**
* 是否显示删除按钮
* @type {boolean}
* @default false
*/
deletable: {
type: Boolean,
default: false
},
/**
* 是否已收藏
* @type {boolean}
* @default false
*/
collected: {
type: Boolean,
default: false
}
})
/**
* 组件事件
*/
const emit = defineEmits({
/**
* 点击查看按钮时触发
*/
view: null,
/**
* 点击收藏按钮时触发
*/
collect: null,
/**
* 点击删除按钮时触发
*/
delete: null
})
const isCollected = computed(() => props.collected)
/**
* 处理查看点击
*/
const handleView = () => {
emit('view')
}
/**
* 处理收藏点击
*/
const handleCollect = () => {
emit('collect')
}
/**
* 处理删除点击
*/
const handleDelete = () => {
emit('delete')
}
</script>
<style lang="less" scoped>
</style>
......@@ -43,16 +43,12 @@
<view class="h-[1rpx] bg-gray-100 mb-[20rpx]"></view>
<!-- Actions -->
<view class="flex justify-end gap-[24rpx]">
<view class="flex items-center text-blue-600" @tap="viewFile({...item, fileName: item.title})">
<IconFont name="eye" size="14" class="mr-[8rpx]" />
<text class="text-[24rpx]">查看</text>
</view>
<view class="flex items-center text-red-500" @tap="onDelete(item)">
<IconFont name="del" size="14" class="mr-[8rpx]" />
<text class="text-[24rpx]">删除</text>
</view>
</view>
<ListItemActions
:viewable="true"
:deletable="true"
@view="viewFile({...item, fileName: item.title})"
@delete="onDelete(item)"
/>
</view>
<!-- Empty State -->
......@@ -77,6 +73,7 @@ import { getDocumentIcon } from '@/utils/documentIcons'
import IconFont from '@/components/IconFont.vue'
import TabBar from '@/components/TabBar.vue'
import NavHeader from '@/components/NavHeader.vue'
import ListItemActions from '@/components/ListItemActions/index.vue'
const go = useGo()
const { viewFile } = useFileOperation()
......
......@@ -126,61 +126,40 @@
</view>
<!-- Material List -->
<view class="flex flex-col gap-[32rpx]">
<!-- Item 1 -->
<view class="flex gap-[24rpx]" @tap="handleMaterialClick(hotMaterials[0])">
<view class="w-[80rpx] h-[88rpx] flex-shrink-0 flex items-center justify-center bg-blue-50 rounded-[12rpx]">
<image :src="getDocumentIcon(hotMaterials[0].fileName)" class="w-[48rpx] h-[48rpx]" mode="aspectFit" />
</view>
<view class="flex-1 flex flex-col justify-between py-[4rpx]">
<text class="text-gray-800 text-[28rpx] leading-[40rpx] line-clamp-2 mb-1">{{ hotMaterials[0].title }}</text>
<view class="flex items-center gap-2 mb-1">
<view class="bg-blue-50 rounded px-2 py-0.5">
<text class="text-blue-600 text-[22rpx]">{{ getDocumentLabel(hotMaterials[0].fileName) }}</text>
</view>
</view>
<view class="flex justify-between items-end">
<text class="text-gray-400 text-[24rpx]">{{ hotMaterials[0].learners }}</text>
<text class="text-blue-600 text-[26rpx]">{{ hotMaterials[0].progress }}</text>
</view>
</view>
</view>
<view class="h-[2rpx] bg-gray-100"></view>
<!-- Item 2 -->
<view class="flex gap-[24rpx]" @tap="handleMaterialClick(hotMaterials[1])">
<view class="w-[80rpx] h-[88rpx] flex-shrink-0 flex items-center justify-center bg-blue-50 rounded-[12rpx]">
<image :src="getDocumentIcon(hotMaterials[1].fileName)" class="w-[48rpx] h-[48rpx]" mode="aspectFit" />
</view>
<view class="flex-1 flex flex-col justify-between py-[4rpx]">
<text class="text-gray-800 text-[28rpx] leading-[40rpx] line-clamp-2 mb-1">{{ hotMaterials[1].title }}</text>
<view class="flex items-center gap-2 mb-1">
<view class="bg-blue-50 rounded px-2 py-0.5">
<text class="text-blue-600 text-[22rpx]">{{ getDocumentLabel(hotMaterials[1].fileName) }}</text>
</view>
</view>
<view class="flex justify-between items-end">
<text class="text-gray-400 text-[24rpx]">{{ hotMaterials[1].learners }}</text>
<text class="text-blue-600 text-[26rpx]">{{ hotMaterials[1].progress }}</text>
</view>
</view>
</view>
<view class="h-[2rpx] bg-gray-100"></view>
<!-- Item 3 -->
<view class="flex gap-[24rpx]" @tap="handleMaterialClick(hotMaterials[2])">
<view class="w-[80rpx] h-[88rpx] flex-shrink-0 flex items-center justify-center bg-blue-50 rounded-[12rpx]">
<image :src="getDocumentIcon(hotMaterials[2].fileName)" class="w-[48rpx] h-[48rpx]" mode="aspectFit" />
<view class="flex flex-col gap-[24rpx]">
<!-- Material Items -->
<view v-for="(item, index) in hotMaterials" :key="index"
class="flex flex-row bg-white rounded-[24rpx] p-[24rpx] border border-gray-50">
<!-- 左侧图标 -->
<view class="w-[88rpx] h-[88rpx] mr-[24rpx] flex-shrink-0 flex items-center justify-center bg-gradient-to-br from-blue-50 to-blue-100 rounded-[20rpx] shadow-inner self-start">
<image :src="getDocumentIcon(item.fileName)" class="w-[48rpx] h-[48rpx]" mode="aspectFit" />
</view>
<view class="flex-1 flex flex-col justify-between py-[4rpx]">
<text class="text-gray-800 text-[28rpx] leading-[40rpx] line-clamp-2 mb-1">{{ hotMaterials[2].title }}</text>
<view class="flex items-center gap-2 mb-1">
<view class="bg-blue-50 rounded px-2 py-0.5">
<text class="text-blue-600 text-[22rpx]">{{ getDocumentLabel(hotMaterials[2].fileName) }}</text>
<!-- 内容区域 -->
<view class="flex-1 min-w-0">
<text class="text-[#1F2937] text-[30rpx] font-bold leading-[1.4] line-clamp-2 mb-[8rpx]">
{{ item.title }}
</text>
<view class="flex items-center gap-[12rpx] mb-[16rpx]">
<view class="inline-flex items-center justify-center px-[12rpx] py-[4rpx] bg-gray-100 text-gray-500 text-[20rpx] font-medium rounded-[8rpx]">
<text>{{ getDocumentLabel(item.fileName) }}</text>
</view>
<text class="text-[#9CA3AF] text-[22rpx]">{{ item.learners }}</text>
</view>
<view class="flex justify-between items-end">
<text class="text-gray-400 text-[24rpx]">{{ hotMaterials[2].learners }}</text>
<text class="text-blue-600 text-[26rpx]">{{ hotMaterials[2].progress }}</text>
</view>
<!-- 分割线 -->
<view class="h-[1rpx] bg-gray-100 my-[20rpx]"></view>
<!-- 操作按钮 -->
<ListItemActions
:viewable="true"
:collectable="true"
:deletable="false"
:collected="item.collected"
@view="onViewMaterial(item)"
@collect="toggleMaterialCollect(item)"
/>
</view>
</view>
</view>
......@@ -217,6 +196,7 @@ import IconFont from '@/components/IconFont.vue';
import PlanPopup from '@/components/PlanPopup/index.vue';
import SchemeA from '@/components/PlanSchemes/SchemeA.vue';
import SchemeB from '@/components/PlanSchemes/SchemeB.vue';
import ListItemActions from '@/components/ListItemActions/index.vue';
// Plan Popup State
const showPlanPopup = ref(false);
......@@ -256,6 +236,7 @@ const hotMaterials = ref([
title: '2024年保险市场趋势分析报告',
learners: '256人学习',
progress: '78%',
collected: false,
// PDF 文件
fileName: '2024年保险市场趋势分析报告.pdf',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
......@@ -264,6 +245,7 @@ const hotMaterials = ref([
title: '高净值客户产品配置方案模板',
learners: '189人学习',
progress: '65%',
collected: true,
// Word 文件
fileName: '高净值客户产品配置方案模板.docx',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
......@@ -272,6 +254,7 @@ const hotMaterials = ref([
title: '产品收益率测算表(2024版)',
learners: '142人学习',
progress: '52%',
collected: false,
// Excel 文件
fileName: '产品收益率测算表.xlsx',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
......@@ -286,13 +269,28 @@ const go = useGo();
*
* @description 配置为文件类型列表,点击时打开文件预览
*/
const { handleClick: handleMaterialClick } = useListItemClick({
const { handleClick: onViewMaterial } = useListItemClick({
listType: ListType.FILE,
onAfterClick: (item) => {
console.log('用户打开了资料:', item.title);
}
});
/**
* 切换资料收藏状态
*
* @description 切换热门资料的收藏状态
* @param {Object} item - 资料项
*/
const toggleMaterialCollect = (item) => {
item.collected = !item.collected;
Taro.showToast({
title: item.collected ? '已收藏' : '已取消收藏',
icon: 'success',
duration: 1000
});
};
// Handle grid navigation click
const handleGridNav = (item) => {
if (!item.route) {
......
......@@ -45,7 +45,8 @@
</div>
<!-- Desc -->
<div class="mt-auto self-start bg-[#F3F4F6] text-[#6B7280] text-[22rpx] px-[12rpx] py-[4rpx] rounded-full">
<div class="mt-auto self-start text-[22rpx] px-[12rpx] py-[4rpx] rounded-full"
:class="[getDescColor(item.id).bg, getDescColor(item.id).text]">
{{ item.desc }}
</div>
</div>
......@@ -68,6 +69,34 @@ const activeTab = ref(0)
const tabs = ['全部产品', '人寿保险', '医疗保险', '意外保险']
/**
* Desc 颜色调色板
*
* @description 为不同描述提供柔和的背景色和对应的文字颜色
*/
const descColorPalette = [
{ bg: 'bg-blue-50', text: 'text-blue-600' }, // 蓝色
{ bg: 'bg-green-50', text: 'text-green-600' }, // 绿色
{ bg: 'bg-purple-50', text: 'text-purple-600' }, // 紫色
{ bg: 'bg-orange-50', text: 'text-orange-600' }, // 橙色
{ bg: 'bg-pink-50', text: 'text-pink-600' }, // 粉色
{ bg: 'bg-teal-50', text: 'text-teal-600' }, // 青色
{ bg: 'bg-indigo-50', text: 'text-indigo-600' }, // 靛蓝色
{ bg: 'bg-red-50', text: 'text-red-600' }, // 红色
]
/**
* 获取 desc 的颜色样式
*
* @description 根据 ID 获取固定的背景色和文字颜色
* @param {number} id - 产品 ID
* @returns {Object} 包含 bg 和 text 属性的颜色对象
*/
const getDescColor = (id) => {
const index = id % descColorPalette.length
return descColorPalette[index]
}
/**
* 生成产品数据
*
* @description 生成模拟产品列表数据,每个产品包含唯一 id
......
<!--
* @Date: 2026-01-30
* @Date: 2026-01-31
* @Description: 资料列表页
-->
<template>
......@@ -21,12 +21,12 @@
<div class="px-[32rpx] mt-[32rpx]">
<div class="flex flex-col gap-[24rpx]">
<div v-for="(item, index) in list" :key="index"
class="material-item bg-white rounded-[24rpx] p-[24rpx] shadow-sm flex items-start transition-all duration-200 border border-gray-50"
class="material-item bg-white rounded-[24rpx] p-[24rpx] shadow-sm transition-all duration-200 border border-gray-50 flex flex-row"
:style="{ animationDelay: `${index * 50}ms` }">
<!-- 左侧图标 -->
<div
class="w-[88rpx] h-[88rpx] mr-[24rpx] flex-shrink-0 flex items-center justify-center bg-gradient-to-br from-blue-50 to-blue-100 rounded-[20rpx] shadow-inner">
class="w-[88rpx] h-[88rpx] mr-[24rpx] flex-shrink-0 flex items-center justify-center bg-gradient-to-br from-blue-50 to-blue-100 rounded-[20rpx] shadow-inner self-start">
<image
:src="getDocumentIcon(item.fileName)"
class="w-[48rpx] h-[48rpx]"
......@@ -36,47 +36,36 @@
<!-- 内容区域 -->
<div class="flex-1 min-w-0">
<div class="flex justify-between items-start gap-[16rpx]">
<div class="flex-1 pr-[8rpx]">
<h3 class="text-[#1F2937] text-[30rpx] font-bold leading-[1.4] line-clamp-2 mb-[8rpx]">
{{ item.title }}
</h3>
<p class="text-[#6B7280] text-[24rpx] leading-[1.4] line-clamp-1 mb-[16rpx]">
{{ item.desc }}
</p>
</div>
<h3 class="text-[#1F2937] text-[30rpx] font-bold leading-[1.4] line-clamp-2 mb-[8rpx]">
{{ item.title }}
</h3>
<p class="text-[#6B7280] text-[24rpx] leading-[1.4] line-clamp-1 mb-[16rpx]">
{{ item.desc }}
</p>
<!-- 收藏图标 -->
<div
class="heart-btn w-[64rpx] h-[64rpx] -mt-[12rpx] -mr-[12rpx] flex items-center justify-center rounded-full active:bg-gray-50 transition-colors"
@click.stop="toggleCollect(item)">
<div :class="{ 'is-collected': item.collected }" class="transform transition-transform duration-300">
<IconFont :name="item.collected ? 'heart-fill' : 'heart'" size="24"
:color="item.collected ? '#EF4444' : '#D1D5DB'" />
</div>
</div>
<!-- 文件信息 -->
<div class="flex items-center gap-[12rpx] mb-[20rpx]">
<span
class="inline-flex items-center justify-center px-[12rpx] py-[4rpx] bg-gray-100 text-gray-500 text-[20rpx] font-medium rounded-[8rpx]">
{{ getDocumentLabel(item.fileName) }}
</span>
<span class="text-[#9CA3AF] text-[22rpx]">
{{ item.size }}
</span>
</div>
<!-- 底部信息:文件类型、大小和查看按钮 -->
<div class="flex items-center justify-between">
<div class="flex items-center gap-[12rpx]">
<span
class="inline-flex items-center justify-center px-[12rpx] py-[4rpx] bg-gray-100 text-gray-500 text-[20rpx] font-medium rounded-[8rpx]">
{{ getDocumentLabel(item.fileName) }}
</span>
<span class="text-[#9CA3AF] text-[22rpx]">
{{ item.size }}
</span>
</div>
<!-- 分割线 -->
<view class="h-[1rpx] bg-gray-100 my-[20rpx]"></view>
<!-- 查看按钮 -->
<view
class="flex items-center justify-center px-[20rpx] py-[10rpx] bg-blue-50 rounded-full active:bg-blue-100 transition-colors"
@tap.stop="onView(item)">
<IconFont name="eye" size="14" color="#2563EB" class="mr-[6rpx]" />
<text class="text-[24rpx] text-blue-600 font-medium leading-none ml-[8rpx]">查看</text>
</view>
</div>
<!-- 操作按钮 -->
<ListItemActions
:viewable="true"
:collectable="true"
:collected="item.collected"
@view="onView(item)"
@collect="toggleCollect(item)"
@delete="onDelete(item)"
/>
</div>
</div>
</div>
......@@ -92,8 +81,10 @@ import { ref } from 'vue'
import NavHeader from '@/components/NavHeader.vue'
import TabBar from '@/components/TabBar.vue'
import IconFont from '@/components/IconFont.vue'
import ListItemActions from '@/components/ListItemActions/index.vue'
import { useListItemClick, ListType } from '@/composables/useListItemClick'
import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons'
import Taro from '@tarojs/taro'
const searchValue = ref('')
......@@ -111,7 +102,7 @@ const list = ref([
iconColor: '#EF4444',
collected: true,
fileName: '2024年保险代理人考试大纲.pdf',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
},
{
title: '历年真题汇总及解析.pdf',
......@@ -121,7 +112,7 @@ const list = ref([
iconColor: '#EF4444',
collected: false,
fileName: '历年真题汇总及解析.pdf',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
},
{
title: '考试技巧与经验分享.pdf',
......@@ -131,7 +122,7 @@ const list = ref([
iconColor: '#EF4444',
collected: false,
fileName: '考试技巧与经验分享.pdf',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
},
{
title: '保险基础知识速记手册.pdf',
......@@ -141,7 +132,7 @@ const list = ref([
iconColor: '#EF4444',
collected: false,
fileName: '保险基础知识速记手册.pdf',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
},
{
title: '模拟试卷10套及答案.pdf',
......@@ -151,7 +142,7 @@ const list = ref([
iconColor: '#EF4444',
collected: true,
fileName: '模拟试卷10套及答案.pdf',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
},
{
title: '法律法规重点条款解读.pdf',
......@@ -161,7 +152,7 @@ const list = ref([
iconColor: '#EF4444',
collected: false,
fileName: '法律法规重点条款解读.pdf',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
},
{
title: '考试常见易错题分析.pdf',
......@@ -171,7 +162,7 @@ const list = ref([
iconColor: '#EF4444',
collected: false,
fileName: '考试常见易错题分析.pdf',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
},
{
title: '案例分析题库及解答.pdf',
......@@ -181,7 +172,7 @@ const list = ref([
iconColor: '#EF4444',
collected: false,
fileName: '案例分析题库及解答.pdf',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
},
{
title: '考前冲刺复习资料.pdf',
......@@ -191,7 +182,7 @@ const list = ref([
iconColor: '#EF4444',
collected: false,
fileName: '考前冲刺复习资料.pdf',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
},
{
title: '考场注意事项及答题技巧.pdf',
......@@ -201,7 +192,7 @@ const list = ref([
iconColor: '#EF4444',
collected: false,
fileName: '考场注意事项及答题技巧.pdf',
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
}
])
......@@ -215,6 +206,18 @@ const onSearch = () => {
}
/**
* 使用文件列表点击处理器
*
* @description 配置为文件类型列表,点击时打开文件预览
*/
const { handleClick: onView } = useListItemClick({
listType: ListType.FILE,
onAfterClick: (item) => {
console.log('用户打开了资料:', item.title)
}
})
/**
* 切换收藏状态
*
* @description 切换资料的收藏状态
......@@ -222,19 +225,31 @@ const onSearch = () => {
*/
const toggleCollect = (item) => {
item.collected = !item.collected
Taro.showToast({
title: item.collected ? '已收藏' : '已取消收藏',
icon: 'success',
duration: 1000
})
}
/**
* 使用文件列表点击处理器
*
* @description 配置为文件类型列表,点击时打开文件预览
* 删除资料
*/
const { handleClick: onView } = useListItemClick({
listType: ListType.FILE,
onAfterClick: (item) => {
console.log('用户打开了资料:', item.title)
}
})
const onDelete = (item) => {
Taro.showModal({
title: '提示',
content: '确定要删除该资料吗?',
success: (res) => {
if (res.confirm) {
const index = list.value.findIndex(i => i.title === item.title)
if (index !== -1) {
list.value.splice(index, 1)
Taro.showToast({ title: '已删除', icon: 'success' })
}
}
}
})
}
</script>
<style lang="less" scoped>
......@@ -253,31 +268,4 @@ const { handleClick: onView } = useListItemClick({
.material-item {
animation: slideIn 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
}
/* 收藏成功的动画 */
.is-collected {
animation: heartBeat 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
@keyframes heartBeat {
0% {
transform: scale(1);
}
14% {
transform: scale(1.3);
}
28% {
transform: scale(1);
}
42% {
transform: scale(1.3);
}
70% {
transform: scale(1);
}
}
</style>
......
......@@ -23,7 +23,7 @@
</view>
<!-- Arrow -->
<IconFont name="rectRight" size="20" color="#9CA3AF" />
<IconFont name="rect-right" size="20" color="#9CA3AF" />
</view>
<!-- Menu List -->
......
......@@ -53,16 +53,12 @@
<view class="h-[1rpx] bg-gray-100 my-[20rpx]"></view>
<!-- Actions -->
<view class="flex justify-end gap-[24rpx]">
<view class="flex items-center text-blue-600" @tap="onView(item)">
<IconFont name="eye" size="14" class="mr-[8rpx]" />
<text class="text-[24rpx]">查看</text>
</view>
<view class="flex items-center text-red-500" @tap="onDelete(item)">
<IconFont name="del" size="14" class="mr-[8rpx]" />
<text class="text-[24rpx]">删除</text>
</view>
</view>
<ListItemActions
:viewable="true"
:deletable="true"
@view="onView(item)"
@delete="onDelete(item)"
/>
</view>
<!-- Empty State -->
......@@ -85,6 +81,7 @@ import { useFileOperation } from '@/composables/useFileOperation'
import IconFont from '@/components/IconFont.vue'
import TabBar from '@/components/TabBar.vue'
import NavHeader from '@/components/NavHeader.vue'
import ListItemActions from '@/components/ListItemActions/index.vue'
import Taro from '@tarojs/taro'
const go = useGo()
......