tabbar样式改进

main
heiferleaf 2 months ago
parent 700e5370b7
commit 1e0cb425f1

@ -5,7 +5,7 @@ spring:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/wx_miniapp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
password: 1234
mybatis:
mapper-locations: classpath:mapper/*.xml

@ -5,7 +5,7 @@ spring:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/wx_miniapp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
password: 1234
mybatis:
mapper-locations: classpath:mapper/*.xml

@ -1,22 +1,22 @@
-- 创建数据库
CREATE DATABASE IF NOT EXISTS wx_miniapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
-- 使用数据库
USE wx_miniapp;
-- 创建微信用户表
CREATE TABLE IF NOT EXISTS `wx_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`openid` varchar(100) NOT NULL COMMENT '微信openid',
`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
`avatar_url` varchar(500) DEFAULT NULL COMMENT '头像URL',
`gender` tinyint(4) DEFAULT NULL COMMENT '性别 0-未知 1-男 2-女',
`country` varchar(50) DEFAULT NULL COMMENT '国家',
`province` varchar(50) DEFAULT NULL COMMENT '省份',
`city` varchar(50) DEFAULT NULL COMMENT '城市',
`language` varchar(50) DEFAULT NULL COMMENT '语言',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_openid` (`openid`) COMMENT 'openid唯一索引'
-- 创建数据库
CREATE DATABASE IF NOT EXISTS wx_miniapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
-- 使用数据库
USE wx_miniapp;
-- 创建微信用户表
CREATE TABLE IF NOT EXISTS `wx_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`openid` varchar(100) NOT NULL COMMENT '微信openid',
`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
`avatar_url` varchar(500) DEFAULT NULL COMMENT '头像URL',
`gender` tinyint(4) DEFAULT NULL COMMENT '性别 0-未知 1-男 2-女',
`country` varchar(50) DEFAULT NULL COMMENT '国家',
`province` varchar(50) DEFAULT NULL COMMENT '省份',
`city` varchar(50) DEFAULT NULL COMMENT '城市',
`language` varchar(50) DEFAULT NULL COMMENT '语言',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_openid` (`openid`) COMMENT 'openid唯一索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信用户表';

@ -1,58 +1,58 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.learning.newdemo.mapper.WxUserMapper">
<resultMap id="BaseResultMap" type="com.learning.newdemo.entity.WxUser">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="openid" property="openid" jdbcType="VARCHAR"/>
<result column="nickname" property="nickname" jdbcType="VARCHAR"/>
<result column="avatar_url" property="avatarUrl" jdbcType="VARCHAR"/>
<result column="gender" property="gender" jdbcType="INTEGER"/>
<result column="country" property="country" jdbcType="VARCHAR"/>
<result column="province" property="province" jdbcType="VARCHAR"/>
<result column="city" property="city" jdbcType="VARCHAR"/>
<result column="language" property="language" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id, openid, nickname, avatar_url, gender, country, province, city, language, create_time, update_time
</sql>
<select id="selectByOpenid" resultMap="BaseResultMap" parameterType="java.lang.String">
select
<include refid="Base_Column_List"/>
from wx_user
where openid = #{openid,jdbcType=VARCHAR}
</select>
<insert id="insert" parameterType="com.learning.newdemo.entity.WxUser" useGeneratedKeys="true" keyProperty="id">
insert into wx_user (
openid, nickname, avatar_url, gender, country, province, city, language, create_time
)
values (
#{openid,jdbcType=VARCHAR},
#{nickname,jdbcType=VARCHAR},
#{avatarUrl,jdbcType=VARCHAR},
#{gender,jdbcType=INTEGER},
#{country,jdbcType=VARCHAR},
#{province,jdbcType=VARCHAR},
#{city,jdbcType=VARCHAR},
#{language,jdbcType=VARCHAR},
now()
)
</insert>
<update id="updateByPrimaryKey" parameterType="com.learning.newdemo.entity.WxUser">
update wx_user
set nickname = #{nickname,jdbcType=VARCHAR},
avatar_url = #{avatarUrl,jdbcType=VARCHAR},
gender = #{gender,jdbcType=INTEGER},
country = #{country,jdbcType=VARCHAR},
province = #{province,jdbcType=VARCHAR},
city = #{city,jdbcType=VARCHAR},
language = #{language,jdbcType=VARCHAR},
update_time = now()
where id = #{id,jdbcType=INTEGER}
</update>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.learning.newdemo.mapper.WxUserMapper">
<resultMap id="BaseResultMap" type="com.learning.newdemo.entity.WxUser">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="openid" property="openid" jdbcType="VARCHAR"/>
<result column="nickname" property="nickname" jdbcType="VARCHAR"/>
<result column="avatar_url" property="avatarUrl" jdbcType="VARCHAR"/>
<result column="gender" property="gender" jdbcType="INTEGER"/>
<result column="country" property="country" jdbcType="VARCHAR"/>
<result column="province" property="province" jdbcType="VARCHAR"/>
<result column="city" property="city" jdbcType="VARCHAR"/>
<result column="language" property="language" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id, openid, nickname, avatar_url, gender, country, province, city, language, create_time, update_time
</sql>
<select id="selectByOpenid" resultMap="BaseResultMap" parameterType="java.lang.String">
select
<include refid="Base_Column_List"/>
from wx_user
where openid = #{openid,jdbcType=VARCHAR}
</select>
<insert id="insert" parameterType="com.learning.newdemo.entity.WxUser" useGeneratedKeys="true" keyProperty="id">
insert into wx_user (
openid, nickname, avatar_url, gender, country, province, city, language, create_time
)
values (
#{openid,jdbcType=VARCHAR},
#{nickname,jdbcType=VARCHAR},
#{avatarUrl,jdbcType=VARCHAR},
#{gender,jdbcType=INTEGER},
#{country,jdbcType=VARCHAR},
#{province,jdbcType=VARCHAR},
#{city,jdbcType=VARCHAR},
#{language,jdbcType=VARCHAR},
now()
)
</insert>
<update id="updateByPrimaryKey" parameterType="com.learning.newdemo.entity.WxUser">
update wx_user
set nickname = #{nickname,jdbcType=VARCHAR},
avatar_url = #{avatarUrl,jdbcType=VARCHAR},
gender = #{gender,jdbcType=INTEGER},
country = #{country,jdbcType=VARCHAR},
province = #{province,jdbcType=VARCHAR},
city = #{city,jdbcType=VARCHAR},
language = #{language,jdbcType=VARCHAR},
update_time = now()
where id = #{id,jdbcType=INTEGER}
</update>
</mapper>

@ -1,8 +1,29 @@
<template>
<view class="argument-component">
<view class="content-card">
<view class="card-title">立论功能</view>
<view class="card-content">在这里可以进行辩题立论和论点整理</view>
<!-- 动态背景元素 -->
<view class="animated-bg">
<view v-for="i in 6" :key="i" class="bg-circle" :style="getRandomCircleStyle()"></view>
</view>
<!-- 顶部卡片 - 可收缩 -->
<view class="content-card" :class="{ 'collapsed': isCardCollapsed }">
<view class="card-header" @click="toggleCard">
<view class="card-icon-wrapper">
<view class="card-icon">
<image src="/static/icons/brain-line.png" mode="aspectFit"></image>
</view>
</view>
<view class="card-text">
<view class="card-title">立论助手</view>
<view class="card-content" v-if="!isCardCollapsed">AI</view>
</view>
<view class="collapse-icon">
<image
:src="isCardCollapsed ? '/static/icons/arrow-down-s-line.png' : '/static/icons/arrow-up-s-line.png'"
mode="aspectFit"
></image>
</view>
</view>
</view>
<!-- 聊天展示区 -->
@ -19,36 +40,49 @@
class="chat-message"
:class="msg.role === 'user' ? 'from-user' : 'from-ai'"
>
<view class="avatar" v-if="msg.role === 'ai'">AI</view>
<view class="bubble">
<block v-if="msg.loading">
<view class="dot-flashing"></view>
<view class="loading-animation">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></view>
</view>
</block>
<block v-else>
{{ msg.content }}
</block>
</view>
<view class="avatar user-avatar" v-if="msg.role === 'user'"></view>
</view>
</scroll-view>
<!-- 输入框与发送按钮 -->
<view class="chat-input">
<view class="input-group">
<input
class="input-box"
type="text"
v-model="position"
placeholder="请输入论方,例如:正方"
/>
<textarea
class="textarea-box"
v-model="question"
placeholder="请输入辩题,例如:应不应该取消作业"
auto-height
/>
</view>
<button class="send-button" @click="sendMessage()"></button>
<view class="chat-input">
<view class="input-group">
<input
class="input-box"
type="text"
v-model="position"
placeholder="请输入论方,例如:正方"
/>
<view class="textarea-container">
<textarea
class="textarea-box"
v-model="question"
placeholder="请输入辩题,例如:应不应该取消作业"
auto-height
/>
<button class="send-button-embedded" @click="sendMessage()">
<image src="/static/icons/send-plane-fill.png" mode="aspectFit"></image>
</button>
</view>
</view>
</view>
<!-- 底部安全区域 -->
<view class="safe-area-bottom"></view>
</view>
</template>
<script>
@ -63,12 +97,35 @@ export default {
}
],
scrollToView: "",
position: "",
question: "",
isCardCollapsed: false, //
};
},
methods: {
// /
toggleCard() {
this.isCardCollapsed = !this.isCardCollapsed;
},
//
getRandomCircleStyle() {
const size = Math.random() * 300 + 100;
const x = Math.random() * 100;
const y = Math.random() * 100;
const delay = Math.random() * 5;
const duration = Math.random() * 10 + 15;
return {
width: `${size}rpx`,
height: `${size}rpx`,
left: `${x}%`,
top: `${y}%`,
animationDelay: `${delay}s`,
animationDuration: `${duration}s`
};
},
async sendMessage() {
//
this.messages.push({
@ -83,13 +140,14 @@ export default {
this.messages.push({ role: "ai", content: "", loading: true });
this.scrollToBottom();
// AI
// AI
const reply = await this.callAI(this.position, this.question);
// loading AI
this.messages.splice(aiIndex, 1, { role: "ai", content: reply });
this.scrollToBottom();
},
async callAI(topic, stance) {
this.position = "";
this.question = "";
@ -97,7 +155,7 @@ export default {
console.log("callAI:", topic, stance);
uni.request({
url: "http://localhost:8080/api/ai/argument", // URL
url: "http://localhost:8080/api/ai/argument",
method: "POST",
header: {
"Content-Type": "application/json",
@ -136,175 +194,461 @@ export default {
<style scoped>
.argument-component {
width: 100%;
padding: 20rpx;
height: 100vh;
padding: 30rpx;
display: flex;
flex-direction: column;
height: 78vh;
background: linear-gradient(135deg, #2c3e50, #4ca1af);
background: linear-gradient(135deg, #4338CA 0%, #7C3AED 100%);
overflow-x: hidden;
box-sizing: border-box;
padding-bottom: 180rpx; /* 为底部TabBar留出空间 */
position: relative;
}
overflow-x: hidden; /* 禁止横向滚动 */
box-sizing: border-box; /* 避免 padding 导致溢出 */
/* 动态背景 */
.animated-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 0;
}
.bg-circle {
position: absolute;
border-radius: 50%;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
animation: float 20s infinite ease-in-out;
opacity: 0.4;
/* 添加过渡效果 */
transition: all 1.5s ease-in-out;
}
@keyframes float {
0%, 100% {
transform: translate(0, 0) scale(1);
}
25% {
transform: translate(5%, 10%) scale(1.05);
}
50% {
transform: translate(10%, 5%) scale(0.95);
}
75% {
transform: translate(5%, 15%) scale(1.1);
}
}
/* 顶部卡片 */
.content-card {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 20rpx;
padding: 30rpx;
width: 90%;
margin: 0 auto 30rpx;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
border-radius: 28rpx;
padding: 20rpx;
width: 100%;
margin-bottom: 30rpx;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.15);
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
z-index: 1;
animation: slideDown 0.6s ease-out;
transition: all 0.3s ease;
}
.content-card.collapsed {
padding: 16rpx 20rpx;
margin-bottom: 20rpx;
}
.card-header {
display: flex;
align-items: center;
cursor: pointer;
}
.collapse-icon {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s ease;
}
.collapse-icon image {
width: 30rpx;
height: 30rpx;
filter: brightness(0) invert(1);
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-30rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card-icon-wrapper {
position: relative;
margin-right: 24rpx;
}
.card-icon {
width: 80rpx;
height: 80rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.collapsed .card-icon {
width: 70rpx;
height: 70rpx;
}
.card-icon::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
to bottom right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0) 100%
);
transform: rotate(45deg);
animation: shine 3s infinite;
}
@keyframes shine {
0% {
transform: translateX(-100%) rotate(45deg);
}
20%, 100% {
transform: translateX(100%) rotate(45deg);
}
}
.card-icon image {
width: 45rpx;
height: 45rpx;
filter: brightness(0) invert(1);
z-index: 1;
transition: all 0.3s ease;
}
.collapsed .card-icon image {
width: 40rpx;
height: 40rpx;
}
.card-text {
flex: 1;
transition: all 0.3s ease;
}
.card-title {
font-size: 36rpx;
color: #ffffff;
font-weight: bold;
margin-bottom: 20rpx;
color: #FFFFFF;
font-weight: 700;
margin-bottom: 10rpx;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
}
.collapsed .card-title {
font-size: 32rpx;
margin-bottom: 0;
}
.card-content {
font-size: 28rpx;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
line-height: 1.5;
transition: all 0.3s ease;
}
/* 聊天区域 */
.chat-area {
flex: 1;
width: 90%;
margin: 0 auto;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 20rpx;
padding: 20rpx;
width: 100%;
background: rgba(255, 255, 255, 0.1);
border-radius: 28rpx;
padding: 30rpx;
margin-bottom: 30rpx;
overflow-y: auto;
border: 1px solid rgba(255, 255, 255, 0.15);
position: relative;
z-index: 1;
transition: all 0.3s ease;
}
/* 消息通用样式 */
.chat-message {
display: flex;
margin-bottom: 20rpx;
margin-bottom: 30rpx;
align-items: flex-start;
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 用户消息靠右 */
.from-user {
justify-content: flex-end;
}
/* AI消息靠左 */
.from-ai {
justify-content: flex-start;
}
/* 头像样式 */
.avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24rpx;
font-weight: bold;
margin-right: 16rpx;
flex-shrink: 0;
}
.user-avatar {
background: #F59E0B;
margin-left: 16rpx;
margin-right: 0;
}
/* 气泡样式 */
.bubble {
max-width: 70%;
padding: 20rpx;
border-radius: 20rpx;
padding: 20rpx 24rpx;
border-radius: 24rpx;
font-size: 28rpx;
line-height: 1.6;
word-break: break-word;
box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1);
position: relative;
transition: all 0.3s ease; /* 添加过渡效果 */
}
/* 用户气泡颜色 */
.from-user .bubble {
background-color: #00c4ff;
color: white;
border-bottom-right-radius: 0;
background: #F59E0B;
color: #FFFFFF;
border-bottom-right-radius: 4rpx;
font-weight: 500;
}
.from-user .bubble::after {
content: '';
position: absolute;
right: -12rpx;
bottom: 0;
width: 0;
height: 0;
border-left: 16rpx solid #F59E0B;
border-top: 16rpx solid transparent;
transition: all 0.3s ease; /* 添加过渡效果 */
}
/* AI气泡颜色 */
.from-ai .bubble {
background-color: #ffd54f;
color: #333;
border-bottom-left-radius: 0;
background: rgba(255, 255, 255, 0.2);
color: #FFFFFF;
border-bottom-left-radius: 4rpx;
min-width: 80rpx;
}
min-width: 80px;
.from-ai .bubble::after {
content: '';
position: absolute;
left: -12rpx;
bottom: 0;
width: 0;
height: 0;
border-right: 16rpx solid rgba(255, 255, 255, 0.2);
border-top: 16rpx solid transparent;
transition: all 0.3s ease; /* 添加过渡效果 */
}
/* 加载动画 */
.loading-animation {
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
height: 30rpx;
}
.loading-animation .dot {
width: 12rpx;
height: 12rpx;
background-color: #FFFFFF;
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out both;
}
.loading-animation .dot:nth-child(1) {
animation-delay: -0.32s;
}
.loading-animation .dot:nth-child(2) {
animation-delay: -0.16s;
}
@keyframes bounce {
0%, 80%, 100% {
transform: scale(0);
}
40% {
transform: scale(1.0);
}
}
/* 输入区域 */
.chat-input {
display: flex;
padding: 20rpx;
background-color: rgba(255, 255, 255, 0.05);
border-top: 1rpx solid #ccc;
padding: 16rpx;
background: rgba(255, 255, 255, 0.15);
border-radius: 28rpx;
align-items: flex-end;
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
z-index: 1;
margin-bottom: 20rpx;
}
.send-button.left-send {
margin-right: 12rpx;
margin-left: 0;
background: #F59E0B;
width: 70rpx;
height: 70rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 15rpx rgba(245, 158, 11, 0.4);
transition: all 0.3s;
padding: 0;
}
.send-button.left-send:active {
transform: scale(0.95) rotate(-15deg);
box-shadow: 0 2rpx 8rpx rgba(245, 158, 11, 0.3);
}
.send-button.left-send image {
width: 36rpx;
height: 36rpx;
filter: brightness(0) invert(1);
}
.input-group {
flex: 1;
display: flex;
flex-direction: column;
gap: 10rpx;
gap: 16rpx;
}
.input-box {
background-color: #fff;
border-radius: 10rpx;
padding: 10rpx 20rpx;
margin-bottom: 20rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 20rpx;
padding: 16rpx 20rpx;
font-size: 28rpx;
border: none;
color: #FFFFFF;
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s;
height: 70rpx;
}
.textarea-box {
background-color: #fff;
border-radius: 10rpx;
padding: 10rpx 20rpx;
font-size: 28rpx;
min-height: 80rpx;
border: none;
.textarea-container {
position: relative;
width: 100%;
}
.send-button {
margin-left: 20rpx;
background-color: #00bcd4;
color: #fff;
border-radius: 10rpx;
padding: 20rpx;
.textarea-box {
background: rgba(255, 255, 255, 0.2);
border-radius: 20rpx;
padding: 16rpx 20rpx 16rpx 90rpx; /* 增加左侧内边距 */
font-size: 28rpx;
white-space: nowrap;
color: #FFFFFF;
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s;
min-height: 70rpx;
max-height: 180rpx;
width: 100%;
box-sizing: border-box;
}
/* AI loading 动画 */
.dot-flashing {
width: 20rpx;
height: 20rpx;
.send-button-embedded {
position: absolute;
bottom: 16rpx;
left: 16rpx;
background: transparent;
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background-color: #333;
animation: dotFlashing 1s infinite linear alternate;
position: relative;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
padding: 0;
z-index: 2;
}
margin-left: auto;
margin-right: auto;
.send-button-embedded:active {
transform: scale(1.2) rotate(-15deg);
}
.dot-flashing::before,
.dot-flashing::after {
content: "";
display: inline-block;
position: absolute;
top: 0;
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background-color: #333;
.send-button-embedded image {
width: 40rpx;
height: 40rpx;
filter: invert(72%) sepia(87%) saturate(1242%) hue-rotate(325deg) brightness(101%) contrast(96%);
}
.dot-flashing::before {
left: -30rpx;
animation: dotFlashing 1s infinite linear alternate;
animation-delay: 0.2s;
.input-box::placeholder,
.textarea-box::placeholder {
color: burlywood;
}
.dot-flashing::after {
left: 30rpx;
animation: dotFlashing 1s infinite linear alternate;
animation-delay: 0.4s;
.input-box:focus,
.textarea-box:focus {
border-color: #F59E0B;
outline: none;
background: rgba(255, 255, 255, 0.25);
}
@keyframes dotFlashing {
0% {
background-color: #333;
}
50%,
100% {
background-color: rgba(51, 51, 51, 0.3);
}
/* 底部安全区域 */
.safe-area-bottom {
height: 20rpx;
height: calc(20rpx + constant(safe-area-inset-bottom)); /* iOS 11.0-11.2 */
height: calc(20rpx + env(safe-area-inset-bottom)); /* iOS 11.2+ */
}
</style>
</style>

@ -1,8 +1,43 @@
<template>
<view class="argument-component">
<view class="content-card">
<view class="card-title">辩论功能</view>
<view class="card-content">在这里可以和AI进行激烈的交流~</view>
<view class="debate-component">
<!-- 动态背景元素 -->
<view class="animated-bg">
<view v-for="i in 6" :key="i" class="bg-circle" :style="getRandomCircleStyle()"></view>
</view>
<!-- 顶部卡片 - 可收缩 -->
<view class="content-card" :class="{ 'collapsed': isCardCollapsed }">
<view class="card-header" @click="toggleCard">
<view class="card-icon-wrapper">
<view class="card-icon">
<image src="/static/icons/robot-2-line.png" mode="aspectFit"></image>
</view>
</view>
<view class="card-text">
<view class="card-title">模拟辩论</view>
<view class="card-content" v-if="!isCardCollapsed">AI</view>
</view>
<view class="collapse-icon">
<image
:src="isCardCollapsed ? '/static/icons/arrow-down-s-line.png' : '/static/icons/arrow-up-s-line.png'"
mode="aspectFit"
></image>
</view>
</view>
</view>
<!-- 辩论主题选择器 -->
<view class="topic-selector" v-if="!isCardCollapsed">
<scroll-view scroll-x class="topic-scroll" show-scrollbar="false">
<view
v-for="(topic, idx) in debateTopics"
:key="idx"
:class="['topic-pill', { active: selectedTopic === idx }]"
@click="selectTopic(idx)"
>
{{ topic }}
</view>
</scroll-view>
</view>
<!-- 聊天展示区 -->
@ -19,30 +54,43 @@
class="chat-message"
:class="msg.role === 'user' ? 'from-user' : 'from-ai'"
>
<view class="avatar" v-if="msg.role === 'ai'">AI</view>
<view class="bubble">
<block v-if="msg.loading">
<view class="dot-flashing"></view>
<view class="loading-animation">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></view>
</view>
</block>
<block v-else>
{{ msg.content }}
</block>
</view>
<view class="avatar user-avatar" v-if="msg.role === 'user'"></view>
</view>
</scroll-view>
<!-- 输入框与发送按钮 -->
<view class="chat-input">
<view class="input-group">
<textarea
class="textarea-box"
v-model="content"
placeholder="请输入辩论的过程"
auto-height
maxlength="1000"
/>
<view class="textarea-container">
<textarea
class="textarea-box"
v-model="content"
placeholder="输入你的辩论观点与AI进行思辨交锋"
auto-height
maxlength="1000"
/>
<button class="send-button-embedded" @click="sendMessage()">
<image src="/static/icons/send-plane-fill.png" mode="aspectFit"></image>
</button>
</view>
</view>
<button class="send-button" @click="sendMessage()"></button>
</view>
<!-- 底部安全区域 -->
<view class="safe-area-bottom"></view>
</view>
</template>
@ -53,16 +101,62 @@ export default {
input: "",
messages: [
{
role: "ai",
content: "在这里你可以和我进行辩论,现在,辩论开始!"
role: "ai",
content: "在这里你可以和我进行辩论,现在,辩论开始!请选择一个话题或提出你想讨论的观点。"
}
],
scrollToView: "",
content: "",
debateTopics: [
"教育改革", "环境保护", "人工智能", "社会公平", "文化传承", "科技伦理"
],
selectedTopic: -1,
isCardCollapsed: false, //
};
},
methods: {
// /
toggleCard() {
this.isCardCollapsed = !this.isCardCollapsed;
},
//
getRandomCircleStyle() {
const size = Math.random() * 300 + 100;
const x = Math.random() * 100;
const y = Math.random() * 100;
const delay = Math.random() * 5;
const duration = Math.random() * 10 + 15;
return {
width: `${size}rpx`,
height: `${size}rpx`,
left: `${x}%`,
top: `${y}%`,
animationDelay: `${delay}s`,
animationDuration: `${duration}s`
};
},
//
selectTopic(index) {
if (this.selectedTopic === index) return;
this.selectedTopic = index;
const topic = this.debateTopics[index];
//
this.messages.push({
role: "ai",
content: `已选择「${topic}」作为辩论主题。请分享你对这个话题的观点,我们开始辩论吧!`
});
this.scrollToBottom();
},
async sendMessage() {
if (!this.content.trim()) return;
//
this.messages.push({
role: "user",
@ -97,6 +191,7 @@ export default {
this.messages.splice(aiIndex, 1, { role: "ai", content: reply });
this.scrollToBottom();
},
async callAI(history, content) {
this.content = "";
return new Promise((resolve, reject) => {
@ -141,177 +236,478 @@ export default {
</script>
<style scoped>
.argument-component {
.debate-component {
width: 100%;
padding: 20rpx;
height: 100vh;
padding: 30rpx;
display: flex;
flex-direction: column;
height: 78vh;
background: linear-gradient(135deg, #2c3e50, #c6c333);
background: linear-gradient(135deg, #4338CA 0%, #7C3AED 100%);
overflow-x: hidden;
box-sizing: border-box;
padding-bottom: 180rpx; /* 为底部TabBar留出空间 */
position: relative;
}
overflow-x: hidden; /* 禁止横向滚动 */
box-sizing: border-box; /* 避免 padding 导致溢出 */
/* 动态背景 */
.animated-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 0;
}
.bg-circle {
position: absolute;
border-radius: 50%;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
animation: float 20s infinite ease-in-out;
opacity: 0.4;
/* 添加过渡效果 */
transition: all 1.5s ease-in-out;
}
@keyframes float {
0%, 100% {
transform: translate(0, 0) scale(1);
}
25% {
transform: translate(5%, 10%) scale(1.05);
}
50% {
transform: translate(10%, 5%) scale(0.95);
}
75% {
transform: translate(5%, 15%) scale(1.1);
}
}
/* 顶部卡片 */
.content-card {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 20rpx;
padding: 30rpx;
width: 90%;
margin: 0 auto 30rpx;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
border-radius: 28rpx;
padding: 20rpx;
width: 100%;
margin-bottom: 30rpx;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.15);
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
z-index: 1;
animation: slideDown 0.6s ease-out;
transition: all 0.3s ease;
}
.content-card.collapsed {
padding: 16rpx 20rpx;
margin-bottom: 20rpx;
}
.card-header {
display: flex;
align-items: center;
cursor: pointer;
}
.collapse-icon {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s ease;
}
.collapse-icon image {
width: 30rpx;
height: 30rpx;
filter: brightness(0) invert(1);
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-30rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card-icon-wrapper {
position: relative;
margin-right: 24rpx;
}
.card-icon {
width: 80rpx;
height: 80rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.collapsed .card-icon {
width: 70rpx;
height: 70rpx;
}
.card-icon::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
to bottom right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0) 100%
);
transform: rotate(45deg);
animation: shine 3s infinite;
}
@keyframes shine {
0% {
transform: translateX(-100%) rotate(45deg);
}
20%, 100% {
transform: translateX(100%) rotate(45deg);
}
}
.card-icon image {
width: 45rpx;
height: 45rpx;
filter: brightness(0) invert(1);
z-index: 1;
transition: all 0.3s ease;
}
.collapsed .card-icon image {
width: 40rpx;
height: 40rpx;
}
.card-text {
flex: 1;
transition: all 0.3s ease;
}
.card-title {
font-size: 36rpx;
color: #ffffff;
font-weight: bold;
margin-bottom: 20rpx;
color: #FFFFFF;
font-weight: 700;
margin-bottom: 10rpx;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
}
.collapsed .card-title {
font-size: 32rpx;
margin-bottom: 0;
}
.card-content {
font-size: 28rpx;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
line-height: 1.5;
transition: all 0.3s ease;
}
/* 主题选择器 */
.topic-selector {
margin-bottom: 30rpx;
position: relative;
z-index: 1;
transition: all 0.3s ease;
}
.topic-scroll {
white-space: nowrap;
padding: 10rpx 0;
}
.topic-pill {
display: inline-block;
padding: 16rpx 30rpx;
background: rgba(255, 255, 255, 0.15);
border-radius: 50rpx;
margin-right: 20rpx;
color: rgba(255, 255, 255, 0.8);
font-size: 26rpx;
transition: all 0.3s;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.topic-pill.active {
background: #F59E0B;
color: #FFFFFF;
box-shadow: 0 4rpx 15rpx rgba(245, 158, 11, 0.4);
transform: scale(1.05);
border-color: #F59E0B;
}
/* 聊天区域 */
.chat-area {
flex: 1;
width: 90%;
margin: 0 auto;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 20rpx;
padding: 20rpx;
width: 100%;
background: rgba(255, 255, 255, 0.1);
border-radius: 28rpx;
padding: 30rpx;
margin-bottom: 30rpx;
overflow-y: auto;
border: 1px solid rgba(255, 255, 255, 0.15);
position: relative;
z-index: 1;
transition: all 0.3s ease;
}
/* 消息通用样式 */
.chat-message {
display: flex;
margin-bottom: 20rpx;
margin-bottom: 30rpx;
align-items: flex-start;
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 用户消息靠右 */
.from-user {
justify-content: flex-end;
}
/* AI消息靠左 */
.from-ai {
justify-content: flex-start;
}
/* 头像样式 */
.avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24rpx;
font-weight: bold;
margin-right: 16rpx;
flex-shrink: 0;
}
.user-avatar {
background: #F59E0B;
margin-left: 16rpx;
margin-right: 0;
}
/* 气泡样式 */
.bubble {
max-width: 70%;
padding: 20rpx;
border-radius: 20rpx;
padding: 20rpx 24rpx;
border-radius: 24rpx;
font-size: 28rpx;
line-height: 1.6;
word-break: break-word;
box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1);
position: relative;
transition: all 0.3s ease; /* 添加过渡效果 */
}
/* 用户气泡颜色 */
.from-user .bubble {
background-color: #00c4ff;
color: white;
border-bottom-right-radius: 0;
background: #F59E0B;
color: #FFFFFF;
border-bottom-right-radius: 4rpx;
font-weight: 500;
}
.from-user .bubble::after {
content: '';
position: absolute;
right: -12rpx;
bottom: 0;
width: 0;
height: 0;
border-left: 16rpx solid #F59E0B;
border-top: 16rpx solid transparent;
transition: all 0.3s ease; /* 添加过渡效果 */
}
/* AI气泡颜色 */
.from-ai .bubble {
background-color: #ffd54f;
color: #333;
border-bottom-left-radius: 0;
background: rgba(255, 255, 255, 0.2);
color: #FFFFFF;
border-bottom-left-radius: 4rpx;
min-width: 80rpx;
}
min-width: 80px;
.from-ai .bubble::after {
content: '';
position: absolute;
left: -12rpx;
bottom: 0;
width: 0;
height: 0;
border-right: 16rpx solid rgba(255, 255, 255, 0.2);
border-top: 16rpx solid transparent;
transition: all 0.3s ease; /* 添加过渡效果 */
}
/* 输入区域 */
.chat-input {
/* 加载动画 */
.loading-animation {
display: flex;
padding: 20rpx;
background-color: rgba(255, 255, 255, 0.05);
border-top: 1rpx solid #ccc;
align-items: flex-end;
align-items: center;
justify-content: center;
gap: 8rpx;
height: 30rpx;
}
.input-group {
flex: 1;
display: flex;
flex-direction: column;
gap: 10rpx;
.loading-animation .dot {
width: 12rpx;
height: 12rpx;
background-color: #FFFFFF;
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out both;
}
.loading-animation .dot:nth-child(1) {
animation-delay: -0.32s;
}
.input-box {
background-color: #fff;
border-radius: 10rpx;
padding: 10rpx 20rpx;
.loading-animation .dot:nth-child(2) {
animation-delay: -0.16s;
}
@keyframes bounce {
0%, 80%, 100% {
transform: scale(0);
}
40% {
transform: scale(1.0);
}
}
/* 输入区域 */
.chat-input {
display: flex;
padding: 16rpx;
background: rgba(255, 255, 255, 0.15);
border-radius: 28rpx;
align-items: flex-end;
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
z-index: 1;
margin-bottom: 20rpx;
font-size: 28rpx;
border: none;
}
.textarea-box {
background-color: #fff;
border-radius: 10rpx;
padding: 10rpx 20rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 20rpx;
padding: 16rpx 20rpx 16rpx 90rpx; /* 增加左侧内边距为发送按钮留出空间 */
font-size: 28rpx;
min-height: 80rpx;
border: none;
color: #FFFFFF;
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s;
min-height: 70rpx;
max-height: 180rpx; /* 限制最大高度 */
width: 100%;
box-sizing: border-box;
}
.send-button {
margin-left: 20rpx;
background-color: #00bcd4;
color: #fff;
border-radius: 10rpx;
padding: 20rpx;
font-size: 28rpx;
white-space: nowrap;
.textarea-box::placeholder {
color: hsla(0, 6%, 15%, 0.877);
}
/* AI loading 动画 */
.dot-flashing {
width: 20rpx;
height: 20rpx;
.textarea-box:focus {
border-color: #F59E0B;
outline: none;
background: rgba(255, 255, 255, 0.25);
}
.send-button.left-send {
margin-right: 12rpx;
margin-left: 0;
background: #F59E0B;
width: 70rpx;
height: 70rpx;
border-radius: 50%;
background-color: #333;
animation: dotFlashing 1s infinite linear alternate;
position: relative;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 15rpx rgba(245, 158, 11, 0.4);
transition: all 0.3s;
padding: 0;
}
margin-left: auto;
margin-right: auto;
.send-button.left-send:active {
transform: scale(0.95) rotate(-15deg);
box-shadow: 0 2rpx 8rpx rgba(245, 158, 11, 0.3);
}
.dot-flashing::before,
.dot-flashing::after {
content: "";
display: inline-block;
.input-group {
flex: 1;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.textarea-container {
position: relative;
width: 100%;
}
.send-button-embedded {
position: absolute;
top: 0;
width: 20rpx;
height: 20rpx;
bottom: 16rpx;
left: 16rpx;
background: transparent;
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background-color: #333;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
padding: 0;
z-index: 2;
}
.dot-flashing::before {
left: -30rpx;
animation: dotFlashing 1s infinite linear alternate;
animation-delay: 0.2s;
.send-button-embedded:active {
transform: scale(1.2) rotate(-15deg);
}
.dot-flashing::after {
left: 30rpx;
animation: dotFlashing 1s infinite linear alternate;
animation-delay: 0.4s;
.send-button-embedded image {
width: 40rpx;
height: 40rpx;
filter: invert(72%) sepia(87%) saturate(1242%) hue-rotate(325deg) brightness(101%) contrast(96%);
}
@keyframes dotFlashing {
0% {
background-color: #333;
}
50%,
100% {
background-color: rgba(51, 51, 51, 0.3);
}
/* 底部安全区域 */
.safe-area-bottom {
height: 20rpx;
height: calc(20rpx + constant(safe-area-inset-bottom)); /* iOS 11.0-11.2 */
height: calc(20rpx + env(safe-area-inset-bottom)); /* iOS 11.2+ */
}
</style>
</style>

@ -1,387 +1,471 @@
<template>
<view class="home-component">
<!-- 添加云的背景图像 -->
<image class="cloud-image" src="/static/cloud.png" mode="aspectFit"></image>
<!-- 欢迎标题保留 -->
<view class="welcome-section">
<view class="welcome-title">欢迎使用智辩云枢</view>
<view class="welcome-subtitle">AI驱动的辩论助手平台</view>
</view>
<!-- 三个功能卡片 - 错落布局 -->
<view class="feature-cards">
<!-- 第一张卡片 (奇数 - 靠左) -->
<view class="feature-card card-odd" @click="navigateToFeature(0)">
<view class="card-left">
<view class="card-icon">🧠</view>
</view>
<view class="card-right">
<view class="card-title">立论助手</view>
<view class="card-description">AI辅助构建辩论框架智能生成论点与论据</view>
</view>
</view>
<!-- 第二张卡片 (偶数 - 靠右) -->
<view class="feature-card card-even" @click="navigateToFeature(1)">
<view class="card-left">
<view class="card-title">复盘分析</view>
<view class="card-description">智能分析辩论过程提供专业化改进建议</view>
</view>
<view class="card-right">
<view class="card-icon">📊</view>
</view>
</view>
<!-- 第三张卡片 (奇数 - 靠左) -->
<view class="feature-card card-odd" @click="navigateToFeature(2)">
<view class="card-left">
<view class="card-icon">🤖</view>
</view>
<view class="card-right">
<view class="card-title">模拟辩论</view>
<view class="card-description">与AI进行实时辩论对练提升应变能力</view>
</view>
</view>
</view>
<!-- 底部名句展示 -->
<view class="quote-container">
<view class="quote-icon"></view>
<view class="quote-content">
<text class="quote-text">{{ currentQuote.text }}</text>
<text class="quote-author"> {{ currentQuote.author }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const emit = defineEmits(['change-tab', 'change-index']);
const tabList = [
{
pageName:'立论',
component:'PageOfArgument'
},
{
pageName:'复盘',
component:'PageOfReview'
},
{
pageName:'辩论',
component:'PageOfDebate'
}
];
const changeTab = (index) => {
emit('change-tab', tabList[index].component);
}
const changeIndex = (index) => {
emit('change-index', index);
}
//
const navigateToFeature = (index) => {
changeTab(index);
changeIndex(index + 1);
};
//
const debateQuotes = [
{
text: "辩论的艺术不在于赢得争论,而在于不失去朋友",
author: "爱因斯坦"
},
{
text: "真理越辩越明,正义自在人心",
author: "中国古语"
},
{
text: "不要用事实来干扰我的论点",
author: "辩论格言"
},
{
text: "辩论是发现真理的艺术,而不是为了胜利",
author: "苏格拉底"
},
{
text: "思辨是灵魂的对话",
author: "柏拉图"
},
{
text: "辩论不是为了证明谁对谁错,而是为了共同接近真理",
author: "亚里士多德"
},
{
text: "最好的论点不是说服对手,而是让对手思考",
author: "辩论艺术"
},
{
text: "辩论的目的不是为了赢,而是为了真理",
author: "苏格拉底"
}
];
//
const currentQuote = ref(debateQuotes[0]);
let quoteInterval = null;
//
const changeQuote = () => {
const randomIndex = Math.floor(Math.random() * debateQuotes.length);
currentQuote.value = debateQuotes[randomIndex];
};
onMounted(() => {
//
changeQuote();
// 15
quoteInterval = setInterval(changeQuote, 15000);
});
onUnmounted(() => {
//
if (quoteInterval) {
clearInterval(quoteInterval);
}
});
</script>
<style scoped>
.home-component {
width: 100%;
padding: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
z-index: 5;
border-radius: 20rpx;
box-sizing: border-box;
max-width: 100vw;
overflow-x: hidden;
}
/* 云图像样式 */
.cloud-image {
position: absolute;
top: 20rpx;
right: 40rpx;
width: 180rpx;
height: 120rpx;
opacity: 0.6;
z-index: 1;
animation: float 6s ease-in-out infinite;
}
@keyframes float {
0% {
transform: translateY(0px) translateX(0px);
}
50% {
transform: translateY(-15px) translateX(10px);
}
100% {
transform: translateY(0px) translateX(0px);
}
}
.welcome-section {
width: 90%;
padding: 40rpx 0;
text-align: center;
margin-bottom: 40rpx;
position: relative;
z-index: 2;
}
.welcome-title {
font-size: 42rpx;
color: #ffffff;
font-weight: bold;
margin-bottom: 10rpx;
text-shadow: 0 0 10rpx rgba(0, 214, 185, 0.3);
}
.welcome-subtitle {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
}
.feature-cards {
width: 100%;
display: flex;
flex-direction: column;
gap: 40rpx;
margin-bottom: 50rpx;
padding: 0 20rpx;
box-sizing: border-box;
position: relative;
z-index: 3;
}
.feature-card {
width: 90%;
display: flex;
background-color: rgba(255, 255, 255, 0.08);
border-radius: 20rpx;
padding: 30rpx 20rpx;
transition: all 0.3s ease;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 214, 185, 0.2);
min-height: 160rpx;
box-sizing: border-box;
}
/* 悬停/触摸效果 */
.card-hover {
transform: translateY(-5rpx) scale(1.02);
background-color: rgba(0, 214, 185, 0.15);
box-shadow: 0 12rpx 25rpx rgba(0, 0, 0, 0.2), 0 0 20rpx rgba(0, 214, 185, 0.3);
border-color: rgba(0, 214, 185, 0.5);
}
/* 奇数卡片靠左 */
.card-odd {
align-self: flex-start;
margin-left: 0;
border-top-left-radius: 0;
border-left: 3px solid #00D6B9;
}
/* 偶数卡片靠右 */
.card-even {
align-self: flex-end;
margin-right: 0;
border-top-right-radius: 0;
border-right: 3px solid #00D6B9;
}
.feature-card:active {
transform: scale(0.98);
background-color: rgba(0, 214, 185, 0.2);
}
.card-left, .card-right {
display: flex;
flex-direction: column;
justify-content: center;
}
.card-left {
width: 30%;
align-items: center;
}
.card-right {
width: 70%;
padding-left: 20rpx;
}
/* 在偶数卡片中,反转左右区域的宽度 */
.card-even .card-left {
width: 70%;
align-items: flex-start;
padding-right: 20rpx;
}
.card-even .card-right {
width: 30%;
align-items: center;
padding-left: 0;
}
.card-icon {
font-size: 70rpx;
background: rgba(0, 214, 185, 0.1);
width: 100rpx;
height: 100rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 10rpx;
box-shadow: 0 0 15rpx rgba(0, 214, 185, 0.3);
transition: all 0.3s ease;
}
.card-hover .card-icon {
background: rgba(0, 214, 185, 0.25);
box-shadow: 0 0 20rpx rgba(0, 214, 185, 0.5);
transform: scale(1.1);
}
.card-title {
font-size: 32rpx;
color: #ffffff;
font-weight: bold;
margin-bottom: 15rpx;
}
.card-description {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
line-height: 1.5;
}
/* 名句容器样式 */
.quote-container {
width: 90%;
display: flex;
padding: 30rpx;
background-color: rgba(0, 214, 185, 0.1);
border-radius: 20rpx;
margin-top: 20rpx;
position: relative;
z-index: 2;
box-shadow: 0 5rpx 15rpx rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 214, 185, 0.2);
transition: all 0.5s ease;
overflow: hidden;
}
.quote-icon {
font-size: 60rpx;
color: rgba(0, 214, 185, 0.5);
margin-right: 15rpx;
align-self: flex-start;
line-height: 1;
}
.quote-content {
display: flex;
flex-direction: column;
flex: 1;
}
.quote-text {
font-size: 28rpx;
color: #ffffff;
line-height: 1.5;
font-style: italic;
margin-bottom: 10rpx;
animation: fadeIn 0.5s ease;
}
.quote-author {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
text-align: right;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
<template>
<view class="home-component">
<!-- 动态背景元素 -->
<view class="animated-bg">
<view v-for="i in 6" :key="i" class="bg-circle" :style="getRandomCircleStyle()"></view>
</view>
<!-- 顶部欢迎卡片 -->
<view class="welcome-card">
<view class="welcome-content">
<view class="welcome-title">智辩云枢</view>
<view class="welcome-subtitle">AI驱动的辩论训练与分析平台</view>
</view>
</view>
<!-- 功能卡片区域 -->
<view class="feature-grid">
<view class="feature-card" @click="switchTab(1)">
<view class="feature-icon">
<image src="/static/icons/chat-1-line.png" mode="aspectFit"></image>
</view>
<view class="feature-text">
<view class="feature-title">模拟辩论</view>
<view class="feature-desc">与AI进行实时辩论对练提升应变能力</view>
</view>
<view class="feature-arrow">
<image src="/static/icons/arrow-right-s-line.png" mode="aspectFit"></image>
</view>
</view>
<view class="feature-card" @click="switchTab(2)">
<view class="feature-icon">
<image src="/static/icons/lightbulb-line.png" mode="aspectFit"></image>
</view>
<view class="feature-text">
<view class="feature-title">立论助手</view>
<view class="feature-desc">AI辅助构建辩论框架智能生成论点与论据</view>
</view>
<view class="feature-arrow">
<image src="/static/icons/arrow-right-s-line.png" mode="aspectFit"></image>
</view>
</view>
<view class="feature-card" @click="switchTab(3)">
<view class="feature-icon">
<image src="/static/icons/file-chart-line.png" mode="aspectFit"></image>
</view>
<view class="feature-text">
<view class="feature-title">复盘分析</view>
<view class="feature-desc">分析辩论过程提供改进建议与优化方向</view>
</view>
<view class="feature-arrow">
<image src="/static/icons/arrow-right-s-line.png" mode="aspectFit"></image>
</view>
</view>
</view>
<!-- 最近活动区域 -->
<view class="recent-section">
<view class="section-header">
<view class="section-title">最近活动</view>
<view class="section-more" @click="showMoreActivities">
<text>查看更多</text>
<image src="/static/icons/arrow-right-s-line.png" mode="aspectFit"></image>
</view>
</view>
<view class="activity-list">
<view class="activity-item" v-for="(activity, index) in recentActivities" :key="index">
<view class="activity-icon" :class="activity.type">
<image :src="getActivityIcon(activity.type)" mode="aspectFit"></image>
</view>
<view class="activity-content">
<view class="activity-title">{{ activity.title }}</view>
<view class="activity-time">{{ activity.time }}</view>
</view>
<view class="activity-arrow">
<image src="/static/icons/arrow-right-s-line.png" mode="aspectFit"></image>
</view>
</view>
</view>
</view>
<!-- 底部安全区域 -->
<view class="safe-area-bottom"></view>
</view>
</template>
<script>
export default {
data() {
return {
recentActivities: [
{
type: 'debate',
title: '关于"教育改革"的辩论',
time: '今天 14:30'
},
{
type: 'argument',
title: '关于"环境保护"的立论',
time: '昨天 09:15'
},
{
type: 'review',
title: '关于"人工智能"的辩论复盘',
time: '3天前'
}
]
};
},
methods: {
//
getRandomCircleStyle() {
const size = Math.random() * 300 + 100;
const x = Math.random() * 100;
const y = Math.random() * 100;
const delay = Math.random() * 5;
const duration = Math.random() * 10 + 15;
return {
width: `${size}rpx`,
height: `${size}rpx`,
left: `${x}%`,
top: `${y}%`,
animationDelay: `${delay}s`,
animationDuration: `${duration}s`
};
},
//
getActivityIcon(type) {
const icons = {
'debate': '/static/icons/chat-1-line.png',
'argument': '/static/icons/lightbulb-line.png',
'review': '/static/icons/file-chart-line.png'
};
return icons[type] || icons.debate;
},
//
switchTab(index) {
this.$emit('tab-change', index);
},
//
showMoreActivities() {
uni.showToast({
title: '功能开发中',
icon: 'none'
});
}
}
};
</script>
<style scoped>
.home-component {
width: 100%;
min-height: 100vh;
padding: 30rpx;
display: flex;
flex-direction: column;
background: linear-gradient(135deg, #4338CA 0%, #7C3AED 100%);
overflow-x: hidden;
box-sizing: border-box;
padding-bottom: 180rpx; /* 为底部TabBar留出空间 */
position: relative;
}
/* 动态背景 */
.animated-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 0;
}
.bg-circle {
position: absolute;
border-radius: 50%;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
animation: float 20s infinite ease-in-out;
opacity: 0.4;
transition: all 1.5s ease-in-out;
}
@keyframes float {
0%, 100% {
transform: translate(0, 0) scale(1);
}
25% {
transform: translate(5%, 10%) scale(1.05);
}
50% {
transform: translate(10%, 5%) scale(0.95);
}
75% {
transform: translate(5%, 15%) scale(1.1);
}
}
/* 欢迎卡片 */
.welcome-card {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
border-radius: 28rpx;
padding: 30rpx;
width: 100%;
margin-bottom: 30rpx;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.15);
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
z-index: 1;
display: flex;
justify-content: space-between;
align-items: center;
animation: slideDown 0.6s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-30rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.welcome-content {
flex: 1;
}
.welcome-title {
font-size: 48rpx;
color: #FFFFFF;
font-weight: 700;
margin-bottom: 16rpx;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
}
.welcome-subtitle {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
line-height: 1.5;
}
/* 功能卡片区域 */
.feature-grid {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-bottom: 40rpx;
position: relative;
z-index: 1;
}
.feature-card {
background: rgba(255, 255, 255, 0.1);
border-radius: 24rpx;
padding: 24rpx;
display: flex;
align-items: center;
border: 1px solid rgba(255, 255, 255, 0.15);
transition: all 0.3s;
animation: fadeIn 0.5s ease-out;
animation-fill-mode: both;
}
.feature-card:nth-child(1) {
animation-delay: 0.1s;
}
.feature-card:nth-child(2) {
animation-delay: 0.2s;
}
.feature-card:nth-child(3) {
animation-delay: 0.3s;
}
.feature-card:active {
transform: scale(0.98);
background: rgba(255, 255, 255, 0.15);
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.feature-icon {
width: 80rpx;
height: 80rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.feature-icon image {
width: 44rpx;
height: 44rpx;
filter: brightness(0) invert(1);
}
.feature-text {
flex: 1;
}
.feature-title {
font-size: 32rpx;
color: #FFFFFF;
font-weight: 600;
margin-bottom: 8rpx;
}
.feature-desc {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.7);
line-height: 1.4;
}
.feature-arrow {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.feature-arrow image {
width: 30rpx;
height: 30rpx;
filter: brightness(0) invert(1);
opacity: 0.7;
}
/* 最近活动区域 */
.recent-section {
position: relative;
z-index: 1;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.section-title {
font-size: 36rpx;
color: #FFFFFF;
font-weight: 600;
}
.section-more {
display: flex;
align-items: center;
color: rgba(255, 255, 255, 0.7);
font-size: 26rpx;
}
.section-more image {
width: 24rpx;
height: 24rpx;
margin-left: 6rpx;
filter: brightness(0) invert(1);
opacity: 0.7;
}
.activity-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.activity-item {
background: rgba(255, 255, 255, 0.1);
border-radius: 20rpx;
padding: 20rpx;
display: flex;
align-items: center;
border: 1px solid rgba(255, 255, 255, 0.15);
transition: all 0.3s;
}
.activity-item:active {
background: rgba(255, 255, 255, 0.15);
}
.activity-icon {
width: 60rpx;
height: 60rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
}
.activity-icon.debate {
background: rgba(245, 158, 11, 0.3);
}
.activity-icon.argument {
background: rgba(16, 185, 129, 0.3);
}
.activity-icon.review {
background: rgba(79, 70, 229, 0.3);
}
.activity-icon image {
width: 36rpx;
height: 36rpx;
filter: brightness(0) invert(1);
}
.activity-content {
flex: 1;
}
.activity-title {
font-size: 28rpx;
color: #FFFFFF;
margin-bottom: 6rpx;
}
.activity-time {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.6);
}
.activity-arrow {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.activity-arrow image {
width: 24rpx;
height: 24rpx;
filter: brightness(0) invert(1);
opacity: 0.7;
}
/* 底部安全区域 */
.safe-area-bottom {
height: 20rpx;
height: calc(20rpx + constant(safe-area-inset-bottom)); /* iOS 11.0-11.2 */
height: calc(20rpx + env(safe-area-inset-bottom)); /* iOS 11.2+ */
}
</style>

@ -1,8 +1,29 @@
<template>
<view class="argument-component">
<view class="content-card">
<view class="card-title">复盘功能</view>
<view class="card-content">在这里可以进行辩论过程的复盘</view>
<view class="review-component">
<!-- 动态背景元素 -->
<view class="animated-bg">
<view v-for="i in 6" :key="i" class="bg-circle" :style="getRandomCircleStyle()"></view>
</view>
<!-- 顶部卡片 - 可收缩 -->
<view class="content-card" :class="{ 'collapsed': isCardCollapsed }">
<view class="card-header" @click="toggleCard">
<view class="card-icon-wrapper">
<view class="card-icon">
<image src="/static/icons/file-chart-line.png" mode="aspectFit"></image>
</view>
</view>
<view class="card-text">
<view class="card-title">复盘分析</view>
<view class="card-content" v-if="!isCardCollapsed"></view>
</view>
<view class="collapse-icon">
<image
:src="isCardCollapsed ? '/static/icons/arrow-down-s-line.png' : '/static/icons/arrow-up-s-line.png'"
mode="aspectFit"
></image>
</view>
</view>
</view>
<!-- 聊天展示区 -->
@ -19,30 +40,43 @@
class="chat-message"
:class="msg.role === 'user' ? 'from-user' : 'from-ai'"
>
<view class="avatar" v-if="msg.role === 'ai'">AI</view>
<view class="bubble">
<block v-if="msg.loading">
<view class="dot-flashing"></view>
<view class="loading-animation">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></view>
</view>
</block>
<block v-else>
{{ msg.content }}
</block>
</view>
<view class="avatar user-avatar" v-if="msg.role === 'user'"></view>
</view>
</scroll-view>
<!-- 输入框与发送按钮 -->
<view class="chat-input">
<view class="input-group">
<textarea
class="textarea-box"
v-model="content"
placeholder="请输入辩论的过程"
auto-height
maxlength="20000"
/>
<view class="textarea-container">
<textarea
class="textarea-box"
v-model="content"
placeholder="粘贴你的辩论记录AI将为你分析优缺点并提供改进建议"
auto-height
maxlength="5000"
/>
<button class="send-button-embedded" @click="sendMessage()">
<image src="/static/icons/send-plane-fill.png" mode="aspectFit"></image>
</button>
</view>
</view>
<button class="send-button" @click="sendMessage()"></button>
</view>
<!-- 底部安全区域 -->
<view class="safe-area-bottom"></view>
</view>
</template>
@ -54,16 +88,41 @@ export default {
messages: [
{
role: "ai",
content:
"辩论过程复盘功能,可以帮你回顾和总结辩论过程中的关键点、亮点以及可能的改进之处。",
},
content: "欢迎使用辩论复盘分析!请粘贴你的辩论记录,我将为你分析优缺点并提供改进建议。"
}
],
scrollToView: "",
content: "",
isCardCollapsed: false, //
};
},
methods: {
// /
toggleCard() {
this.isCardCollapsed = !this.isCardCollapsed;
},
//
getRandomCircleStyle() {
const size = Math.random() * 300 + 100;
const x = Math.random() * 100;
const y = Math.random() * 100;
const delay = Math.random() * 5;
const duration = Math.random() * 10 + 15;
return {
width: `${size}rpx`,
height: `${size}rpx`,
left: `${x}%`,
top: `${y}%`,
animationDelay: `${delay}s`,
animationDuration: `${duration}s`
};
},
async sendMessage() {
if (!this.content.trim()) return;
//
this.messages.push({
role: "user",
@ -77,13 +136,14 @@ export default {
this.messages.push({ role: "ai", content: "", loading: true });
this.scrollToBottom();
// AI
// AI
const reply = await this.callAI(this.content);
// loading AI
this.messages.splice(aiIndex, 1, { role: "ai", content: reply });
this.scrollToBottom();
},
async callAI(content) {
this.content = "";
return new Promise((resolve, reject) => {
@ -127,177 +187,424 @@ export default {
</script>
<style scoped>
.argument-component {
.review-component {
width: 100%;
padding: 20rpx;
height: 100vh;
padding: 30rpx;
display: flex;
flex-direction: column;
height: 78vh;
background: linear-gradient(135deg, #2c3e50, #337fc6);
background: linear-gradient(135deg, #4338CA 0%, #7C3AED 100%);
overflow-x: hidden;
box-sizing: border-box;
padding-bottom: 180rpx; /* 为底部TabBar留出空间 */
position: relative;
}
overflow-x: hidden; /* 禁止横向滚动 */
box-sizing: border-box; /* 避免 padding 导致溢出 */
/* 动态背景 */
.animated-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 0;
}
.bg-circle {
position: absolute;
border-radius: 50%;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
animation: float 20s infinite ease-in-out;
opacity: 0.4;
/* 添加过渡效果 */
transition: all 1.5s ease-in-out;
}
@keyframes float {
0%, 100% {
transform: translate(0, 0) scale(1);
}
25% {
transform: translate(5%, 10%) scale(1.05);
}
50% {
transform: translate(10%, 5%) scale(0.95);
}
75% {
transform: translate(5%, 15%) scale(1.1);
}
}
/* 顶部卡片 */
.content-card {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 20rpx;
padding: 30rpx;
width: 90%;
margin: 0 auto 30rpx;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
border-radius: 28rpx;
padding: 20rpx;
width: 100%;
margin-bottom: 30rpx;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.15);
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
z-index: 1;
animation: slideDown 0.6s ease-out;
transition: all 0.3s ease;
}
.content-card.collapsed {
padding: 16rpx 20rpx;
margin-bottom: 20rpx;
}
.card-header {
display: flex;
align-items: center;
cursor: pointer;
}
.collapse-icon {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s ease;
}
.collapse-icon image {
width: 30rpx;
height: 30rpx;
filter: brightness(0) invert(1);
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-30rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card-icon-wrapper {
position: relative;
margin-right: 24rpx;
}
.card-icon {
width: 80rpx;
height: 80rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.collapsed .card-icon {
width: 70rpx;
height: 70rpx;
}
.card-icon::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
to bottom right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0) 100%
);
transform: rotate(45deg);
animation: shine 3s infinite;
}
@keyframes shine {
0% {
transform: translateX(-100%) rotate(45deg);
}
20%, 100% {
transform: translateX(100%) rotate(45deg);
}
}
.card-icon image {
width: 45rpx;
height: 45rpx;
filter: brightness(0) invert(1);
z-index: 1;
transition: all 0.3s ease;
}
.collapsed .card-icon image {
width: 40rpx;
height: 40rpx;
}
.card-text {
flex: 1;
transition: all 0.3s ease;
}
.card-title {
font-size: 36rpx;
color: #ffffff;
font-weight: bold;
margin-bottom: 20rpx;
color: #FFFFFF;
font-weight: 700;
margin-bottom: 10rpx;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
}
.collapsed .card-title {
font-size: 32rpx;
margin-bottom: 0;
}
.card-content {
font-size: 28rpx;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
line-height: 1.5;
transition: all 0.3s ease;
}
/* 聊天区域 */
.chat-area {
flex: 1;
width: 90%;
margin: 0 auto;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 20rpx;
padding: 20rpx;
width: 100%;
background: rgba(255, 255, 255, 0.1);
border-radius: 28rpx;
padding: 30rpx;
margin-bottom: 30rpx;
overflow-y: auto;
border: 1px solid rgba(255, 255, 255, 0.15);
position: relative;
z-index: 1;
transition: all 0.3s ease;
}
/* 消息通用样式 */
.chat-message {
display: flex;
margin-bottom: 20rpx;
margin-bottom: 30rpx;
align-items: flex-start;
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 用户消息靠右 */
.from-user {
justify-content: flex-end;
}
/* AI消息靠左 */
.from-ai {
justify-content: flex-start;
}
/* 头像样式 */
.avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24rpx;
font-weight: bold;
margin-right: 16rpx;
flex-shrink: 0;
}
.user-avatar {
background: #F59E0B;
margin-left: 16rpx;
margin-right: 0;
}
/* 气泡样式 */
.bubble {
max-width: 70%;
padding: 20rpx;
border-radius: 20rpx;
padding: 20rpx 24rpx;
border-radius: 24rpx;
font-size: 28rpx;
line-height: 1.6;
word-break: break-word;
box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1);
position: relative;
transition: all 0.3s ease; /* 添加过渡效果 */
}
/* 用户气泡颜色 */
.from-user .bubble {
background-color: #00c4ff;
color: white;
border-bottom-right-radius: 0;
background: #F59E0B;
color: #FFFFFF;
border-bottom-right-radius: 4rpx;
font-weight: 500;
}
.from-user .bubble::after {
content: '';
position: absolute;
right: -12rpx;
bottom: 0;
width: 0;
height: 0;
border-left: 16rpx solid #F59E0B;
border-top: 16rpx solid transparent;
transition: all 0.3s ease; /* 添加过渡效果 */
}
/* AI气泡颜色 */
.from-ai .bubble {
background-color: #ffd54f;
color: #333;
border-bottom-left-radius: 0;
background: rgba(255, 255, 255, 0.2);
color: #FFFFFF;
border-bottom-left-radius: 4rpx;
min-width: 80rpx;
}
.from-ai .bubble::after {
content: '';
position: absolute;
left: -12rpx;
bottom: 0;
width: 0;
height: 0;
border-right: 16rpx solid rgba(255, 255, 255, 0.2);
border-top: 16rpx solid transparent;
transition: all 0.3s ease; /* 添加过渡效果 */
}
/* 加载动画 */
.loading-animation {
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
height: 30rpx;
}
.loading-animation .dot {
width: 12rpx;
height: 12rpx;
background-color: #FFFFFF;
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out both;
}
.loading-animation .dot:nth-child(1) {
animation-delay: -0.32s;
}
min-width: 80px;
.loading-animation .dot:nth-child(2) {
animation-delay: -0.16s;
}
@keyframes bounce {
0%, 80%, 100% {
transform: scale(0);
}
40% {
transform: scale(1.0);
}
}
/* 输入区域 */
.chat-input {
display: flex;
padding: 20rpx;
background-color: rgba(255, 255, 255, 0.05);
border-top: 1rpx solid #ccc;
padding: 16rpx;
background: rgba(255, 255, 255, 0.15);
border-radius: 28rpx;
align-items: flex-end;
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
z-index: 1;
margin-bottom: 20rpx;
}
.input-group {
flex: 1;
display: flex;
flex-direction: column;
gap: 10rpx;
gap: 16rpx;
}
.input-box {
background-color: #fff;
border-radius: 10rpx;
padding: 10rpx 20rpx;
margin-bottom: 20rpx;
font-size: 28rpx;
border: none;
.textarea-container {
position: relative;
width: 100%;
}
.textarea-box {
background-color: #fff;
border-radius: 10rpx;
padding: 10rpx 20rpx;
font-size: 28rpx;
min-height: 80rpx;
border: none;
.send-button-embedded {
position: absolute;
bottom: 16rpx;
left: 16rpx;
background: transparent;
width: 60rpx;
height: 60rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
padding: 0;
z-index: 2;
}
.send-button {
margin-left: 20rpx;
background-color: #00bcd4;
color: #fff;
border-radius: 10rpx;
padding: 20rpx;
font-size: 28rpx;
white-space: nowrap;
.send-button-embedded:active {
transform: scale(1.2) rotate(-15deg);
}
/* AI loading 动画 */
.dot-flashing {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background-color: #333;
animation: dotFlashing 1s infinite linear alternate;
position: relative;
margin-left: auto;
margin-right: auto;
.send-button-embedded image {
width: 40rpx;
height: 40rpx;
filter: invert(72%) sepia(87%) saturate(1242%) hue-rotate(325deg) brightness(101%) contrast(96%);
}
.dot-flashing::before,
.dot-flashing::after {
content: "";
display: inline-block;
position: absolute;
top: 0;
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background-color: #333;
.textarea-box {
background: rgba(255, 255, 255, 0.2);
border-radius: 20rpx;
padding: 16rpx 20rpx 16rpx 90rpx; /* 增加左侧内边距为发送按钮留出空间 */
font-size: 28rpx;
color: #FFFFFF;
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s;
min-height: 120rpx;
max-height: 300rpx; /* 增加最大高度,适合复盘分析的长文本 */
width: 100%;
box-sizing: border-box;
}
.dot-flashing::before {
left: -30rpx;
animation: dotFlashing 1s infinite linear alternate;
animation-delay: 0.2s;
.textarea-box::placeholder {
color: #362d2de1;
}
.dot-flashing::after {
left: 30rpx;
animation: dotFlashing 1s infinite linear alternate;
animation-delay: 0.4s;
.textarea-box:focus {
border-color: #F59E0B;
outline: none;
background: rgba(255, 255, 255, 0.25);
}
@keyframes dotFlashing {
0% {
background-color: #333;
}
50%,
100% {
background-color: rgba(51, 51, 51, 0.3);
}
/* 底部安全区域 */
.safe-area-bottom {
height: 20rpx;
height: calc(20rpx + constant(safe-area-inset-bottom)); /* iOS 11.0-11.2 */
height: calc(20rpx + env(safe-area-inset-bottom)); /* iOS 11.2+ */
}
</style>
</style>

@ -1,136 +1,164 @@
<template>
<view class="tab-bar">
<!-- 添加移动的背景块 -->
<view class="active-bg" :style="{ transform: `translateX(${100 * props.currentComponentIndex}%)` }"></view>
<view
class="tab-item"
:class="{
'active': props.currentComponentIndex === index,
'is-first-tab': index === 0
}"
v-for="(item, index) in tabList"
:key="index"
@click="changeTab(index)">
<text :class="{'active': props.currentComponentIndex === index}">{{ item.pageName }}</text>
</view>
</view>
</template>
<script setup>
// emit
const emit = defineEmits(['change-tab', 'change-index']);
const props = defineProps(['currentComponentIndex']);
const tabList = [
{
pageName:'首页',
component:'PageOfHome'
},
{
pageName:'立论',
component:'PageOfArgument'
},
{
pageName:'复盘',
component:'PageOfReview'
},
{
pageName:'辩论',
component:'PageOfDebate'
}
];
const changeTab = (index) => {
if(props.currentComponentIndex === index) return;
//
emit('change-tab', tabList[index].component);
emit('change-index', index);
};
</script>
<style scoped>
.tab-bar {
display: flex;
width: 70%;
margin: 0 auto;
height: 70%;
background-color: #1A2C42;
border: none;
position: relative;
border-radius: 25rpx;
overflow: hidden;
box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.2);
transform: translateZ(0);
-webkit-transform: translateZ(0);
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}
/* 移动的背景块 */
.active-bg {
position: absolute;
width: 25%;
height: 100%;
background-color: #00D6B9;
border-radius: 25rpx;
transition: transform 0.3s ease;
z-index: 1;
box-shadow: 0 0 20rpx rgba(0, 214, 185, 0.5);
/* 添加硬件加速 */
will-change: transform;
}
.tab-item {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
position: relative;
z-index: 2;
}
.tab-item::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 1px;
height: 60%;
z-index: 2;
background: linear-gradient(
to bottom,
transparent,
rgba(0, 214, 185, 0.1) 15%,
rgba(0, 214, 185, 0.5) 50%,
rgba(0, 214, 185, 0.1) 85%,
transparent
);
}
.tab-item.is-first-tab::before {
display: none;
}
.tab-item.active {
background-color: transparent;
}
.tab-item text {
font-size: 24rpx;
color: rgba(0, 214, 185, 0.7);
transition: all 0.3s ease;
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.2);
}
.tab-item text.active {
color: #ffffff;
font-weight: bold;
text-shadow: 0 1rpx 8rpx rgba(255, 255, 255, 0.4);
transform: scale(1.05);
}
<template>
<view class="tab-bar">
<view
v-for="(tab, index) in tabs"
:key="index"
class="tab-item"
:class="{ active: activeTab === index }"
@click="switchTab(index)"
>
<view class="tab-icon-container" :class="{ active: activeTab === index }">
<view class="tab-icon">
<image :src="activeTab === index ? tab.activeIcon : tab.icon" mode="aspectFit"></image>
</view>
</view>
<view class="tab-label" :class="{ active: activeTab === index }">{{ tab.label }}</view>
</view>
</view>
</template>
<script>
export default {
props: {
activeTab: {
type: Number,
default: 0
}
},
data() {
return {
tabs: [
{
label: '首页',
icon: '/static/icons/home.png',
activeIcon: '/static/icons/home-fill.png'
},
{
label: '辩论',
icon: '/static/icons/chat-1-line.png',
activeIcon: '/static/icons/chat-1-fill.png'
},
{
label: '立论',
icon: '/static/icons/lightbulb-line.png',
activeIcon: '/static/icons/lightbulb-fill.png'
},
{
label: '复盘',
icon: '/static/icons/file-chart-line.png',
activeIcon: '/static/icons/file-chart-fill.png'
}
]
};
},
methods: {
switchTab(index) {
if (this.activeTab === index) return;
this.$emit('tab-change', index);
}
}
};
</script>
<style scoped>
.tab-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background: rgba(255, 255, 255, 0.5);
display: flex;
justify-content: space-around;
align-items: center;
border-top: 1px solid rgba(255, 255, 255, 0.1);
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
z-index: 100;
}
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
flex: 1;
height: 100%;
transition: all 0.3s;
}
.tab-icon-container {
width: 100rpx;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: all 0.3s ease;
z-index: 2;
border-radius: 50%;
min-width: 80rpx; /* 防止 flex 压缩 */
min-height: 80rpx;
flex-shrink: 0; /* 禁止压缩 */
}
.tab-icon-container.active {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
background-color: #B59DF1;
transform: translateY(-45rpx);
border: 14rpx solid #6839E0;
position: relative;
box-sizing: border-box;
box-shadow: 0 4rpx 20rpx rgba(104, 57, 224, 0.4);
}
.tab-icon {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.tab-icon image {
width: 50rpx;
height: 50rpx;
filter: brightness(0) invert(0.8);
opacity: 0.7;
transition: all 0.3s;
}
.tab-label {
font-size: 24rpx;
font-weight: 400;
color: rgba(255, 255, 255, 0.7);
transition: all 0.3s;
margin-top: 6rpx;
opacity: 0.7;
font-family: 'Poppins' , 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
letter-spacing: 1rpx;
}
.tab-label.active {
color: #FFFFFF;
font-weight: 600;
opacity: 1;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
transform: translateY(-15rpx) scale(1.1);
}
.tab-icon-container.active .tab-icon image {
opacity: 1;
filter: brightness(0) invert(1);
transform: scale(1.1);
}
.tab-item:active {
opacity: 0.7;
}
</style>

@ -1,22 +1,25 @@
{
"pages": [
{
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "智辩云枢",
"navigationBarBackgroundColor": "#1a2a6c",
"backgroundColor": "#000000"
}
}
{
"pages": [
{
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "智辩云枢",
"navigationBarBackgroundColor": "#1a2a6c",
"backgroundColor": "#000000"
},
"style": {
"scoped": false
}
}

@ -1,154 +1,52 @@
<template>
<view class="home-page">
<view class="home-header">
<text class="home-header-title"></text>
<text class="home-header-title"></text>
<text class="home-header-title"></text>
<text class="home-header-title"></text>
</view>
<view class="container">
<HomeCom v-if="activeTab === 0" @tab-change="handleTabChange"/>
<DebateCom v-if="activeTab === 1" />
<ArgumentCom v-if="activeTab === 2" />
<ReviewCom v-if="activeTab === 3" />
<view class="home-content">
<!-- 使用条件渲染替代动态组件 -->
<PageOfHome v-if="currentComponent === 'PageOfHome'" @change-tab="handleTabChange" @change-index="handleIndexChange" />
<PageOfArgument v-else-if="currentComponent === 'PageOfArgument'" />
<PageOfReview v-else-if="currentComponent === 'PageOfReview'" />
<PageOfDebate v-else-if="currentComponent === 'PageOfDebate'" />
</view>
<view class="home-footer">
<TabBar @change-tab="handleTabChange" @change-index="handleIndexChange" :currentComponentIndex="currentComponentIndex"/>
</view>
<TabBar :activeTab="activeTab" @tab-change="handleTabChange" />
</view>
</template>
<script setup>
import TabBar from '../../components/TabBar.vue';
import { ref } from 'vue';
//
import PageOfHome from '../../components/HomeCom.vue';
import PageOfArgument from '../../components/ArgumentCom.vue';
import PageOfReview from '../../components/ReviewCom.vue';
import PageOfDebate from '../../components/DebateCom.vue'
//
const currentComponent = ref('PageOfHome');
// index
const currentComponentIndex = ref(0);
//
const handleTabChange = (componentName) => {
currentComponent.value = componentName;
};
const handleIndexChange = (index) => {
currentComponentIndex.value = index;
}
</script>
<style scoped>
.home-page {
position: relative;
width: 100vw;
height: 100vh;
background: linear-gradient(135deg, #1a2a6c, #000000);
display: flex;
flex-direction: column;
align-items: center;
}
.home-header {
width: 100%;
height: min(100rpx, fit-content);
/* background-color: #000000; */
margin-top: 20rpx;
margin-left: 25rpx;
margin-bottom: 20rpx;
display: flex;
}
.home-header::after{
content: '';
width: 100%;
height: 1px;
background: linear-gradient(to right, transparent, rgba(0, 214, 185, 0.1) 15%, rgba(0, 214, 185, 0.5) 50%, rgba(0, 214, 185, 0.1) 85%, transparent);
position: absolute;
top: 100rpx; /* 将分割线定位在header的底部 */
left: 0;
z-index: 1;
transform: translateY(20rpx);
}
.home-header-title {
font-size: 40rpx;
color: #ffffff;
text-align: left;
margin-left: 20rpx;
font-weight: bold;
margin-top: 20rpx;
}
.home-header-title:nth-child(odd) {
animation: float-up-to-down 3s infinite;
}
.home-header-title:nth-child(even) {
animation: float-down-to-up 3s infinite;
}
@keyframes float-up-to-down {
0%{
transform: translateY(-10rpx);
}
20%{
transform: translateY(-7rpx);
}
50%{
transform: translateY(0rpx);
}
70%{
transform: translateY(7rpx);
}
100%{
transform: translateY(-10rpx);
}
}
@keyframes float-down-to-up {
0%{
transform: translateY(10rpx);
}
20%{
transform: translateY(7rpx);
}
50%{
transform: translateY(0rpx);
}
70%{
transform: translateY(-7rpx);
}
100%{
transform: translateY(10rpx);
<script>
import HomeCom from '@/components/HomeCom.vue';
import DebateCom from '@/components/DebateCom.vue';
import ArgumentCom from '@/components/ArgumentCom.vue';
import ReviewCom from '@/components/ReviewCom.vue';
import TabBar from '@/components/TabBar.vue';
export default {
components: {
HomeCom,
DebateCom,
ArgumentCom,
ReviewCom,
TabBar
},
data() {
return {
activeTab: 0
};
},
methods: {
handleTabChange(index) {
this.activeTab = index;
//
const titles = ['首页', '辩论', '立论', '复盘'];
uni.setNavigationBarTitle({
title: titles[index]
});
}
}
};
</script>
.home-content {
flex: 1;
width: 100%;
padding: 30rpx 0;
box-sizing: border-box;
overflow-y: auto;
min-height: 400rpx;
position: relative;
z-index: 2;
margin-bottom: 140rpx;
}
.home-footer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 120rpx;
z-index: 100;
}
</style>
<style>
.container {
width: 100%;
min-height: 100vh;
position: relative;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Loading…
Cancel
Save