5 Commits

Author SHA1 Message Date
2c9377c019 feat: 搭建快捷提问组件与一些样式调整 2026-04-27 16:31:56 +08:00
722dec025e feat: tabs 结构调整 2026-04-27 15:48:11 +08:00
d54ff8895e feat: 搭建了tabs样式 2026-04-27 15:40:01 +08:00
b41ca9c4a2 feat: 首页调整 2026-04-27 14:40:45 +08:00
058c9c0e77 feat: 调整了tab的样式 2026-04-27 14:10:21 +08:00
12 changed files with 297 additions and 99 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -7,6 +7,12 @@
@tap="handleSwitch(0)" @tap="handleSwitch(0)"
> >
<view class="tab-content"> <view class="tab-content">
<image
v-if="leftSelected || leftUnselected"
:src="modelValue === 0 ? (leftSelected || leftUnselected) : (leftUnselected || leftSelected)"
class="tab-image"
mode="aspectFill"
/>
<text class="tab-text">探索发现</text> <text class="tab-text">探索发现</text>
</view> </view>
</view> </view>
@@ -17,8 +23,17 @@
@tap="handleSwitch(1)" @tap="handleSwitch(1)"
> >
<view class="tab-content"> <view class="tab-content">
<image
v-if="rightSelected || rightUnselected"
:src="modelValue === 1 ? (rightSelected || rightUnselected) : (rightUnselected || rightSelected)"
class="tab-image"
mode="aspectFill"
/>
<text class="tab-text">AI伴游</text> <text class="tab-text">AI伴游</text>
<view v-if="showDot" class="status-dot"></view> <view
v-if="showDot"
:class="['status-dot', modelValue === 1 ? 'status-dot--active' : 'status-dot--inactive']"
></view>
</view> </view>
</view> </view>
</view> </view>
@@ -26,6 +41,12 @@
</template> </template>
<script setup> <script setup>
import leftSelected from "./images/L_02.png";
import leftUnselected from "./images/L_01.png";
import rightSelected from "./images/R_02.png";
import rightUnselected from "./images/R_01.png";
const props = defineProps({ const props = defineProps({
modelValue: { type: Number, default: 0 }, modelValue: { type: Number, default: 0 },
showDot: { type: Boolean, default: true }, showDot: { type: Boolean, default: true },
@@ -47,23 +68,21 @@ const handleSwitch = (i) => {
.tab-container { .tab-container {
position: relative; position: relative;
width: 100%; width: 100%;
height: 48px; height: 50px;
display: flex; display: flex;
overflow: hidden; overflow: hidden;
/* 基础容器不做裁切,交给内部 Item */
} }
.tab-item { .tab-item {
position: relative; position: relative;
flex: 1; flex: 1;
height: 48px; height: 50px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
/* 选中态提升层级,压住对方的交汇线 */
.tab-item.active { .tab-item.active {
z-index: 10; z-index: 10;
} }
@@ -74,91 +93,57 @@ const handleSwitch = (i) => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
/* 未选中样式:半透明渐变 */
background: rgba(0, 0, 0, 0.05);
transition: background 0.3s; transition: background 0.3s;
} overflow: hidden;
.tab-item.active .tab-content {
/* 选中样式:纯白 */
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0) 0%,
#f0f8f3 75.52%
);
} }
.tab-text { .tab-text {
font-size: 15px; font-size: 18px;
color: rgba(255, 255, 255, 0.95); font-weight: 500;
color: rgba(255, 255, 255, 0.65);
z-index: 20; z-index: 20;
} }
.tab-image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
object-fit: cover;
z-index: 5;
}
.tab-item.active .tab-text { .tab-item.active .tab-text {
color: #2e312f; color: #2e312f;
font-weight: bold; font-weight: bold;
} }
/* ------------------- 核心:异形路径优化 ------------------- */
/* 路径逻辑:
1. H 指令缩短,让出更多空间给交汇处。
2. C 指令(贝塞尔曲线)的终点设在高度 48 的位置,确保底部平齐不留空隙。
3. 通过 margin-right/left 让两个形状在中心高度24px处重叠。
*/
.is-left { .is-left {
margin-right: -14px; margin-right: -24px;
/* 减小重叠宽度,使形状更精巧 */
}
.is-left .tab-content {
/* 路径:左侧顶满 -> 顶部直线 -> 在中间开始向下弯曲 -> 底部平齐拉回 */
-webkit-mask-image: url("./images/L_01.png");
mask-image: url("./images/L_01.png");
-webkit-mask-size: 100% 100%;
} }
.is-right { .is-right {
margin-left: -14px; margin-left: -24px;
}
.is-right .tab-content {
-webkit-mask-image: url("./images/R_01.png");
mask-image: url("./images/R_01.png");
-webkit-mask-size: 100% 100%;
}
/* ------------------- 选中态的渐变边框 ------------------- */
.tab-item.active::after {
content: "";
position: absolute;
inset: 0;
z-index: 15;
pointer-events: none;
/* 360deg 渐变 */
background: linear-gradient(
0deg,
rgba(255, 255, 255, 1) 0%,
rgba(255, 255, 255, 0) 100%
);
}
.is-left.active::after {
-webkit-mask-image: url("./images/L_02.png");
}
.is-right.active::after {
-webkit-mask-image: url("./images/R_02.png");
} }
.status-dot { .status-dot {
width: 7px; display: inline-block;
height: 7px; margin-left: 6px;
background-color: #26d46c; width: 6px;
height: 6px;
border-radius: 50%; border-radius: 50%;
margin-left: 5px; z-index: 22;
transform: translateY(-8px); transform: translateY(-1px);
} }
.status-dot--active {
background-color: #26d46c;
}
.status-dot--inactive {
background-color: rgba(255, 255, 255, 0.65);
border: 1px solid rgba(0, 0, 0, 0.08);
}
</style> </style>

View File

@@ -6,6 +6,7 @@
.module-title { .module-title {
font-size: 18px; font-size: 18px;
font-weight: bold;
color: #171717; color: #171717;
position: relative; position: relative;
z-index: 1; z-index: 1;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,53 @@
<template>
<view class="find-tabs-wrapper">
<scroll-view class="tabs-scroll" scroll-x="true" :scroll-into-view="'tab-' + modelValue" scroll-with-animation="true">
<view class="tabs-list">
<view
v-for="(tab, idx) in tabs"
:key="idx"
:id="'tab-' + idx"
class="tab-item"
:class="{ active: modelValue === idx }"
@tap="handleSwitch(idx)"
>
<view class="tab-content">
<view class="tab-label">
<text class="tab-text">{{ tab.label }}</text>
<image v-if="modelValue === idx && indicatorSrc" :src="indicatorSrc" class="tab-indicator" mode="widthFix" />
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import indicatorSrc from "./images/selected_tabs_icon.png";
const props = defineProps({
modelValue: { type: Number, default: 0 },
tabs: {
type: Array,
default: () => [
{ label: '小七孔古桥' },
{ label: '翠谷瀑布' },
{ label: '鸳鸯湖' },
{ label: '天河潭' },
{ label: '卧龙潭' }
],
},
});
const emit = defineEmits(['update:modelValue', 'change']);
const handleSwitch = (i) => {
emit('update:modelValue', i);
emit('change', i);
};
</script>
<style lang="scss" scoped>
@import "./styles/index.scss";
</style>

View File

@@ -0,0 +1,70 @@
.find-tabs-wrapper {
width: 100%;
background-color: transparent;
}
.tabs-scroll {
width: 100%;
}
.tabs-list {
display: flex;
align-items: flex-end;
height: 50px;
gap: 16px;
flex-wrap: nowrap;
padding: 0 12px;
}
.tab-item {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
height: 50px;
box-sizing: border-box;
flex: 0 0 auto;
}
.tab-item:last-child {
margin-right: 12px;
}
.tab-content {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 50px;
}
.tab-label {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
}
.tab-text {
font-size: 16px;
font-weight: 500;
color: rgba(128, 140, 153, 0.9);
z-index: 5;
padding: 0 4px;
line-height: 1;
}
.tab-item.active .tab-text {
color: #0b0b0b;
font-weight: bold;
}
.tab-indicator {
position: absolute;
bottom: -6px;
left: 50%;
transform: translateX(-50%);
width: 56px;
height: auto;
z-index: 6;
}

View File

@@ -0,0 +1,78 @@
<template>
<view class="container pl-12">
<ModuleTitle :title="themeName" />
<view class="container-scroll font-size-0 scroll-x whitespace-nowrap">
<view class="card-item bg-white inline-block rounded-20 p-10 mr-10"
v-for="(item, index) in recommendPostsList" :key="index" @click="sendReply(item)">
<view class="flex flex-row items-center">
<image class="card-img rounded-14" :src="item.coverPhoto" mode="aspectFill" />
<view class="ml-8 mt-4 border-box">
<view class="font-size-12 font-600 color-171717 ellipsis-1">
{{ item.title }}
</view>
<view class="font-size-9 color-94A3B8 ellipsis-1">
{{ item.subTitle }}
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { defineProps } from "vue";
import { SEND_MESSAGE_CONTENT_TEXT, SEND_MESSAGE_COMMAND_TYPE } from "@/constant/constant";
import ModuleTitle from "@/components/ModuleTitle/index.vue";
const props = defineProps({
themeName: {
type: String,
default: "快捷提问",
},
recommendPostsList: {
type: Array,
default: () => [
{
title: "小七孔古桥",
subTitle: "小七孔古桥",
coverPhoto: "https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=800&q=80"
},
{
title: "翠谷瀑布",
subTitle: "翠谷瀑布",
coverPhoto: "https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=800&q=80"
},
{
title: "鸳鸯湖",
subTitle: "鸳鸯湖",
coverPhoto: "https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=800&q=80",
},
{
title: "卧龙潭",
subTitle: "卧龙潭",
coverPhoto: "https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=800&q=80",
}
],
},
});
const sendReply = (item) => {
if (item.userInputContentType && item.userInputContentType === '1') {
const commonItem = { type: item.userInputContent, title: item.topic }
uni.$emit(SEND_MESSAGE_COMMAND_TYPE, commonItem);
return;
}
uni.$emit(SEND_MESSAGE_CONTENT_TEXT, item.userInputContent);
};
</script>
<style lang="scss" scoped>
@import "./styles/index.scss";
</style>

View File

@@ -0,0 +1,12 @@
.container-scroll {
margin: 4px 0 6px;
}
.card-item {
width: 130px;
}
.card-img {
height: 40px;
width: 40px;
}

View File

@@ -1,14 +1,22 @@
<template> <template>
<AiTabSwitch v-model="tabIndex" :list="['探索发现', 'AI伴游']" @change="handleChange" /> <view>
<FindTabs v-model="activeIndex" @change="handleTabChange" />
<QuickQuestions />
</view>
</template> </template>
<script setup> <script setup>
import { ref } from "vue"; import { ref } from "vue";
import AiTabSwitch from "@/components/AiTabSwitch/index.vue"; import FindTabs from "./components/FindTabs/index.vue";
import QuickQuestions from "./components/QuickQuestions/index.vue";
const tabIndex = ref(0); const activeIndex = ref(0);
const handleChange = (i) => { const handleTabChange = (index) => {
console.log("切换:", i); activeIndex.value = index;
}; };
</script> </script>

View File

@@ -27,25 +27,9 @@
@change="handleChange" @change="handleChange"
/> />
<view> <view class="tab-content">
<view <Discovery v-if="tabIndex === 0" />
class="absolute top-0 left-0 w-full h-full flex flex-col items-center justify-center" <ChatMainList v-if="tabIndex === 1" />
>
<text class="font-size-24 font-bold color-white mb-4"
>欢迎来到AI助手</text
>
<text class="font-size-16 color-white mb-8"
>您的智能聊天伴侣随时为您提供帮助</text
>
<view class="flex space-x-4">
<view class="px-6 py-2 bg-green-500 text-white rounded-lg"
>开始聊天</view
>
<view class="px-6 py-2 bg-gray-300 text-gray-700 rounded-lg"
>了解更多</view
>
</view>
</view>
</view> </view>
</view> </view>
</view> </view>
@@ -57,8 +41,9 @@ import { onLoad } from "@dcloudio/uni-app";
import HomeNavBar from "./components/HomeNavBar.vue"; import HomeNavBar from "./components/HomeNavBar.vue";
import HomeWelcome from "./components/HomeWelcome.vue"; import HomeWelcome from "./components/HomeWelcome.vue";
import AiTabSwitch from "@/components/AiTabSwitch/index.vue"; import AiTabSwitch from "@/components/AiTabSwitch/index.vue";
import ChatMainList from "../ChatMain/ChatMainList/index.vue";
import Discovery from "../Discovery/index.vue";
const tabIndex = ref(0); const tabIndex = ref(0);
@@ -80,7 +65,5 @@ onLoad(() => {
</script> </script>
<style scoped> <style scoped>
.bg-color {
background: red;
}
</style> </style>

View File

@@ -40,6 +40,10 @@
color: #ff3d60; color: #ff3d60;
} }
.color-94A3B8 {
color: #94a3b8;
}
.theme-color-500 { .theme-color-500 {
color: $theme-color-500; color: $theme-color-500;
} }

View File

@@ -23,6 +23,10 @@
border-radius: 12px; border-radius: 12px;
} }
.rounded-14 {
border-radius: 14px;
}
.rounded-16 { .rounded-16 {
border-radius: 16px; border-radius: 16px;
} }