Files
AIProd/pages/ToolDetail/index.vue
2025-10-24 15:45:38 +08:00

344 lines
8.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div id="normal-container" class="tool-detail">
<IntegratedLayout>
<div class="content">
<!--面包屑-->
<div class="bread-menu">
<span>Home</span>
<i class="el-icon-arrow-right"></i>
<span>{{ category_slug }}</span>
<i class="el-icon-arrow-right"></i>
<span class="crumbs gradient-color">{{ tool_detail.name || '' }}</span>
</div>
<!--标题-->
<p class="title-text">{{ tool_detail.name || '' }}</p>
<!--评分-->
<div class="rate-box">
<Rate v-model="tool_detail.rating" readonly />
<div class="flex" style="gap: 20px">
<ThumbBtn
:like-count="tool_detail.likeCount || 0"
:id="tool_detail.id"
type="tool"
@like-success="refreshToolDetail"
/>
<CommentBtn :comment-count="commentCount" />
</div>
</div>
<!--工具内容-->
<div class="tool-content">
<div class="left-content flex flex-col">
<div class="terms-item">
<div class="item-title">
<img src="/ToolDetail/icon_note.png" alt="">
<span>Introduction: </span>
</div>
<div class="item-content">
{{ tool_detail.memo || '' }}
</div>
</div>
<div class="terms-item">
<div class="item-title">
<img src="/ToolDetail/icon_clock.png" alt="">
<span>Data update: </span>
</div>
<div class="item-content">{{ tool_detail.updatedAt || '' }}</div>
</div>
<div class="tags">
<div class="tag-item" v-for="(it, index) in tagList" :key="index">{{ it }}</div>
</div>
</div>
<div class="right-content">
<div class="card-website-view">
<iframe :src="tool_detail.url || ''" style="width: 100%; height: 100%; pointer-events: none"></iframe>
</div>
<a :href="tool_detail.url || ''" class="link-button">
<img src="/ToolDetail/icon_link.png" alt="" style="width: 16px; height: 16px" />
<span>Visit website</span>
</a>
</div>
</div>
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="Product information" name="product">
<Product
:other-tools="other_tools"
:tool-id="tool_detail.id || 0"
:tool-slug="tool_slug || ''"
:category-slug="category_slug || ''"
:tool_content="tool_detail.description || ''"
/>
</el-tab-pane>
<el-tab-pane label="Comment" name="comment"></el-tab-pane>
</el-tabs>
<Comment comment-type="tool" :id="tool_detail.id" @update:commentCount="handleCommentCountUpdate" />
</div>
</IntegratedLayout>
</div>
</template>
<script>
import Product from "@/pages/ToolDetail/Product/index.vue";
import Comment from "@/pages/ToolDetail/Comment/index.vue";
import ThumbBtn from "@/pages/ToolDetail/components/ThumbBtn.vue";
import CommentBtn from "@/pages/ToolDetail/components/CommentBtn.vue";
import Rate from "@/components/Rate.vue";
export default {
components: {
Product,
Comment,
ThumbBtn,
CommentBtn,
Rate,
},
data() {
return {
activeName: 'product',
tool_slug: null,
tool_detail: {},
commentCount: 0,
other_tools: [],
category_slug: null,
}
},
mounted() {
this.tool_slug = this.$route.query.tool_slug;
this.category_slug = this.$route.query.category_slug;
this.getAsyncToolDetailData();
this.getAsyncOtherTools();
},
watch: {
'$route'(to, from) {
// 当路由参数发生变化时重新加载数据
if (to.query.tool_slug !== from.query.tool_slug) {
this.tool_slug = to.query.tool_slug;
this.category_slug = to.query.category_slug;
this.resetAndReloadData();
}
}
},
methods: {
// 刷新工具详情数据
async refreshToolDetail() {
await this.getAsyncToolDetailData();
},
// 获取详情数据
async getAsyncToolDetailData() {
if (this.tool_slug) {
const {data: res} = await this.$api.tool.getToolDetailBySlug(this.tool_slug);
const {code, data} = res;
if (code === 0 && data) {
this.tool_detail = {...data};
}
}
},
stringJsonToObject(str) {
// 将json字符串转为对象捕获错误当str为空或者转对象失败时默认返回空数组
try {
return JSON.parse(str);
} catch (e) {
console.error('Error parsing JSON string:', e);
return [];
}
},
handleCommentCountUpdate(count) {
this.commentCount = count;
},
handleClick() {},
// 添加重置和重新加载数据的方法
resetAndReloadData() {
// 重置数据
this.tool_detail = {};
this.commentCount = 0;
this.other_tools = [];
// 重新加载数据
this.getAsyncToolDetailData();
this.getAsyncOtherTools();
},
// 获取其他工具
async getAsyncOtherTools() {
if (!this.category_slug) {
return false;
}
const params = {categorySlug: this.category_slug, page: 1, limit: 9};
const {data: res} = await this.$api.tool.getToolsList(params);
const {code, data} = res;
if (code === 0 && data.list) {
this.other_tools = data.list;
}
}
},
computed: {
tagList() {
return this.stringJsonToObject(this.tool_detail.tags || '[]');
}
},
}
</script>
<style scoped lang="scss">
.tool-detail {
flex: 1;
overflow-y: auto;
position: relative;
.content {
padding-top: 100px;
padding-bottom: 100px;
min-height: 100vh;
.bread-menu {
font-size: $mid-font-size;
font-family: 'Poppins-Medium', serif;
font-weight: 600;
.crumbs {
font-family: 'Poppins-SemiBold', serif;
font-weight: 600;
}
}
.title-text {
font-size: 34px;
color: #1E293B;
font-family: 'Poppins-SemiBold', serif;
font-weight: 600;
margin-top: 55px;
}
.rate-box {
margin-top: 20px;
display: flex;
align-items: center;
justify-content: space-between;
.btn-wrapper {
display: flex;
align-items: center;
gap: 20px;
}
}
.tool-content {
display: flex;
margin-top: 20px;
margin-bottom: 20px;
gap: 50px;
.left-content {
padding-top: 12px;
gap: 50px;
flex: 1;
.terms-item {
margin-bottom: 30px;
display: flex;
align-items: flex-start;
gap: 7px;
.item-title {
display: flex;
align-items: center;
gap: 8px;
color: #1E293B;
font-family: 'Poppins-Medium', serif;
img {
width: 24px;
height: 24px;
}
}
.item-content {
font-family: 'Poppins-Regular', serif;
color: #64748B;
}
}
.tags {
display: flex;
overflow-x: auto;
gap: 12px;
scrollbar-width: none;
-ms-overflow-style: none;
user-select: none;
flex-wrap: wrap;
width: 80%;
.tag-item {
flex-shrink: 0;
padding: 4px 12px;
border-radius: 12px;
@include gradient-border($linear-gradient-start, $linear-gradient-end);
}
}
}
.right-content {
width: 450px;
.card-website-view {
width: 100%;
height: 268px;
background-color: #FFFFFF;
border-radius: 16px;
box-shadow: 0 10px 30px 0 #0000000d;
padding: 19px 25px;
margin-bottom: 28px;
}
.link-button {
width: 280px;
height: 44px;
background: $header-backgroungd;
border-radius: 12px;
margin: auto;
color: #fff;
font-family: 'Poppins-SemiBold', serif;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
&:hover {
opacity: 0.8;
}
&:active {
opacity: 0.8;
}
}
}
}
}
:deep(.el-tabs) {
.el-tabs__header {
.el-tabs__nav-wrap::after {
height: 4px !important;
background-color: #E2E8F0 !important;
}
.el-tabs__item {
font-weight: 700 !important;
font-size: 20px !important;
margin-bottom: 14px;
&.is-active {
background: linear-gradient(90deg, $linear-gradient-start, $linear-gradient-end);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
color: transparent;
}
}
.el-tabs__active-bar {
height: 4px !important;
background: linear-gradient(90deg, $linear-gradient-start, $linear-gradient-end) !important;
}
}
}
}
</style>