Compare commits

...

4 Commits

Author SHA1 Message Date
yvan 1dde942ff1 1 2025-08-23 17:09:34 +08:00
yvan 6678bce527 1 2025-08-23 16:00:52 +08:00
yvan befb1dd8f9 1 2025-08-23 14:40:48 +08:00
yvan 7bc3d491b8 1 2025-08-23 14:28:20 +08:00
7 changed files with 235 additions and 314 deletions

View File

@ -1,249 +0,0 @@
// HTTP API使用示例
// 注意:鉴权配置已简化,只需要在 http/config/config.js 中配置不需要鉴权的接口即可
// 导入API模块
import { petsApi, assistantApi, adoptionApi, profileApi, commonApi, addNoAuthApis, setEnvironment } from '@/http/index.js'
// 或者导入所有API
// import api from '@/http/index.js'
export default {
data() {
return {
petsList: [],
userInfo: {},
loading: false
}
},
methods: {
// 示例1获取宠物列表自动鉴权
async loadPets() {
try {
// 使用默认配置,自动根据接口判断是否需要鉴权
const pets = await petsApi.getPetsList()
this.petsList = pets
} catch (error) {
console.error('获取宠物列表失败:', error)
}
},
// 示例2添加不需要鉴权的接口
addCustomNoAuthApis() {
// 如果有自定义的接口不需要鉴权,可以这样添加
addNoAuthApis([
'/custom/public-api',
'/special/no-auth-endpoint'
])
},
// 示例3切换环境
switchEnvironment() {
// 根据需要切换环境
// #ifdef H5
setEnvironment('development') // H5开发环境
// #endif
// #ifdef MP-WEIXIN
setEnvironment('production') // 小程序生产环境
// #endif
// 或者根据条件动态切换
const isDev = process.env.NODE_ENV === 'development'
setEnvironment(isDev ? 'development' : 'production')
},
// 示例2添加宠物
async addNewPet() {
try {
const petData = {
name: '小白',
breed: '金毛',
age: 2,
gender: '公'
}
const result = await petsApi.addPet(petData, {
custom: {
auth: true,
loading: true,
toast: true // 显示成功/失败提示
}
})
uni.showToast({
title: '添加成功',
icon: 'success'
})
// 重新加载列表
this.loadPets()
} catch (error) {
// 错误已在拦截器中处理,这里可以做额外处理
console.error('添加宠物失败:', error)
}
},
// 示例3AI助手对话
async sendMessageToAI() {
try {
const messageData = {
message: '我的猫咪最近不爱吃饭,怎么办?',
petId: 123
}
const response = await assistantApi.sendMessage(messageData, {
custom: {
auth: true,
loading: true,
loadingText: 'AI正在思考中...'
}
})
console.log('AI回复:', response.reply)
} catch (error) {
console.error('AI对话失败:', error)
}
},
// 示例4用户登录
async userLogin() {
try {
const loginData = {
username: 'user@example.com',
password: '123456'
}
const result = await profileApi.userLogin(loginData, {
custom: {
auth: false, // 登录接口不需要token
loading: true,
loadingText: '正在登录...'
}
})
// 保存token和用户信息
uni.setStorageSync('token', result.token)
uni.setStorageSync('userInfo', result.userInfo)
uni.showToast({
title: '登录成功',
icon: 'success'
})
} catch (error) {
console.error('登录失败:', error)
}
},
// 示例5上传图片
async uploadPetImage() {
try {
// 选择图片
const chooseResult = await uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera']
})
const imageData = {
filePath: chooseResult.tempFilePaths[0],
name: 'petImage',
formData: {
type: 'pet',
petId: 123
}
}
const uploadResult = await commonApi.uploadImage(imageData, {
custom: {
auth: true,
loading: true,
loadingText: '正在上传图片...'
}
})
console.log('上传成功:', uploadResult.url)
uni.showToast({
title: '上传成功',
icon: 'success'
})
} catch (error) {
console.error('上传失败:', error)
}
},
// 示例6搜索领养宠物
async searchAdoptionPets() {
try {
const searchParams = {
keyword: '金毛',
type: 'dog',
age: '1-3',
location: '北京'
}
const pets = await adoptionApi.searchPets(searchParams, {
custom: {
auth: false, // 搜索不需要登录
loading: true
}
})
console.log('搜索结果:', pets)
} catch (error) {
console.error('搜索失败:', error)
}
},
// 示例7批量操作
async batchOperations() {
try {
// 并发执行多个请求
const [pets, userInfo, adoptionPets] = await Promise.all([
petsApi.getPetsList(),
profileApi.getUserInfo(),
adoptionApi.getAdoptionPets()
])
console.log('批量获取数据成功:', { pets, userInfo, adoptionPets })
} catch (error) {
console.error('批量操作失败:', error)
}
},
// 示例8自定义错误处理
async customErrorHandling() {
try {
const result = await petsApi.getPetsList({}, {
custom: {
auth: true,
loading: true,
toast: false, // 不显示默认错误提示
catch: true // 允许catch捕获错误
}
})
} catch (error) {
// 自定义错误处理
if (error.code === 401) {
uni.showModal({
title: '提示',
content: '登录已过期,请重新登录',
success: (res) => {
if (res.confirm) {
uni.reLaunch({
url: '/pages/login/login'
})
}
}
})
} else {
uni.showToast({
title: error.message || '操作失败',
icon: 'none'
})
}
}
}
}
}

