feat: 收藏工具
This commit is contained in:
@@ -1,24 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { RouterLink } from "vue-router"
|
||||
// import { Star } from '@element-plus/icons-vue'
|
||||
import { StarFilled } from '@element-plus/icons-vue'
|
||||
import { useToolsStore } from '@/store/modules/tools'
|
||||
// import { ElMessage } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRoute } from "vue-router"
|
||||
//store
|
||||
const toolsStore = useToolsStore()
|
||||
const route = useRoute()
|
||||
// const getToolsCate = async () => {
|
||||
// try {
|
||||
// await toolsStore.getToolCate()
|
||||
// } catch (error: any) {
|
||||
// ElMessage.error(error.message)
|
||||
// }
|
||||
// }
|
||||
|
||||
// 取消收藏
|
||||
const removeCollect = (url: string) => {
|
||||
const success = toolsStore.removeCollect(url)
|
||||
if (success) {
|
||||
ElMessage.success('已取消收藏');
|
||||
} else {
|
||||
ElMessage.error('取消收藏失败');
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// getToolsCate()
|
||||
toolsStore.loadCollectedTools()
|
||||
if (route.query && route.query.value) {//底部导航跳转过来的则定位到响应位置
|
||||
document?.querySelector('#' + `${route.query.value}`)?.scrollIntoView();
|
||||
} else {//其他位置跳转过来不需要定位的则定位到顶部
|
||||
@@ -29,7 +31,46 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="md:mr-6 c-xs:mr-0">
|
||||
<!-- list -->
|
||||
<!-- 收藏工具 -->
|
||||
<div id="collect">
|
||||
<!-- 收藏标题 -->
|
||||
<div class="mt-8 mb-3 text-xl font-bold text-[--base-black]">
|
||||
收藏工具
|
||||
</div>
|
||||
<!-- 收藏卡片 -->
|
||||
<div v-if="toolsStore.collectedTools.length > 0" class="flex justify-between flex-wrap self-card-div c-xs:ml-0" :gutter="10">
|
||||
<router-link v-for="(item, index) in toolsStore.collectedTools" :key="index" :to="item.url" class="flex flex-col mt-5 border-solid rounded-2xl border-gray w-[24%] p-2 bg-white hover:shadow-md c-xs:w-[99.5%] c-md:w-[24%] c-sm:w-[32%] p-5 hover:-translate-y-2 duration-300">
|
||||
<div class="flex items-center border-b pb-2">
|
||||
<el-image :src="item.logo" class="w-10 h-10 min-h-[2.5rem] min-w-[2.5rem] rounded-full"></el-image>
|
||||
<div class="flex flex-col ml-2 w-full">
|
||||
<div class="flex justify-between">
|
||||
<div class="font-semibold text-lg line-clamp-1">{{ item.title }}</div>
|
||||
<el-button
|
||||
:icon="StarFilled"
|
||||
circle
|
||||
size="small"
|
||||
@click.prevent="removeCollect(item.url)"
|
||||
type="warning"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<el-text size="small">{{ item.cate }}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mt-2">
|
||||
<el-text line-clamp="2">{{ item.desc }}</el-text>
|
||||
</div>
|
||||
</router-link>
|
||||
<!-- 占位 div -->
|
||||
<div class="w-[24%] c-md:w-[24%] c-sm:w-[32%] "></div>
|
||||
</div>
|
||||
<div v-else class="mt-5 p-5 bg-white rounded-2xl">
|
||||
<el-empty description="暂无收藏工具" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工具分类列表 -->
|
||||
<div v-for="(cate, index) in toolsStore.cates" :key="index">
|
||||
<!-- cate title -->
|
||||
<div class="mt-8 mb-3 text-xl font-bold text-[--base-black]" :id="'cate_' + cate.id">
|
||||
@@ -46,7 +87,6 @@ onMounted(() => {
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<el-text size="small">{{ item.cate }}</el-text>
|
||||
<!-- <el-button :icon="Star" circle size="small" @click.prevent="collect(item.id)" type="warning" :plain="!toolsStore.collectIds.includes(item.id)"/> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
// import { Star } from '@element-plus/icons-vue'
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import { Star, StarFilled } from '@element-plus/icons-vue'
|
||||
import { onMounted, reactive, ref, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useToolsStore } from '@/store/modules/tools'
|
||||
// import { ElMessageBox } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {rtrim} from '@/utils/string'
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
id: Number
|
||||
})
|
||||
const props = defineProps({ title: String, id: Number})
|
||||
const route = useRoute()
|
||||
//查询参数
|
||||
const searchParam = reactive({
|
||||
@@ -18,41 +15,67 @@ const searchParam = reactive({
|
||||
})
|
||||
//store
|
||||
const toolsStore = useToolsStore()
|
||||
const isCollected = ref(false)
|
||||
|
||||
//根据路由查询tool id
|
||||
const getToolInfo = async () => {
|
||||
let routeStr = route.path
|
||||
searchParam.route = rtrim(routeStr, '/')
|
||||
await toolsStore.getToolInfo(searchParam)
|
||||
// 检查收藏状态
|
||||
isCollected.value = toolsStore.isCollected(searchParam.route)
|
||||
}
|
||||
|
||||
// 收藏/取消收藏
|
||||
const toggleCollect = () => {
|
||||
const toolInfo = toolsStore.currentTool
|
||||
if (!toolInfo) {
|
||||
ElMessage.warning('获取工具信息失败');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCollected.value) {
|
||||
// 取消收藏
|
||||
const success = toolsStore.removeCollect(toolInfo.url)
|
||||
if (success) {
|
||||
isCollected.value = false
|
||||
ElMessage.success('已取消收藏');
|
||||
} else {
|
||||
ElMessage.error('取消收藏失败');
|
||||
}
|
||||
} else {
|
||||
// 添加收藏
|
||||
const success = toolsStore.addCollect(toolInfo)
|
||||
if (success) {
|
||||
isCollected.value = true
|
||||
ElMessage.success('收藏成功');
|
||||
} else {
|
||||
ElMessage.error('收藏失败,可能已经收藏过了');
|
||||
}
|
||||
}
|
||||
}
|
||||
//收藏
|
||||
// const collect = () => {
|
||||
// ElMessageBox({
|
||||
// title: '提示',
|
||||
// message: '请使用快捷键`Ctrl+D`进行收藏'
|
||||
// })
|
||||
// }
|
||||
|
||||
onMounted(() => {
|
||||
toolsStore.loadCollectedTools()
|
||||
getToolInfo()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-between rounded-2xl bg-white p-4 mt-5 mb-5">
|
||||
<!-- <div class="text-gray-600">
|
||||
<div class="flex">
|
||||
<RouterLink to="/" class="flex items-center">
|
||||
<el-icon color="blue"><Back /></el-icon>
|
||||
<div>返回首页</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="flex justify-between items-center rounded-2xl bg-white p-4 mt-5 mb-5">
|
||||
<div class="text-xl">
|
||||
{{ props.title }}
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
:icon="isCollected ? StarFilled : Star"
|
||||
:type="isCollected ? 'warning' : 'default'"
|
||||
@click="toggleCollect"
|
||||
>
|
||||
{{ isCollected ? '已收藏' : '收藏' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { RouterLink } from 'vue-router';
|
||||
import { useToolsStore } from '@/store/modules/tools';
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '推荐工具'
|
||||
},
|
||||
cateId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
currentUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const toolsStore = useToolsStore();
|
||||
const recommendedTools = ref([]);
|
||||
|
||||
// 随机打乱数组
|
||||
const shuffleArray = (array: any[]) => {
|
||||
const shuffled = [...array];
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
return shuffled;
|
||||
};
|
||||
|
||||
// 计算推荐工具
|
||||
const getRecommendedTools = () => {
|
||||
if (!props.cateId) return [];
|
||||
|
||||
// 获取当前分类下的所有工具
|
||||
let allTools = [];
|
||||
toolsStore.cates.forEach(cate => {
|
||||
if (cate.id === props.cateId) {
|
||||
allTools = cate.list;
|
||||
}
|
||||
});
|
||||
|
||||
// 过滤掉当前工具
|
||||
const otherTools = allTools.filter(tool => {
|
||||
const toolUrl = tool.url.replace(/\/$/, '');
|
||||
const currentUrl = props.currentUrl.replace(/\/$/, '');
|
||||
return toolUrl !== currentUrl;
|
||||
});
|
||||
|
||||
// 随机打乱并取前4个
|
||||
const shuffled = shuffleArray(otherTools);
|
||||
return shuffled.slice(0, 4);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 加载工具分类
|
||||
toolsStore.getToolCate().then(() => {
|
||||
recommendedTools.value = getRecommendedTools();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 推荐工具 -->
|
||||
<div class="mt-3 rounded-2xl bg-white p-4">
|
||||
<el-divider content-position="left">{{ props.title }}</el-divider>
|
||||
<div class="m-4">
|
||||
<div v-if="recommendedTools.length > 0" class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<router-link
|
||||
v-for="tool in recommendedTools"
|
||||
:key="tool.url"
|
||||
:to="tool.url"
|
||||
class="flex flex-col items-center p-3 border rounded-lg hover:shadow-md transition-shadow"
|
||||
>
|
||||
<el-image :src="tool.logo" class="w-12 h-12 mb-2 rounded-full"></el-image>
|
||||
<div class="text-center">
|
||||
<div class="font-medium text-sm line-clamp-1">{{ tool.title }}</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
<div v-else class="text-center text-gray-500 py-4">
|
||||
暂无推荐工具
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -720,7 +720,7 @@ export function urlKeyMap() {
|
||||
//获取工具
|
||||
export function getTools(data: ToolsReqData) {
|
||||
//接收参数
|
||||
const { cateId, title } = data
|
||||
const { cateId, title, route } = data
|
||||
//获取工具list
|
||||
let list = toolsList()
|
||||
//标题筛选
|
||||
@@ -738,6 +738,15 @@ export function getTools(data: ToolsReqData) {
|
||||
return item.cateId == cateId;
|
||||
});
|
||||
}
|
||||
//路由筛选
|
||||
if (route) {
|
||||
list = list.filter(item => {
|
||||
// 移除末尾的斜杠后比较
|
||||
const itemUrl = item.url.replace(/\/$/, '');
|
||||
const routeUrl = route.replace(/\/$/, '');
|
||||
return itemUrl === routeUrl;
|
||||
});
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
|
||||
@@ -7,17 +7,19 @@ import type { IpReqData, IpInfo } from '@/api/ip/type'
|
||||
import type { WebInfo, WebInfoReqData } from '@/api/webinfo/type'
|
||||
import { fetchWebInfo } from '@/api/webinfo'
|
||||
|
||||
// 收藏工具列表存储键名
|
||||
const COLLECTED_TOOLS_KEY = 'collected_tools';
|
||||
|
||||
export const useToolsStore = defineStore('tools', {
|
||||
//用来存放变量
|
||||
state: () => ({
|
||||
list: [] as ToolsInfo[],
|
||||
toolInfo: {} as ToolsInfo,
|
||||
currentTool: {} as ToolsInfo,
|
||||
cates: [] as any[],
|
||||
recommends: [] as ToolsInfo[],
|
||||
ipData: {} as IpInfo,
|
||||
webInfo: {} as WebInfo,
|
||||
collect: [] as ToolsInfo[],
|
||||
collectIds: [] as number[],
|
||||
collectedTools: [] as ToolsInfo[],
|
||||
}),
|
||||
//方法
|
||||
actions: {
|
||||
@@ -32,7 +34,7 @@ export const useToolsStore = defineStore('tools', {
|
||||
async getToolInfo(data: ToolsReqData) {
|
||||
//发送请求
|
||||
const result: any = await getTools(data)
|
||||
this.toolInfo = result
|
||||
this.currentTool = result[0] || {}
|
||||
return result
|
||||
},
|
||||
//获取tools cate
|
||||
@@ -60,5 +62,54 @@ export const useToolsStore = defineStore('tools', {
|
||||
return Promise.reject(new Error(result.message))
|
||||
}
|
||||
},
|
||||
// 加载收藏工具
|
||||
loadCollectedTools() {
|
||||
try {
|
||||
const stored = localStorage.getItem(COLLECTED_TOOLS_KEY);
|
||||
if (stored) {
|
||||
this.collectedTools = JSON.parse(stored);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载收藏工具失败:', error);
|
||||
this.collectedTools = [];
|
||||
}
|
||||
},
|
||||
// 保存收藏工具到本地存储
|
||||
saveCollectedTools() {
|
||||
try {
|
||||
localStorage.setItem(COLLECTED_TOOLS_KEY, JSON.stringify(this.collectedTools));
|
||||
} catch (error) {
|
||||
console.error('保存收藏工具失败:', error);
|
||||
}
|
||||
},
|
||||
// 添加收藏
|
||||
addCollect(tool: ToolsInfo) {
|
||||
// 检查是否已经收藏
|
||||
const isCollected = this.collectedTools.some(t => t.url === tool.url);
|
||||
if (!isCollected) {
|
||||
this.collectedTools.push(tool);
|
||||
this.saveCollectedTools();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// 移除收藏
|
||||
removeCollect(url: string) {
|
||||
const index = this.collectedTools.findIndex(tool => tool.url === url);
|
||||
if (index > -1) {
|
||||
this.collectedTools.splice(index, 1);
|
||||
this.saveCollectedTools();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// 检查是否已收藏
|
||||
isCollected(url: string) {
|
||||
const normalizedUrl = url.replace(/\/$/, '');
|
||||
return this.collectedTools.some(tool => {
|
||||
const toolUrl = tool.url.replace(/\/$/, '');
|
||||
return toolUrl === normalizedUrl;
|
||||
});
|
||||
},
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user