对接数据
This commit is contained in:
@ -1,153 +0,0 @@
|
||||
<template>
|
||||
<div :id="id">
|
||||
<div class="bar-list">
|
||||
<div class="top-box">
|
||||
<div>
|
||||
<img :src="`/logo/${tool.img}_check.png`" />
|
||||
<span class="title-text .gradient-color">
|
||||
{{tool.name}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="more pointer" v-if="!tagList.length">
|
||||
View more<i class="el-icon-arrow-right"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tagList.length">
|
||||
<div class="tags" ref="tagsContainer" @mousedown="startDrag" @mousemove="onDrag" @mouseup="stopDrag"
|
||||
@mouseleave="stopDrag">
|
||||
<div class="tag-item" v-for="(item,index) in 10">
|
||||
AI Image & illustration Generation
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="more pointer" v-if="tagList.length">
|
||||
View more<i class="el-icon-arrow-right"></i>
|
||||
</div>
|
||||
<div class="item-card">
|
||||
<div class="card-caontainer" v-for="(item,index) in 8">
|
||||
<div class="title">
|
||||
<img src="/logo/collect.png" />
|
||||
<span>
|
||||
Skywork
|
||||
</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
Latest ToolsLatest ToolsLatest ToolsLatest Tools
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['tool','id'],
|
||||
data() {
|
||||
return {
|
||||
isDragging: false,
|
||||
startX: 0,
|
||||
scrollLeft: 0,
|
||||
tagList: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
startDrag(e) {
|
||||
this.isDragging = true;
|
||||
this.startX = e.pageX - this.$refs.tagsContainer.offsetLeft;
|
||||
this.scrollLeft = this.$refs.tagsContainer.scrollLeft;
|
||||
},
|
||||
onDrag(e) {
|
||||
if (!this.isDragging) return;
|
||||
e.preventDefault();
|
||||
const x = e.pageX - this.$refs.tagsContainer.offsetLeft;
|
||||
const walk = (x - this.startX) * 2; // 调整滑动速度
|
||||
this.$refs.tagsContainer.scrollLeft = this.scrollLeft - walk;
|
||||
},
|
||||
stopDrag() {
|
||||
this.isDragging = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bar-list {
|
||||
margin: 50px 0;
|
||||
}
|
||||
|
||||
.top-box {
|
||||
@include display-flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.title-text {
|
||||
|
||||
font-weight: 600;
|
||||
font-size: $larg-font-size;
|
||||
}
|
||||
}
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
gap: 12px;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
// cursor: grab;
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
flex-shrink: 0;
|
||||
padding: 10px;
|
||||
@include gradient-border($linear-gradient-start, $linear-gradient-end);
|
||||
// cursor: grab;
|
||||
/* 显示抓取手势 */
|
||||
|
||||
&:active {
|
||||
// cursor: grabbing;
|
||||
/* 抓取中状态 */
|
||||
}
|
||||
}
|
||||
|
||||
.more {
|
||||
text-align: right;
|
||||
color: $grey-color;
|
||||
font-size: $mid-font-size;
|
||||
}
|
||||
|
||||
.item-card {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.card-caontainer {
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0px 10px 30px 0px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border: 1px solid #E2E8F0;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
color: $main-font-color;
|
||||
font-size: $big-font-size;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
color: $grey-color;
|
||||
// line-height: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
110
pages/Home/components/PopularToolList.vue
Normal file
110
pages/Home/components/PopularToolList.vue
Normal file
@ -0,0 +1,110 @@
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pop_tools: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getToolsAsyncData() {
|
||||
const {data: res} = await this.$api.tool.getToolsList({isHot: 1, page: 1, limit: 6});
|
||||
const {code, data} = res;
|
||||
if (code === 0 && data.list) {
|
||||
this.pop_tools = data.list;
|
||||
}
|
||||
},
|
||||
// 跳转工具详情页
|
||||
goToToolDetail(item) {
|
||||
if (item.slug && item.categorySlug) {
|
||||
this.recordToolClick(item);
|
||||
this.$router.push(`/detail?tool_slug=${item.slug}&category_slug=${item.categorySlug}`);
|
||||
}
|
||||
},
|
||||
// 记录工具点击次数
|
||||
async recordToolClick(item) {
|
||||
if (item.id) {
|
||||
await this.$api.tool.recordToolClickNum(item.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getToolsAsyncData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="clearfix">
|
||||
<img src="/logo/hot.png" :style="{marginRight: '6px'}" alt=""/>
|
||||
Popular Tools
|
||||
</div>
|
||||
<div class="line" />
|
||||
<div class="pop-item">
|
||||
<div v-for="item in pop_tools" class="box" @click="goToToolDetail(item)">
|
||||
<div class="img-box">
|
||||
<img :src="item.iconUrl || ''" alt=""/>
|
||||
</div>
|
||||
<div class="tool-name">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.clearfix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: $larg-font-size;
|
||||
font-weight: bold;
|
||||
font-family: 'Poppins-SemiBold', serif;
|
||||
}
|
||||
|
||||
.img-box {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: 15px 0 12px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #E2E8F0;
|
||||
@include flex-center;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.15);
|
||||
cursor: pointer;
|
||||
@include gradient-border($linear-gradient-start, $linear-gradient-end);
|
||||
box-shadow: 0 5px 7px 0 #00000014;
|
||||
}
|
||||
}
|
||||
|
||||
.pop-item {
|
||||
display: grid;
|
||||
grid-auto-rows: 1fr;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.tool-name {
|
||||
font-family: 'Poppins-Medium', serif;
|
||||
color: #64748B;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
40
pages/Home/components/ScrollList.vue
Normal file
40
pages/Home/components/ScrollList.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<script>
|
||||
import BScroll from '@better-scroll/core';
|
||||
|
||||
export default {
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.bs = new BScroll(this.$refs.wrapper, {
|
||||
scrollX: true,
|
||||
scrollY: false,
|
||||
click: true,
|
||||
bounce: false
|
||||
})
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.bs && this.bs.destroy()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="wrapper" class="btn-wrapper">
|
||||
<div class="btn-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-wrapper {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%
|
||||
}
|
||||
.btn-content {
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
width: max-content;
|
||||
}
|
||||
</style>
|
||||
113
pages/Home/components/ToolItemCard.vue
Normal file
113
pages/Home/components/ToolItemCard.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
categorySlug: {
|
||||
type: String,
|
||||
default: '',
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
goToToolDetail() {
|
||||
if (this.config.slug && this.categorySlug) {
|
||||
this.recordToolClick(this.config);
|
||||
this.$router.push(`/detail?tool_slug=${this.config.slug}&category_slug=${this.categorySlug}`);
|
||||
}
|
||||
},
|
||||
// 记录点击次数
|
||||
async recordToolClick(item) {
|
||||
if (item.id) {
|
||||
await this.$api.tool.recordToolClickNum(item.id);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 检测文本是否包含有效的HTML标签
|
||||
* @param {string} text - 要检测的文本
|
||||
* @returns {boolean} - 是否包含有效的HTML标签
|
||||
*/
|
||||
containsHtml(text) {
|
||||
if (!text || typeof text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
// 简单检测常见HTML标签格式,实际项目中可能需要更复杂的验证
|
||||
const htmlRegex = /<[^>]*>/;
|
||||
return htmlRegex.test(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card-caontainer" @click="goToToolDetail">
|
||||
<div class="title">
|
||||
<img :src="config.iconUrl || '/'" alt="" />
|
||||
<span style="font-size: 18px">
|
||||
{{ config.name || '' }}
|
||||
</span>
|
||||
</div>
|
||||
<!--<div class="text"-->
|
||||
<!-- v-if="config.memo && containsHtml(config.memo + '</p>')"-->
|
||||
<!-- v-html="config.memo"></div>-->
|
||||
<div class="text">
|
||||
{{ config.memo ? config.memo : '' }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card-caontainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.05);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border: 1px solid #E2E8F0;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.08);
|
||||
@include gradient-border($linear-gradient-start, $linear-gradient-end);
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
@include gradient-border($linear-gradient-start, $linear-gradient-end);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $main-font-color;
|
||||
font-size: $big-font-size;
|
||||
font-weight: 600;
|
||||
font-family: 'Poppins-SemiBold', serif;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
color: $grey-color;
|
||||
font-family: 'Poppins-Regular', serif;
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -3,12 +3,10 @@
|
||||
<div v-for="item in list" class="tools" @click="checkTool(item)">
|
||||
<span class="tool-card" :class="item.active?'checkedBg':''">
|
||||
<span class="content">
|
||||
<img :src="`/logo/${item.img}_${item.active ? 'checkd' : 'un'}.png`" />
|
||||
<img :src="''" alt="" />
|
||||
<span>{{ item.name }}</span>
|
||||
</span>
|
||||
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -28,9 +26,14 @@
|
||||
},
|
||||
methods: {
|
||||
checkTool(item) {
|
||||
this.list.forEach(i => this.$set(i, 'active', false)) // 重置其他项
|
||||
this.$set(item, 'active', true)
|
||||
this.$emit('tool-selected', item.name) // 发送工具名称
|
||||
if (item.active) {
|
||||
this.$set(item, 'active', false);
|
||||
} else {
|
||||
// 否则,先重置所有项,再激活当前项
|
||||
this.list.forEach(i => this.$set(i, 'active', false));
|
||||
this.$set(item, 'active', true);
|
||||
this.$emit('tool-selected', item.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -48,16 +51,20 @@
|
||||
|
||||
.tool-card {
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0px 10px 30px 0px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.05);
|
||||
border-radius: 12px;
|
||||
padding: 10px 16px;
|
||||
display: inline-block;
|
||||
font-weight: 600;
|
||||
font-family: 'Poppins-SemiBold', serif;
|
||||
|
||||
.content {
|
||||
@include display-flex;
|
||||
|
||||
img {
|
||||
margin-right: 5px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
142
pages/Home/components/Toolbar.vue
Normal file
142
pages/Home/components/Toolbar.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div :id="id">
|
||||
<div class="bar-list">
|
||||
<div class="top-box">
|
||||
<div class="title-wrap">
|
||||
<img :src="`/logo/${tool.img}_check.png`" alt="" />
|
||||
<span class="title-text gradient-color">
|
||||
{{tool.categoryName}}
|
||||
</span>
|
||||
</div>
|
||||
<div @click="goToViewMore" class="more pointer" v-if="!tool.tagList">
|
||||
View more<i class="el-icon-arrow-right"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tool.tagList && tool.tagList.length">
|
||||
<ScrollList>
|
||||
<div class="tags">
|
||||
<div class="tag-item" v-for="(item,index) in tool.tagList" :key="index">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollList>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div @click="goToViewMore" class="more pointer" v-if="tool.tagList && tool.tagList.length">
|
||||
View more<i class="el-icon-arrow-right"></i>
|
||||
</div>
|
||||
<div class="item-card" v-if="tool.tools && tool.tools.length">
|
||||
<div v-for="(item, index) in tool.tools" :key="index" style="min-height: 110px">
|
||||
<ToolItemCard :config="item" :categorySlug="categorySlug" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ScrollList from "~/pages/Home/components/ScrollList.vue";
|
||||
import ToolItemCard from "~/pages/Home/components/ToolItemCard.vue";
|
||||
|
||||
export default {
|
||||
components: {ToolItemCard, ScrollList},
|
||||
props: {
|
||||
tool: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
categorySlug: {
|
||||
type: String,
|
||||
default: '',
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 查看更多
|
||||
goToViewMore() {
|
||||
if (this.categorySlug) {
|
||||
this.$router.push('/home/more?category_slug=' + this.categorySlug)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.bar-list {
|
||||
margin: 50px 0;
|
||||
}
|
||||
|
||||
.top-box {
|
||||
@include display-flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.title-text {
|
||||
font-weight: 600;
|
||||
font-size: $larg-font-size;
|
||||
font-family: 'Poppins-SemiBold', serif;
|
||||
}
|
||||
}
|
||||
|
||||
.tags {
|
||||
margin-top: 27px;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
gap: 12px;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
user-select: none;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
flex-shrink: 0;
|
||||
padding: 10px;
|
||||
@include gradient-border($linear-gradient-start, $linear-gradient-end);
|
||||
/* 显示抓取手势 */
|
||||
font-family: 'Poppins-SemiBold', sans-serif;
|
||||
color: #64748B;
|
||||
font-weight: 600;
|
||||
|
||||
&:active {
|
||||
// cursor: grabbing;
|
||||
/* 抓取中状态 */
|
||||
}
|
||||
}
|
||||
|
||||
.more {
|
||||
display: block;
|
||||
text-align: right;
|
||||
color: $grey-color;
|
||||
font-size: $mid-font-size;
|
||||
font-family: 'Poppins-Regular', serif;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.item-card {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
||||
@ -9,297 +9,167 @@
|
||||
Get global AI tools in one stop
|
||||
</div>
|
||||
<div class="third-text">
|
||||
It includes over a <span class="special">thousand global AI tools</span>, covering writing, images,
|
||||
It includes over a <a href="/" class="special">thousand global AI tools</a>, covering writing, images,
|
||||
videos, audio, programming,
|
||||
music, design, chatting, etc., and recommends learning platforms, frameworks and models
|
||||
</div>
|
||||
<div class="input-container">
|
||||
<input type="text" placeholder="Please enter the key words">
|
||||
<i class="el-icon-search gradient-color search-icon pointer"></i>
|
||||
<!-- 修改输入框容器 -->
|
||||
<div style="margin: 68px auto 62px">
|
||||
<SearchSelectInput />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card flex">
|
||||
<div class="left-card card-box">
|
||||
<el-carousel indicator-position="outside">
|
||||
<el-carousel-item v-for="item in 4" :key="item">
|
||||
<el-carousel :autoplay="false" height="354px" autoplay :interval="8000">
|
||||
<el-carousel-item v-for="(item, i) in banner" :key="i">
|
||||
<img :src="item.imageUrl || ''" alt="" style="height: 354px; width: 100%; border-radius: 12px" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
|
||||
</div>
|
||||
<div class="right-card card-box">
|
||||
<div class="clearfix">
|
||||
<img src="/logo/hot.png" />
|
||||
Popular Tools
|
||||
</div>
|
||||
<div class="line">
|
||||
|
||||
</div>
|
||||
<div class="pop-item">
|
||||
<div v-for="item in pop_tools" class="box">
|
||||
<div class="img-box">
|
||||
<img src="/logo/bottom-logo.png" />
|
||||
</div>
|
||||
<div>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PopularToolList />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-list">
|
||||
<ToolList :list="list" @tool-selected="scrollToTool"></ToolList>
|
||||
</div>
|
||||
<div class="line">
|
||||
</div>
|
||||
<div class="toolbar" v-for="tool in list">
|
||||
<Toolbar :tool="tool" :id="`tool-${tool.name}`"></Toolbar>
|
||||
</div>
|
||||
|
||||
<NuxtChild />
|
||||
</IntegratedLayout>
|
||||
<!-- <el-backtop target=".scroll-container" :bottom="100">
|
||||
<div>
|
||||
<img src="/logo/back-top.png" />
|
||||
</div>
|
||||
</el-backtop> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ToolList from './ToolList.vue'
|
||||
import Toolbar from './Toolbar.vue'
|
||||
export default {
|
||||
name: 'Home',
|
||||
components: {
|
||||
ToolList,
|
||||
Toolbar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pop_tools: [{
|
||||
name: 'Gemini',
|
||||
src: ''
|
||||
},
|
||||
{
|
||||
name: 'Gemini',
|
||||
src: ''
|
||||
},
|
||||
{
|
||||
name: 'Gemini',
|
||||
src: ''
|
||||
},
|
||||
{
|
||||
name: 'Gemini',
|
||||
src: ''
|
||||
},
|
||||
{
|
||||
name: 'Gemini',
|
||||
src: ''
|
||||
},
|
||||
{
|
||||
name: 'Gemini',
|
||||
src: ''
|
||||
},
|
||||
],
|
||||
list: [{
|
||||
name: 'AI Image Tool',
|
||||
img: 'image'
|
||||
},
|
||||
{
|
||||
name: 'AI Office Tools',
|
||||
img: 'office'
|
||||
},
|
||||
{
|
||||
name: 'AI Video Tool',
|
||||
img: 'Video'
|
||||
},
|
||||
{
|
||||
name: 'AI Programming Tools',
|
||||
img: 'program'
|
||||
},
|
||||
{
|
||||
name: 'AI Chat Assistant',
|
||||
img: 'chat'
|
||||
},
|
||||
{
|
||||
name: 'AI Writing Tool',
|
||||
img: 'write'
|
||||
},
|
||||
{
|
||||
name: 'AI learning Website',
|
||||
img: 'learn'
|
||||
},
|
||||
{
|
||||
name: 'AI Design Tool',
|
||||
img: 'design'
|
||||
},
|
||||
{
|
||||
name: 'AI Search Engine',
|
||||
img: 'search'
|
||||
},
|
||||
{
|
||||
name: 'AI Development Platform',
|
||||
img: 'develop'
|
||||
},
|
||||
{
|
||||
name: 'AI Audio Tool',
|
||||
img: 'audio'
|
||||
},
|
||||
{
|
||||
name: 'AI Model Evaluation',
|
||||
img: 'model'
|
||||
},
|
||||
{
|
||||
name: 'AI Prompt Command',
|
||||
img: 'prompt'
|
||||
},
|
||||
{
|
||||
name: 'AI Content Detection',
|
||||
img: 'content'
|
||||
},
|
||||
{
|
||||
name: 'AI Agent',
|
||||
img: 'agent'
|
||||
},
|
||||
{
|
||||
name: 'Model Training',
|
||||
img: 'train'
|
||||
}
|
||||
]
|
||||
import PopularToolList from "@/pages/Home/components/PopularToolList.vue";
|
||||
import GlobalLoading from "@/components/GlobalLoading.vue";
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
components: {
|
||||
PopularToolList,
|
||||
GlobalLoading,
|
||||
},
|
||||
computed: {
|
||||
banner() {
|
||||
const bannerConfig = this.$store.getters.bannerConfig;
|
||||
if (bannerConfig.home && bannerConfig.home.length > 0) {
|
||||
return bannerConfig.home;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
scrollToTool(toolName) {
|
||||
const target = document.getElementById(`tool-${toolName}`)
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
})
|
||||
return [];
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('getBannerConfig');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped> #home-page {
|
||||
flex: 1;
|
||||
overflow-y: auto; // 必须启用滚动
|
||||
position: relative; // 添加相对定位
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain; // 控制图片自适应
|
||||
background-position: top center;
|
||||
background-image: url('/logo/mask.png');
|
||||
}
|
||||
|
||||
.top-title {
|
||||
@include flex-center;
|
||||
flex-direction: column;
|
||||
font-weight: bold;
|
||||
margin-top: 120px;
|
||||
|
||||
div {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.first-text {
|
||||
line-height: 90px;
|
||||
font-size: $huge-font-size3;
|
||||
font-weight: 900;
|
||||
font-family: 'Poppins-Bold', serif;
|
||||
}
|
||||
|
||||
.second-text {
|
||||
margin: 18px 0;
|
||||
font-family: 'Poppins-Bold', serif;
|
||||
font-size: $huge-font-size2;
|
||||
font-weight: 900;
|
||||
line-height: 75px;
|
||||
}
|
||||
|
||||
.third-text {
|
||||
width: 716px;
|
||||
font-weight: 500;
|
||||
color: $grey-color;
|
||||
font-size: $normal-font-size;
|
||||
margin-top: 8px;
|
||||
font-family: 'Poppins-Medium', serif;
|
||||
|
||||
.special {
|
||||
color: $main-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr; // 4列布局
|
||||
gap: 11px; // 网格间距
|
||||
margin: 0 auto;
|
||||
|
||||
.card-box {
|
||||
background: $white;
|
||||
box-shadow: 0 18px 33px 0 rgba(0, 0, 0, 0.05);
|
||||
border-radius: 12px;
|
||||
border: 1px solid #FFFFFF;
|
||||
}
|
||||
|
||||
.left-card {
|
||||
// min-width: 805px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
::v-deep .el-carousel {
|
||||
.el-carousel__arrow {
|
||||
opacity: 0 !important;
|
||||
transition: none !important;
|
||||
}
|
||||
.el-carousel__indicators {
|
||||
.el-carousel__indicator {
|
||||
height: 4px !important;
|
||||
width: 12px !important;
|
||||
border-radius: 2.66px !important;
|
||||
border: 0.66px solid #2563eb !important;
|
||||
background: transparent !important;
|
||||
margin: 0 3px 0 3px !important;
|
||||
padding: 0 !important;
|
||||
|
||||
.el-carousel__button {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border: none !important;
|
||||
width: 20px !important;
|
||||
background: linear-gradient(0deg, #2563EB 22%, #7B61FF 73%) !important;
|
||||
}
|
||||
|
||||
// 重置其他可能的默认样式
|
||||
&:before,
|
||||
&:after {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#home-page {
|
||||
flex: 1;
|
||||
overflow-y: auto; // 必须启用滚动
|
||||
position: relative; // 添加相对定位
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain; // 控制图片自适应
|
||||
background-position: top center;
|
||||
background-image: url('/logo/mask.png');
|
||||
}
|
||||
|
||||
|
||||
|
||||
.top-title {
|
||||
@include flex-center;
|
||||
flex-direction: column;
|
||||
font-weight: bold;
|
||||
height: 400px;
|
||||
margin-top: 180px;
|
||||
|
||||
div {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.first-text {
|
||||
line-height: 90px;
|
||||
font-size: $huge-font-size2;
|
||||
}
|
||||
|
||||
.second-text {
|
||||
margin: 18px 0;
|
||||
font-size: $huge-font-size3;
|
||||
}
|
||||
|
||||
.third-text {
|
||||
width: 716px;
|
||||
height: 81px;
|
||||
font-weight: 500;
|
||||
color: $grey-color;
|
||||
font-size: $normal-font-size;
|
||||
|
||||
.special {
|
||||
color: $main-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.card {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr; // 4列布局
|
||||
gap: 20px; // 网格间距
|
||||
margin: 0 auto;
|
||||
height: 366px;
|
||||
|
||||
.card-box {
|
||||
background: $white;
|
||||
box-shadow: 0px 18px 33px 0px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 12px;
|
||||
border: 1px solid #FFFFFF;
|
||||
}
|
||||
|
||||
.left-card {
|
||||
// min-width: 805px;
|
||||
}
|
||||
|
||||
.right-card {
|
||||
padding: 20px;
|
||||
// min-width: 372px;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: $larg-font-size;
|
||||
}
|
||||
|
||||
.img-box {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: 15px 0;
|
||||
background: #FFFFFF;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #E2E8F0;
|
||||
@include flex-center;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.15);
|
||||
cursor: pointer;
|
||||
@include gradient-border($linear-gradient-start, $linear-gradient-end);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.pop-item {
|
||||
display: grid;
|
||||
grid-auto-rows: 1fr;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px; // 新增间距控制
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.tool-list {
|
||||
margin: 40px 0;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
margin: 60px auto;
|
||||
.right-card {
|
||||
padding: 20px;
|
||||
max-width: 372px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
70
pages/Home/views/List.vue
Normal file
70
pages/Home/views/List.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div v-loading.fullscreen.lock="fullscreenLoading">
|
||||
<div class="tool-list">
|
||||
<ToolList :list="categoryList" @tool-selected="scrollToTool"></ToolList>
|
||||
</div>
|
||||
<div class="line">
|
||||
</div>
|
||||
<div class="toolbar" v-for="tool in toolsGroup">
|
||||
<Toolbar :tool="tool" :id="`tool-${tool.categoryName}`" :category-slug="tool.categorySlug || ''"></Toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Toolbar from "../components/Toolbar.vue";
|
||||
import ToolList from "../components/ToolList.vue";
|
||||
|
||||
export default {
|
||||
components: {ToolList, Toolbar},
|
||||
data() {
|
||||
return {
|
||||
fullscreenLoading: false,
|
||||
categoryList: [],
|
||||
toolsGroup: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
scrollToTool(toolName) {
|
||||
const target = document.getElementById(`tool-${toolName}`)
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
})
|
||||
}
|
||||
},
|
||||
// 获取分类列表
|
||||
async getCategoryAsyncData() {
|
||||
const {data: res} = await this.$api.tool.getCategoryList();
|
||||
const {code, data} = res;
|
||||
if (code === 0 && data.list) {
|
||||
this.categoryList = data.list;
|
||||
}
|
||||
},
|
||||
// 获取分类分组的工具列表
|
||||
async getToolsGroupAsyncData() {
|
||||
const {data: res} = await this.$api.tool.getToolByCategory({limit: 8});
|
||||
const {code, data} = res;
|
||||
if (code === 0 && data.list) {
|
||||
this.toolsGroup = data.list;
|
||||
}
|
||||
},
|
||||
async onLoad() {
|
||||
this.fullscreenLoading = true;
|
||||
await this.getCategoryAsyncData();
|
||||
await this.getToolsGroupAsyncData();
|
||||
this.fullscreenLoading = false;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.onLoad();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tool-list {
|
||||
margin: 60px 0;
|
||||
}
|
||||
</style>
|
||||
109
pages/Home/views/ViewMore.vue
Normal file
109
pages/Home/views/ViewMore.vue
Normal file
@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="content" v-loading.fullscreen.lock="fullscreenLoading">
|
||||
<div class="tag-item" v-if="tagName">
|
||||
{{tagName}}
|
||||
</div>
|
||||
<div class="item-card">
|
||||
<ToolItemCard v-for="(item, index) in toolList" :key="index" :config="item" :category-slug="category_slug" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ToolItemCard from "~/pages/Home/components/ToolItemCard.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ToolItemCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tagName: '',
|
||||
toolList: [],
|
||||
category_slug: '',
|
||||
fullscreenLoading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 按分类获取工具列表
|
||||
async getToolsByTag(slug) {
|
||||
if (slug) {
|
||||
this.fullscreenLoading = true;
|
||||
const params = {categorySlug: slug};
|
||||
const {data: res} = await this.$api.tool.getToolsList(params);
|
||||
const {code, data} = res;
|
||||
if (code === 0 && data.list) {
|
||||
this.toolList = data.list;
|
||||
}
|
||||
this.fullscreenLoading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听路由变化
|
||||
'$route'(to) {
|
||||
const slug = to.query.category_slug;
|
||||
if (slug) {
|
||||
this.category_slug = slug;
|
||||
this.getToolsByTag(slug);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 组件挂载时也检查一次路由参数
|
||||
const slug = this.$route.query.category_slug;
|
||||
if (slug) {
|
||||
this.category_slug = slug;
|
||||
this.getToolsByTag(slug);
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.content {
|
||||
padding-top: 60px;
|
||||
padding-bottom: 100px;
|
||||
.tag-item {
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
border-radius: 12px;
|
||||
font-family: 'Poppins-SemiBold', sans-serif;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
background: $header-backgroungd;
|
||||
}
|
||||
.item-card {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
margin-top: 30px;
|
||||
|
||||
.card-caontainer {
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.05);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border: 1px solid #E2E8F0;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
color: $main-font-color;
|
||||
font-size: $big-font-size;
|
||||
font-weight: 600;
|
||||
font-family: 'Poppins-SemiBold', serif;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
color: $grey-color;
|
||||
font-family: 'Poppins-Regular', serif;
|
||||
// line-height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user