View File

@ -1,5 +1,5 @@
<template>
<view class="adoption-container page-container-with-bg">
<view class="adoption-container page-container-unified">
<!-- 头部搜索栏 -->
<view class="header-section">
<view class="search-wrapper">
@ -1192,7 +1192,7 @@ export default {
/* 头部搜索栏 */
.header-section {
background: transparent;
padding: 24rpx 20rpx;
padding: 0 0 24rpx 0;
.search-wrapper {
display: flex;
@ -1287,7 +1287,7 @@ export default {
.filter-panel {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
padding: 24rpx 20rpx;
padding: 24rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
position: relative;
z-index: 10;
@ -1514,7 +1514,7 @@ export default {
/* 宠物列表区域 */
.pets-scroll {
height: calc(100vh - 100rpx);
padding: 0 20rpx;
padding: 0;
overflow-x: hidden;
width: 100%;
box-sizing: border-box;

View File

@ -1,5 +1,5 @@
<template>
<view class="assistant-container page-container-with-bg">
<view class="assistant-container page-container-unified">
<!-- 宠物助手信息卡片 -->
<view class="assistant-info-card">
<view class="assistant-avatar-wrapper">
@ -191,31 +191,54 @@ export default {
sendMessage() {
if (!this.inputMessage.trim() || this.isThinking) return
//
const userInput = this.inputMessage
//
const userMessage = {
type: 'user',
content: this.inputMessage,
content: userInput,
time: this.getCurrentTime()
}
this.messageList.push(userMessage)
//
this.inputMessage = ''
// AI
this.isThinking = true
this.scrollToBottom()
// DOM
this.$nextTick(() => {
this.forceScrollToBottom()
//
setTimeout(() => {
this.forceScrollToBottom()
}, 200)
setTimeout(() => {
this.forceScrollToBottom()
}, 500)
})
// AI
setTimeout(() => {
const aiMessage = {
type: 'ai',
content: this.getAIResponse(this.inputMessage),
content: this.getAIResponse(userInput),
time: this.getCurrentTime()
}
this.messageList.push(aiMessage)
this.isThinking = false
this.scrollToBottom()
}, 1500 + Math.random() * 1000) // 1.5-2.5
this.inputMessage = ''
// AI
this.$nextTick(() => {
this.forceScrollToBottom()
//
setTimeout(() => {
this.forceScrollToBottom()
}, 100)
})
}, 1500 + Math.random() * 1000) // 1.5-2.5
},
getAIResponse(question) {
@ -245,6 +268,17 @@ export default {
})
},
//
forceScrollToBottom() {
this.$nextTick(() => {
// 使
this.scrollTop = Date.now()
setTimeout(() => {
this.scrollTop = 999999
}, 50)
})
},
startVoiceInput() {
uni.showToast({
title: '语音功能开发中',
@ -273,7 +307,7 @@ export default {
.assistant-info-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 20rpx 30rpx 0 30rpx;
margin: 0 0 0 0;
border-radius: 24rpx;
padding: 24rpx;
display: flex;
@ -359,7 +393,7 @@ export default {
.quick-questions {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 20rpx 30rpx;
margin: 20rpx 0;
border-radius: 24rpx;
padding: 24rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
@ -400,7 +434,7 @@ export default {
}
.chat-messages {
margin-top: 20rpx;
margin-top: 24rpx;
flex: 1;
overflow: hidden;
}
@ -425,21 +459,31 @@ export default {
.message-item.user {
justify-content: flex-end;
padding: 0 10rpx 0 80rpx;
padding: 0 0 0 60rpx;
}
.message-item.ai {
justify-content: flex-start;
padding: 0 80rpx 0 10rpx;
padding: 0 60rpx 0 0;
}
.message-avatar {
width: 64rpx;
height: 64rpx;
margin: 0 16rpx;
margin: 0;
flex-shrink: 0;
}
/* AI消息头像 - 右边距 */
.message-item.ai .message-avatar {
margin-right: 16rpx;
}
/* 用户消息头像 - 左边距 */
.message-item.user .message-avatar {
margin-left: 16rpx;
}
.assistant-avatar, .user-avatar {
width: 100%;
height: 100%;
@ -555,10 +599,8 @@ export default {
}
.chat-input-area {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
padding: 16rpx 20rpx;
//padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
padding: 16rpx 0;
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
}
@ -566,48 +608,131 @@ export default {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 0 !important;
.input-wrapper {
flex: 1;
background: rgba(255, 255, 255, 0.9);
border-radius: 28rpx;
padding: 0 20rpx;
backdrop-filter: blur(10rpx);
border: 1rpx solid rgba(255, 255, 255, 0.3);
backdrop-filter: blur(15rpx);
border: 3rpx solid rgba(255, 255, 255, 0.98);
height: 56rpx;
display: flex;
align-items: center;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:
0 0 0 1rpx rgba(255, 255, 255, 0.4),
0 2rpx 8rpx rgba(255, 255, 255, 0.15),
0 4rpx 16rpx rgba(255, 255, 255, 0.1),
inset 0 1rpx 0 rgba(255, 255, 255, 0.2);
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 25rpx;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%, rgba(255, 255, 255, 0.05) 100%);
pointer-events: none;
}
&:focus-within {
border-color: rgba(255, 255, 255, 1);
box-shadow:
0 0 0 2rpx rgba(255, 255, 255, 0.6),
0 0 20rpx rgba(255, 255, 255, 0.3),
0 4rpx 12rpx rgba(255, 255, 255, 0.2),
0 8rpx 24rpx rgba(255, 255, 255, 0.15),
inset 0 1rpx 0 rgba(255, 255, 255, 0.3);
transform: translateY(-2rpx);
}
.message-input {
width: 100%;
height: 100%;
font-size: 28rpx;
color: #333333;
color: #ffffff;
border: none;
outline: none;
background: transparent;
position: relative;
z-index: 1;
&::placeholder {
color: rgba(255, 255, 255, 0.95);
font-weight: 400;
text-shadow: 0 1rpx 2rpx rgba(255, 255, 255, 0.1);
}
}
}
.send-button {
padding: 12rpx 20rpx;
height: 56rpx;
padding: 0 20rpx;
border-radius: 24rpx;
background: rgba(200, 200, 200, 0.5);
transition: all 0.3s ease;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%);
border: 3rpx solid rgba(255, 255, 255, 0.85);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: center;
position: relative;
backdrop-filter: blur(10rpx);
box-shadow:
0 0 0 1rpx rgba(255, 255, 255, 0.3),
0 2rpx 6rpx rgba(255, 255, 255, 0.12),
0 4rpx 12rpx rgba(255, 255, 255, 0.08),
inset 0 1rpx 0 rgba(255, 255, 255, 0.15);
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 21rpx;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, transparent 50%, rgba(255, 255, 255, 0.03) 100%);
pointer-events: none;
}
&:active {
transform: scale(0.96) translateY(1rpx);
}
.send-text {
font-size: 28rpx;
color: #666666;
color: rgba(255, 255, 255, 0.9);
font-weight: 500;
position: relative;
z-index: 1;
text-shadow: 0 1rpx 2rpx rgba(255, 255, 255, 0.1);
}
}
.send-button.active {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 100%);
box-shadow: 0 4rpx 16rpx rgba(255, 138, 128, 0.4);
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.04) 100%);
border: 3rpx solid rgba(255, 255, 255, 0.98);
box-shadow:
0 0 0 2rpx rgba(255, 255, 255, 0.5),
0 0 16rpx rgba(255, 255, 255, 0.25),
0 4rpx 10rpx rgba(255, 255, 255, 0.18),
0 6rpx 20rpx rgba(255, 255, 255, 0.12),
inset 0 1rpx 0 rgba(255, 255, 255, 0.25);
transform: translateY(-2rpx);
&::before {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.12) 0%, transparent 50%, rgba(255, 255, 255, 0.06) 100%);
}
.send-text {
color: #ffffff;
color: rgba(255, 255, 255, 0.98);
font-weight: 600;
text-shadow: 0 1rpx 3rpx rgba(255, 255, 255, 0.15);
}
}
}

View File

@ -7,21 +7,25 @@
<view class="category-tabs">
<u-tabs :list="categories" @click="switchCategory" :current="currentCategory"></u-tabs>
</view>
<scroll-view class="knowledge-list" scroll-y="true">
<view v-for="item in filteredKnowledge" :key="item.id" class="knowledge-card" @click="viewDetail(item)">
<view class="knowledge-item">
<text class="knowledge-title">{{ item.title }}</text>
<text class="knowledge-summary">{{ item.summary }}</text>
<view class="knowledge-meta">
<view class="category-tag">
<text class="tag-text">{{ item.category }}</text>
<view class="knowledge-list">
<scroll-view class="knowledge-scroll" scroll-y="true">
<view class="knowledge-content">
<view v-for="item in filteredKnowledge" :key="item.id" class="knowledge-card" @click="viewDetail(item)">
<view class="knowledge-item">
<text class="knowledge-title">{{ item.title }}</text>
<text class="knowledge-summary">{{ item.summary }}</text>
<view class="knowledge-meta">
<view class="category-tag">
<text class="tag-text">{{ item.category }}</text>
</view>
<text class="read-count">{{ item.readCount }}次阅读</text>
</view>
</view>
<text class="read-count">{{ item.readCount }}次阅读</text>
</view>
</view>
</view>
</scroll-view>
</scroll-view>
</view>
</view>
</template>
@ -107,39 +111,57 @@ export default {
<style lang="scss" scoped>
.knowledge-container {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
min-height: 100vh;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
position: relative;
}
.search-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 20rpx 30rpx;
margin: 20rpx 30rpx 0 30rpx;
border-radius: 24rpx;
padding: 20rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
flex-shrink: 0;
}
.category-tabs {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 20rpx 30rpx;
margin: 20rpx 30rpx 0 30rpx;
border-radius: 24rpx;
padding: 20rpx 0;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
flex-shrink: 0;
}
.knowledge-list {
height: calc(100vh - 300rpx);
padding: 0 30rpx;
margin-top: 20rpx;
flex: 1;
overflow: hidden;
}
.knowledge-scroll {
width: 100%;
height: 100%;
}
.knowledge-content {
padding: 20rpx 30rpx;
padding-bottom: 80rpx;
min-height: 100%;
box-sizing: border-box;
}
.knowledge-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 20rpx 0;
margin-bottom: 24rpx;
border-radius: 24rpx;
padding: 24rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);

View File

@ -1,5 +1,5 @@
<template>
<view class="pets-container gradient-bg page-container">
<view class="pets-container page-container-unified">
<!-- 标签切换容器 -->
<view class="tabs-container" v-if="petsList.length > 0">
<view class="tabs-header">
@ -653,12 +653,12 @@ export default {
<style lang="scss" scoped>
.pets-container {
padding-bottom: 120rpx;
/* 边距由外层page-container-unified统一管理 */
}
.tabs-container {
background: rgba(255, 255, 255, 0.85);
margin: 20rpx 20rpx 30rpx;
margin: 0 0 30rpx 0;
border-radius: 40rpx;
backdrop-filter: blur(20rpx);
overflow: visible;
@ -758,7 +758,7 @@ export default {
}
.tab-content {
padding: 30rpx;
padding: 0;
}
.tab-panel {
@ -941,7 +941,7 @@ export default {
}
.functions-section {
margin: 16rpx 30rpx 20rpx;
margin: 16rpx 0 20rpx 0;
background: rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
padding: 16rpx 0;
@ -1037,7 +1037,7 @@ export default {
}
.cards-section {
padding: 0 30rpx;
padding: 0;
display: flex;
flex-direction: column;
gap: 16rpx;

View File

@ -1,5 +1,5 @@
<template>
<view class="profile-container page-container-with-bg">
<view class="profile-container page-container-unified">
<!-- 用户信息卡片 -->
<view class="user-info-card">
<view class="user-avatar-section" :class="{ 'logged-in': userInfo.nickName }" @click="handleUserAction">
@ -690,14 +690,14 @@ export default {
<style lang="scss" scoped>
.profile-container {
padding-bottom: 40rpx;
/* 边距由外层page-container-unified统一管理 */
}
/* 用户信息卡片 */
.user-info-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 30rpx 24rpx 30rpx;
margin: 0 0 24rpx 0;
border-radius: 24rpx;
padding: 32rpx;
display: flex;
@ -837,7 +837,7 @@ export default {
.stats-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 30rpx 24rpx 30rpx;
margin: 0 0 24rpx 0;
border-radius: 24rpx;
padding: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
@ -907,7 +907,7 @@ export default {
.family-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 30rpx 24rpx 30rpx;
margin: 0 0 24rpx 0;
border-radius: 24rpx;
padding: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
@ -1025,7 +1025,7 @@ export default {
.adoption-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 30rpx 24rpx 30rpx;
margin: 0 0 24rpx 0;
border-radius: 24rpx;
padding: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
@ -1122,7 +1122,7 @@ export default {
@media (max-width: 375px) {
.profile-container {
.user-info-card {
margin: 0 20rpx 20rpx 20rpx;
margin: 0 0 20rpx 0;
padding: 24rpx;
}
@ -1130,7 +1130,7 @@ export default {
.family-card,
.adoption-card,
.settings-card {
margin: 0 20rpx 20rpx 20rpx;
margin: 0 0 20rpx 0;
padding: 24rpx;
}
@ -1245,7 +1245,7 @@ export default {
.settings-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 30rpx 24rpx 30rpx;
margin: 0 0 24rpx 0;
border-radius: 24rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);

View File

@ -135,6 +135,17 @@
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
}
/* 统一布局架构 - 页面容器 */
.page-container-unified {
@extend .page-container-with-bg;
padding: 20rpx 30rpx 0 30rpx; /* 上边距20rpx左右边距30rpx下边距0 */
/* 响应式设计 - 小屏幕使用较小边距 */
@media (max-width: 375px) {
padding: 20rpx 20rpx 0 20rpx;
}
}
/* 通用背景渐变 */
.gradient-bg {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
@ -175,6 +186,18 @@
border-radius: 28rpx;
}
/* 统一布局架构 - 卡片样式 */
.card-unified {
@extend .card;
margin: 0 0 24rpx 0; /* 仅保留下边距用于卡片间隔 */
/* 响应式设计 - 小屏幕调整间距 */
@media (max-width: 375px) {
margin: 0 0 20rpx 0;
padding: 20rpx;
}
}
/* 通用按钮样式 */
.btn {
padding: 16rpx 32rpx;