feat:交接

This commit is contained in:
2025-11-04 19:25:16 +08:00
parent 4b8498203d
commit d45e556c20
20 changed files with 4441 additions and 677 deletions

View File

@@ -14,7 +14,9 @@
{
"path": "pages/index/index",
"style": {
"navigationStyle": "custom"
"navigationStyle": "custom",
"enablePullDownRefresh": false,
"disableScroll": false
}
},
{
@@ -35,6 +37,12 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/drama/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/voice/clone",
"style": {
@@ -56,6 +64,19 @@
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/recharge/recharge",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/recharge/history",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": true
}
}
],
"globalStyle": {
@@ -65,14 +86,13 @@
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#999999",
"selectedColor": "#ff9800",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"color": "rgba(255, 255, 255, 0.5)",
"selectedColor": "#f9e076",
"backgroundColor": "rgba(26, 11, 46, 0.95)",
"borderStyle": "white",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
},
{

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,24 @@
<template>
<view class="container">
<view class="floral-container">
<!-- 夜空装饰背景 -->
<view class="night-sky-decoration">
<!-- 星星装饰 - 小黄点 -->
<view class="star star-1"></view>
<view class="star star-2"></view>
<view class="star star-3"></view>
<view class="star star-4"></view>
<view class="star star-5"></view>
<view class="star star-6"></view>
<view class="star star-7"></view>
<view class="star star-8"></view>
<view class="star star-9"></view>
<view class="star star-10"></view>
</view>
<!-- 顶部自定义导航栏 -->
<view class="custom-navbar fixed-navbar">
<view class="navbar-left" @click="goBack">
<text class="back-icon"></text>
</view>
<view class="navbar-left" @tap="goBack"> 返回</view>
<text class="navbar-title">创建</text>
<view class="navbar-right"></view>
</view>
<!-- 创建选项区域 -->
@@ -85,71 +97,208 @@ const showLoginTip = () => {
</script>
<style scoped>
/* 夜空主题容器 */
.floral-container {
width: 100vw;
min-height: 100vh;
background: linear-gradient(135deg, #1a0b2e 0%, #4a1e6d 25%, #6b2c9c 50%, #8a2be2 75%, #4b0082 100%);
position: relative;
overflow-x: hidden;
padding-bottom: 120rpx;
padding-top: 100rpx;
display: flex;
flex-direction: column;
}
.fixed-navbar {
position: fixed;
top: 0;
left: 0;
width: 100vw;
z-index: 100;
}
.container {
background: #fff9f2;
min-height: 100vh;
padding-top: 100rpx;
padding-bottom: 40rpx;
z-index: 1002;
}
.custom-navbar {
height: 100rpx;
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(90deg, #ffe5c2, #fff6e5);
padding: 0 30rpx;
}
.navbar-left, .navbar-right {
width: 60rpx;
display: flex;
align-items: center;
}
.back-icon {
font-size: 36rpx;
justify-content: center;
background: linear-gradient(135deg, rgba(26, 11, 46, 0.95) 0%, rgba(74, 30, 109, 0.95) 25%, rgba(107, 44, 156, 0.95) 50%, rgba(138, 43, 226, 0.95) 75%, rgba(75, 0, 130, 0.95) 100%);
backdrop-filter: blur(10px);
border-bottom: 1rpx solid rgba(251, 191, 36, 0.3);
font-weight: bold;
color: #333;
}
.navbar-title {
color: #333;
font-weight: bold;
font-size: 38rpx;
font-size: 32rpx;
letter-spacing: 2rpx;
position: relative;
}
.navbar-left {
position: absolute;
left: 24rpx;
color: #f9e076;
font-size: 28rpx;
font-weight: 500;
}
.navbar-title {
color: #ffffff;
text-shadow: 0 0 10px rgba(249, 224, 118, 0.5);
}
/* 创建选项区域 */
.create-options {
padding: 40rpx 30rpx;
z-index: 2;
position: relative;
}
.option-card {
display: flex;
align-items: center;
background: #fff;
border-radius: 20rpx;
background: rgba(255, 255, 255, 0.9);
border: 2rpx solid rgba(138, 43, 226, 0.3);
border-radius: 25rpx;
box-shadow: 0 8rpx 30rpx rgba(138, 43, 226, 0.15);
padding: 40rpx 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.option-card:hover {
transform: translateY(-4rpx);
box-shadow: 0 12rpx 40rpx rgba(138, 43, 226, 0.2);
border-color: #8a2be2;
}
.option-icon {
font-size: 60rpx;
margin-right: 30rpx;
filter: drop-shadow(0 2rpx 4rpx rgba(138, 43, 226, 0.3));
}
.option-content {
flex: 1;
display: flex;
flex-direction: column;
}
.option-title {
font-size: 34rpx;
font-weight: bold;
margin-bottom: 10rpx;
color: #333;
}
.option-desc {
font-size: 28rpx;
color: #999;
color: #666;
font-style: italic;
}
/* 夜空装饰背景 */
.night-sky-decoration {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
pointer-events: none;
}
/* 星星样式 - 小黄点 */
.star {
position: absolute;
width: 6px;
height: 6px;
background: #f9e076;
border-radius: 50%;
animation: star-twinkle 3s ease-in-out infinite;
box-shadow: 0 0 8px rgba(249, 224, 118, 0.8);
}
.star-1 {
top: 80px;
right: 40px;
animation-delay: 0s;
}
.star-2 {
top: 120px;
right: 80px;
animation-delay: 0.5s;
}
.star-3 {
top: 200px;
right: 20px;
animation-delay: 1s;
}
.star-4 {
top: 300px;
right: 60px;
animation-delay: 1.5s;
}
.star-5 {
top: 150px;
left: 30px;
animation-delay: 2s;
}
.star-6 {
top: 250px;
left: 60px;
animation-delay: 2.5s;
}
.star-7 {
top: 350px;
left: 20px;
animation-delay: 3s;
}
.star-8 {
top: 400px;
right: 100px;
animation-delay: 3.5s;
}
.star-9 {
top: 60px;
left: 80px;
animation-delay: 4s;
}
.star-10 {
top: 180px;
right: 120px;
animation-delay: 4.5s;
}
/* 动画效果 */
@keyframes star-twinkle {
0%, 100% {
opacity: 0.3;
transform: scale(0.8);
box-shadow: 0 0 4px rgba(249, 224, 118, 0.4);
}
25% {
opacity: 0.7;
transform: scale(1.1);
box-shadow: 0 0 8px rgba(249, 224, 118, 0.7);
}
50% {
opacity: 1;
transform: scale(1.3);
box-shadow: 0 0 12px rgba(249, 224, 118, 1);
}
75% {
opacity: 0.8;
transform: scale(1.1);
box-shadow: 0 0 8px rgba(249, 224, 118, 0.8);
}
}
</style>

387
src/pages/drama/index.vue Normal file
View File

@@ -0,0 +1,387 @@
<template>
<view class="floral-container">
<!-- 顶部自定义导航栏 -->
<view class="custom-navbar fixed-navbar">
<view class="navbar-left" @tap="goBack"> 返回</view>
<text class="navbar-title">选择剧情</text>
</view>
<!-- 可滚动的两列卡片区 -->
<scroll-view
class="scroll-container"
scroll-y="true"
:show-scrollbar="false"
>
<view class="two-column-grid">
<view class="column column-left">
<block v-for="(item, idx) in leftColumnItems" :key="item ? item.id : idx">
<view class="floral-grid-card" v-if="item">
<image class="cover" :src="item.cover" mode="aspectFit" />
<view class="floral-tag">{{ item.tag }}</view>
<view class="content-area">
<view class="title">{{ item.title }}</view>
<view class="card-bottom">
<button v-if="userStore.isLoggedIn" class="floral-btn use-btn" @click="showDetail(item)">🔍 查看详情</button>
<button v-else class="floral-btn outline use-btn login-required" @click="showLoginTip">🔐 查看详情</button>
</view>
</view>
</view>
</block>
</view>
<view class="column column-right">
<block v-for="(item, idx) in rightColumnItems" :key="item ? item.id : idx">
<view class="floral-grid-card" v-if="item">
<image class="cover" :src="item.cover" mode="aspectFit" />
<view class="floral-tag">{{ item.tag }}</view>
<view class="content-area">
<view class="title">{{ item.title }}</view>
<view class="card-bottom">
<button v-if="userStore.isLoggedIn" class="floral-btn use-btn" @click="showDetail(item)">🔍 查看详情</button>
<button v-else class="floral-btn outline use-btn login-required" @click="showLoginTip">🔐 查看详情</button>
</view>
</view>
</view>
</block>
</view>
</view>
<view class="bottom-spacing"></view>
</scroll-view>
<!-- 登录提示弹窗 -->
<view class="floral-modal" v-if="showLoginModal">
<view class="floral-modal-content">
<view class="modal-title">💝 温馨提示</view>
<view class="modal-text">请先登录后再开始您的创意之旅</view>
<view class="modal-btns">
<button class="floral-btn outline modal-btn cancel" @click="showLoginModal = false">稍后再说</button>
<button class="floral-btn modal-btn confirm" @click="goToLogin">🌸 去登录</button>
</view>
</view>
</view>
<!-- 角色详情弹窗 -->
<view class="detail-modal" v-if="showDetailModal" @click="closeDetail">
<view class="detail-modal-content" @click.stop>
<view class="detail-cover-container">
<image
class="detail-cover"
:src="selectedItem?.cover"
mode="aspectFit"
:class="{'cover-zoomed': showDetailModal}"
/>
</view>
<view class="detail-info" :class="{'info-visible': showDetailModal}">
<view class="detail-tag">{{ selectedItem?.tag }}</view>
<view class="detail-title">{{ selectedItem?.title }}</view>
<scroll-view class="detail-description" scroll-y="true">
<view class="description-text">
{{ selectedItem?.title }} - 这是一个充满魅力的角色拥有独特的个性和丰富的故事背景在这里您可以与这个角色进行深入的对话体验不同的情感交流
</view>
</scroll-view>
<view class="detail-actions">
<button class="floral-btn outline detail-btn cancel" @click="closeDetail">取消</button>
<button class="floral-btn detail-btn confirm" @click="useFromDetail">💝 去使用</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useUserStore } from '@/stores/user.js';
const userStore = useUserStore();
const showLoginModal = ref(false);
// 详情弹窗相关
const showDetailModal = ref(false);
const selectedItem = ref(null);
// 数据:剧情列表
const dramaList = ref([
{
id: 1,
cover: '/static/bailing.jpg',
tag: '浪漫',
title: '白领女友的温柔早安',
roleId: 1,
roleName: '小何',
roleDesc: '台湾女孩,高情商智能助手',
avatar: '/static/bailing.jpg',
greeting: '你好~我是小何,一个高情商的台湾女孩,很高兴认识你!有什么想聊的吗?'
},
{
id: 2,
cover: '/static/haomen.jpg',
tag: '优雅',
title: '豪门女主的秘密生活',
roleId: 2,
roleName: '小于',
roleDesc: '咖啡店兼职店员,热情友好',
avatar: '/static/haomen.jpg',
greeting: '你好!我是小于,咖啡店的新店员,今天想喝点什么吗?'
},
{
id: 3,
cover: '/static/waimai.jpg',
tag: '活泼',
title: '外卖小姐姐的贴心问候',
roleId: 3,
roleName: '李老师',
roleDesc: '中学图书馆管理员,温文尔雅',
avatar: '/static/waimai.jpg',
greeting: '你好,我是李老师,图书馆管理员。有什么想了解的书吗?'
},
{
id: 4,
cover: '/static/tunvlang.jpg',
tag: '深情',
title: '因爱执著的少女',
roleId: 4,
roleName: '小何',
roleDesc: '另一个小何角色',
avatar: '/static/tunvlang.jpg',
greeting: '你好~我是小何,很高兴为你服务!'
},
// {
// id: 5,
// cover: '/static/logo.png',
// tag: '温暖',
// title: '邻家女孩的暖心故事',
// roleId: 5,
// roleName: '温衡',
// roleDesc: '职场女上司,成熟强势',
// avatar: '/static/logo.png',
// greeting: '我是温衡,你的上司。今天的工作完成了吗?'
// },
// {
// id: 6,
// cover: '/static/logo.png',
// tag: '梦幻',
// title: '公主的浪漫邂逅',
// roleId: 6,
// roleName: '职场上司',
// roleDesc: '另一个温衡角色',
// avatar: '/static/logo.png',
// greeting: '我是你的上司,有什么需要汇报的吗?'
// },
// { id: 7, cover: '/static/logo.png', tag: '治愈', title: '咖啡店的午后时光' },
// { id: 8, cover: '/static/logo.png', tag: '青春', title: '校园里的美好回忆' },
]);
// 列数据拆分
const leftColumnItems = computed(() => dramaList.value.filter((_, index) => index % 2 === 0));
const rightColumnItems = computed(() => dramaList.value.filter((_, index) => index % 2 === 1));
onMounted(() => {
userStore.init();
});
// 方法
const handleUse = (item) => {
if (!item || !item.id) {
uni.showToast({ title: '角色信息无效', icon: 'none' });
return;
}
uni.showLoading({ title: '正在设置角色...' });
if (item.roleId) {
uni.hideLoading();
const params = {
characterId: item.id,
roleId: item.roleId,
roleName: item.roleName || item.title,
roleDesc: item.roleDesc,
avatar: item.avatar || item.cover,
greeting: item.greeting
};
const queryString = Object.keys(params).map(key => `${key}=${encodeURIComponent(params[key] || '')}`).join('&');
uni.navigateTo({ url: `/pages/chat/chat?${queryString}` });
} else {
uni.hideLoading();
uni.navigateTo({ url: `/pages/chat/chat?characterId=${item.id}` });
}
};
const showLoginTip = () => { showLoginModal.value = true; };
const showDetail = (item) => { selectedItem.value = item; showDetailModal.value = true; };
const closeDetail = () => { showDetailModal.value = false; selectedItem.value = null; };
const useFromDetail = () => { if (selectedItem.value) { const v = selectedItem.value; closeDetail(); handleUse(v); } };
const goToLogin = () => { showLoginModal.value = false; uni.switchTab({ url: '/pages/mine/mine' }); };
const goBack = () => { uni.navigateBack(); };
</script>
<style scoped>
.floral-container {
width: 100vw;
min-height: 100vh;
background: linear-gradient(135deg, #1a0b2e 0%, #4a1e6d 25%, #6b2c9c 50%, #8a2be2 75%, #4b0082 100%);
position: relative;
overflow-x: hidden;
padding-bottom: 120rpx;
padding-top: 100rpx;
display: flex;
flex-direction: column;
}
.fixed-navbar {
position: fixed;
top: 0;
left: 0;
width: 100vw;
z-index: 1002;
}
.custom-navbar {
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, rgba(26, 11, 46, 0.95) 0%, rgba(74, 30, 109, 0.95) 25%, rgba(107, 44, 156, 0.95) 50%, rgba(138, 43, 226, 0.95) 75%, rgba(75, 0, 130, 0.95) 100%);
border-bottom: 1rpx solid rgba(251, 191, 36, 0.3);
font-weight: bold;
font-size: 32rpx;
position: relative;
color: #fff;
}
.navbar-left {
position: absolute;
left: 24rpx;
color: #f9e076;
font-size: 28rpx;
}
.navbar-title {
color: #ffffff;
}
.scroll-container {
flex: 1;
min-height: 0;
margin: 24rpx;
border-radius: 20rpx;
}
.two-column-grid {
display: flex;
gap: 20rpx;
}
.column {
flex: 1;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.floral-grid-card {
background: rgba(255, 255, 255, 0.9);
border: 2rpx solid rgba(138, 43, 226, 0.3);
border-radius: 25rpx;
box-shadow: 0 8rpx 30rpx rgba(138, 43, 226, 0.15);
overflow: hidden;
display: flex;
flex-direction: column;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.cover {
width: 100%;
height: 320rpx;
display: block;
}
.floral-tag {
position: absolute;
margin: 12rpx;
background: linear-gradient(135deg, #f9e076, #f59e0b);
color: #4b0082;
padding: 6rpx 12rpx;
border-radius: 16rpx;
font-size: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(245, 158, 11, 0.3);
}
.content-area { padding: 16rpx; }
.title { font-size: 28rpx; color: #333; font-weight: bold; }
.card-bottom { margin-top: 8rpx; display: flex; justify-content: flex-end; }
.floral-btn {
background: linear-gradient(135deg, #8a2be2, #6b2c9c);
color: #fff;
border: none;
border-radius: 25rpx;
font-size: 26rpx;
padding: 12rpx 28rpx;
box-shadow: 0 4rpx 15rpx rgba(138, 43, 226, 0.3);
transition: all 0.3s ease;
}
.floral-btn.outline {
background: transparent;
color: #8a2be2;
border: 2rpx solid #8a2be2;
}
.floral-grid-card:hover {
transform: translateY(-4rpx);
box-shadow: 0 12rpx 40rpx rgba(138, 43, 226, 0.2);
border-color: #8a2be2;
}
.use-btn { font-size: 24rpx; }
.bottom-spacing { height: 40rpx; }
/* 登录弹窗 */
.floral-modal {
position: fixed;
left: 0; right: 0; top: 0; bottom: 0;
background: rgba(0,0,0,0.3);
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
}
.floral-modal-content {
width: 80vw;
background: #fff;
border-radius: 20rpx;
padding: 24rpx;
}
.modal-title { font-size: 30rpx; font-weight: bold; margin-bottom: 12rpx; }
.modal-text { font-size: 24rpx; color: #666; margin-bottom: 16rpx; }
.modal-btns { display: flex; gap: 20rpx; justify-content: flex-end; }
/* 详情弹窗 */
.detail-modal {
position: fixed;
left: 0; right: 0; top: 0; bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 2100;
display: flex;
align-items: center;
justify-content: center;
}
.detail-modal-content {
width: 86vw;
max-height: 80vh;
background: #ffffff;
border-radius: 16rpx;
overflow: hidden;
}
.detail-cover-container { width: 100%; height: 320rpx; overflow: hidden; }
.detail-cover { width: 100%; height: 100%; }
.detail-info { padding: 20rpx; }
.detail-tag { font-size: 22rpx; color: #8a2be2; margin-bottom: 8rpx; }
.detail-title { font-size: 32rpx; font-weight: 700; color: #333; margin-bottom: 12rpx; }
.detail-description { max-height: 200rpx; }
.description-text { font-size: 24rpx; color: #555; line-height: 1.6; }
.detail-actions { margin-top: 12rpx; display: flex; justify-content: flex-end; gap: 20rpx; }
.detail-btn { padding: 10rpx 22rpx; }
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,63 +1,100 @@
<template>
<view class="container">
<!-- 头部以上背景区域 -->
<view class="top-background"></view>
<!-- 顶部自定义导航栏 -->
<view class="custom-navbar fixed-navbar">
<text class="navbar-title">我的</text>
</view>
<!-- 用户信息区域 -->
<view class="user-info">
<image class="avatar" :src="avatarUrl" mode="aspectFill"></image>
<view class="user-details" v-if="isLoggedIn">
<text class="login-text">{{ nickName }}</text>
<text class="user-id">ID: {{ openid }}</text>
</view>
<view class="user-details" v-else>
<text class="login-text">请登录</text>
<text class="user-id">ID:</text>
<button class="login-btn" @click="handleLogin">{{ loginButtonText }}</button>
</view>
</view>
<!-- 菜单卡片 -->
<view class="menu-cards">
<!-- 用户须知 -->
<view class="menu-card" @click="showAgreement">
<view class="card-icon notice-icon">🔊</view>
<text class="card-title">用户须知</text>
<!-- 可滚动内容区域 -->
<scroll-view
class="scroll-container"
scroll-y="true"
enable-back-to-top="false"
refresher-enabled="false"
:refresher-triggered="false"
:refresher-threshold="0"
:show-scrollbar="false"
:scroll-with-animation="false"
:bounces="false"
:always-bounce-vertical="false"
:scroll-top="0"
:upper-threshold="0"
:lower-threshold="0"
>
<!-- 用户信息区域 -->
<view class="user-info">
<image class="avatar" :src="avatarUrl" mode="aspectFill"></image>
<view class="user-details" v-if="isLoggedIn">
<text class="login-text">{{ nickName }}</text>
<text class="user-id">ID: {{ openid }}</text>
</view>
<view class="user-details" v-else>
<text class="login-text">请登录</text>
<text class="user-id">ID:</text>
<button class="login-btn" @click="handleLogin">{{ loginButtonText }}</button>
</view>
</view>
<!-- 用户设备 -->
<view class="menu-card">
<view class="card-icon device-icon">📦</view>
<text class="card-title">用户设备</text>
<!-- 余额显示 -->
<view class="balance-section" v-if="isLoggedIn">
<view class="balance-card">
<view class="balance-info">
<view class="balance-label">账户余额</view>
<view class="balance-amount">¥{{ userBalance }}</view>
</view>
<view class="balance-actions">
<button class="recharge-btn" @click="goToRecharge">充值</button>
<button class="history-btn" @click="goToHistory">记录</button>
</view>
</view>
</view>
</view>
<!-- 剧情角色区块 -->
<view class="section-block">
<view class="section-header">
<text class="section-title">剧情角色</text>
<text class="view-all">查看全部</text>
<!-- 菜单卡片 -->
<view class="menu-cards">
<!-- 用户须知 -->
<view class="menu-card" @click="showAgreement">
<view class="card-icon notice-icon">🔊</view>
<text class="card-title">用户须知</text>
</view>
<!-- 用户设备 -->
<view class="menu-card">
<view class="card-icon device-icon">📦</view>
<text class="card-title">用户设备</text>
</view>
</view>
</view>
<!-- 克隆声音区块 -->
<view class="section-block">
<view class="section-header">
<text class="section-title">克隆声音</text>
<text class="view-all">查看全部</text>
<!-- 剧情角色区块 -->
<view class="section-block">
<view class="section-header">
<text class="section-title">剧情角色</text>
<text class="view-all">查看全部</text>
</view>
</view>
</view>
<!-- 退出登录按钮仅登录后显示 -->
<view class="logout-section">
<button class="logout-btn" @click="handleLogout">退出登录</button>
<button class="logout-btn" style="margin-top: 20rpx; background-color: #ff9800; color: white;" @click="testDirectApiCall">直接测试API</button>
<text style="display: block; margin-top: 20rpx; text-align: center; color: #999;">
登录状态: {{ isLoggedIn ? '已登录' : '未登录' }}
</text>
</view>
<!-- 克隆声音区块 -->
<view class="section-block">
<view class="section-header">
<text class="section-title">克隆声音</text>
<text class="view-all">查看全部</text>
</view>
</view>
<!-- 退出登录按钮仅登录后显示 -->
<view class="logout-section">
<button class="logout-btn" @click="handleLogout">退出登录</button>
<button class="logout-btn" style="margin-top: 20rpx; background-color: #ff9800; color: white;" @click="testDirectApiCall">直接测试API</button>
<text style="display: block; margin-top: 20rpx; text-align: center; color: #999;">
登录状态: {{ isLoggedIn ? '已登录' : '未登录' }}
</text>
</view>
<!-- 底部留白避免被tabbar遮挡 -->
<view class="bottom-spacing"></view>
</scroll-view>
<!-- 用户须知弹窗 -->
<user-agreement
@@ -71,6 +108,7 @@
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useUserStore } from '@/stores/user.js';
import { rechargeAPI } from '@/utils/api.js';
import UserAgreement from '@/components/UserAgreement.vue';
// 状态管理 - 避免直接依赖store的响应式
@@ -82,6 +120,7 @@ const isLoggedIn = ref(false);
const nickName = ref('');
const openid = ref('');
const avatarUrl = ref('/static/default-avatar.png');
const userBalance = ref(0.00);
// 登录按钮文本
const loginButtonText = computed(() => {
@@ -128,15 +167,13 @@ onMounted(() => {
userStore.init();
// 初始化本地状态
initUserInfo();
// 加载用户余额
loadUserBalance();
});
// 登录处理
const handleLogin = async () => {
try {
// 先清理假登录数据
console.log('开始登录,先清理假登录数据...');
userStore.clearFakeLoginData();
uni.showLoading({
title: '登录中...'
});
@@ -156,18 +193,11 @@ const handleLogin = async () => {
uni.hideLoading();
console.error('Login failed:', error);
let errorMessage = '登录失败';
if (error.message) {
errorMessage = error.message;
} else if (typeof error === 'string') {
errorMessage = error;
}
// 显示详细的错误信息
uni.showModal({
// 登录失败
uni.showToast({
title: '登录失败',
content: `错误信息:${errorMessage}\n\n请检查\n1. 网络连接是否正常\n2. 后端服务是否运行\n3. 微信小程序配置是否正确`,
showCancel: false
icon: 'none',
duration: 2000
});
}
};
@@ -339,7 +369,7 @@ const testDirectApiCall = () => {
showCancel: false,
success: () => {
uni.request({
url: 'http://8.145.52.111:8084/app/logout',
url: 'https://www.aixsy.com.cn/app/logout',
method: 'POST',
header: {
'Content-Type': 'application/json'
@@ -376,11 +406,11 @@ Token: ${currentToken || '无'}
showCancel: false,
success: () => {
uni.request({
url: 'http://8.145.52.111:8084/app/logout',
url: 'https://www.aixsy.com.cn/app/logout',
method: 'POST',
header: {
'Content-Type': 'application/json',
'Authorization': currentToken || ''
'Authorization': currentToken ? (currentToken.startsWith('Bearer ') ? currentToken : 'Bearer ' + currentToken) : ''
},
complete: (res) => {
uni.showModal({
@@ -399,7 +429,7 @@ Token: ${currentToken || '无'}
content: `尝试带token参数的URL`,
showCancel: false,
success: () => {
const url = `http://8.145.52.111:8084/app/logout?token=${currentToken || ''}`;
const url = `https://www.aixsy.com.cn/app/logout?token=${currentToken || ''}`;
uni.request({
url: url,
method: 'POST',
@@ -423,34 +453,86 @@ Token: ${currentToken || '无'}
}
});
};
// 加载用户余额
const loadUserBalance = async () => {
if (!isLoggedIn.value) return;
try {
const result = await rechargeAPI.getUserBalance();
if (result.success) {
userBalance.value = result.data.balance || 0;
}
} catch (error) {
console.error('获取用户余额失败:', error);
}
};
// 跳转到充值页面
const goToRecharge = () => {
uni.navigateTo({
url: '/pages/recharge/recharge'
});
};
// 跳转到充值记录页面
const goToHistory = () => {
uni.navigateTo({
url: '/pages/recharge/history'
});
};
</script>
<style scoped>
.fixed-navbar {
position: fixed;
top: 0;
top: 100rpx;
left: 0;
width: 100vw;
z-index: 100;
}
.container {
background: #fff9f2;
background: linear-gradient(135deg, #1a0b2e 0%, #4a1e6d 25%, #6b2c9c 50%, #8a2be2 75%, #4b0082 100%);
min-height: 100vh;
padding-top: 100rpx;
padding-top: 200rpx;
padding-bottom: 120rpx;
}
/* 滚动容器样式 */
.scroll-container {
height: calc(100vh - 200rpx);
z-index: 2;
position: relative;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(5px);
overflow: hidden;
}
/* 头部以上背景区域 */
.top-background {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100rpx;
background: linear-gradient(135deg, #1a0b2e 0%, #4a1e6d 25%, #6b2c9c 50%, #8a2be2 75%, #4b0082 100%);
z-index: 99;
}
.custom-navbar {
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
background: #fff9f2;
background: linear-gradient(135deg, rgba(26, 11, 46, 0.95) 0%, rgba(74, 30, 109, 0.95) 25%, rgba(107, 44, 156, 0.95) 50%, rgba(138, 43, 226, 0.95) 75%, rgba(75, 0, 130, 0.95) 100%);
backdrop-filter: blur(10px);
border-bottom: 1rpx solid rgba(251, 191, 36, 0.3);
font-weight: bold;
font-size: 38rpx;
letter-spacing: 2rpx;
}
.navbar-title {
color: #333;
color: #ffffff;
text-shadow: 0 0 10px rgba(251, 191, 36, 0.5);
}
/* 用户信息区域 */
@@ -458,7 +540,55 @@ Token: ${currentToken || '无'}
display: flex;
align-items: center;
padding: 32rpx;
background: #fff9f2;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
}
/* 余额区域 */
.balance-section {
padding: 0 24rpx 24rpx;
}
.balance-card {
background: linear-gradient(135deg, #8a2be2 0%, #6b2c9c 100%);
border-radius: 20rpx;
padding: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
color: #fff;
box-shadow: 0 8rpx 30rpx rgba(138, 43, 226, 0.3);
}
.balance-info .balance-label {
font-size: 28rpx;
opacity: 0.9;
margin-bottom: 8rpx;
}
.balance-info .balance-amount {
font-size: 48rpx;
font-weight: bold;
}
.balance-actions {
display: flex;
gap: 20rpx;
}
.balance-actions .recharge-btn,
.balance-actions .history-btn {
background: rgba(255, 255, 255, 0.2);
color: #fff;
border: 1rpx solid rgba(255, 255, 255, 0.3);
border-radius: 30rpx;
padding: 16rpx 24rpx;
font-size: 26rpx;
min-width: 100rpx;
}
.balance-actions .recharge-btn {
background: rgba(255, 255, 255, 0.3);
}
.avatar {
width: 140rpx;
@@ -501,15 +631,18 @@ Token: ${currentToken || '无'}
display: flex;
align-items: center;
padding: 40rpx 30rpx;
background: #fff;
background: rgba(255, 255, 255, 0.9);
border: 2rpx solid rgba(138, 43, 226, 0.3);
border-radius: 20rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05);
box-shadow: 0 6rpx 25rpx rgba(138, 43, 226, 0.15);
backdrop-filter: blur(8px);
}
.card-icon {
font-size: 48rpx;
color: #ff9800;
color: #8a2be2;
margin-right: 24rpx;
filter: drop-shadow(0 2rpx 4rpx rgba(138, 43, 226, 0.3));
}
.card-title {
font-size: 32rpx;
@@ -518,11 +651,14 @@ Token: ${currentToken || '无'}
/* 区块样式 */
.section-block {
background: #fff;
background: rgba(255, 255, 255, 0.9);
border: 2rpx solid rgba(138, 43, 226, 0.3);
border-radius: 20rpx;
margin: 24rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 6rpx 25rpx rgba(138, 43, 226, 0.15);
backdrop-filter: blur(8px);
}
.section-header {
display: flex;
@@ -544,9 +680,17 @@ Token: ${currentToken || '无'}
}
.logout-btn {
width: 100%;
background-color: #f5f5f5;
color: #666;
background: rgba(255, 255, 255, 0.9);
color: #8a2be2;
font-size: 30rpx;
border-radius: 8rpx;
border: 2rpx solid rgba(138, 43, 226, 0.3);
border-radius: 20rpx;
box-shadow: 0 4rpx 15rpx rgba(138, 43, 226, 0.2);
backdrop-filter: blur(8px);
}
/* 底部留白 */
.bottom-spacing {
height: 40rpx;
}
</style>

View File

@@ -0,0 +1,520 @@
<template>
<view class="history-container">
<!-- 顶部导航 -->
<view class="nav-bar">
<view class="nav-content">
<view class="nav-left" @click="goBack">
<text class="nav-icon"></text>
</view>
<view class="nav-title">充值记录</view>
<view class="nav-right"></view>
</view>
</view>
<!-- 筛选条件 -->
<view class="filter-section">
<view class="filter-tabs">
<view
v-for="(tab, index) in filterTabs"
:key="index"
class="filter-tab"
:class="{ active: currentFilter === tab.value }"
@click="selectFilter(tab.value)"
>
{{ tab.label }}
</view>
</view>
</view>
<!-- 记录列表 -->
<view class="record-list">
<view v-if="isLoading && recordList.length === 0" class="loading-state">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="recordList.length === 0" class="empty-state">
<view class="empty-icon">📝</view>
<text class="empty-text">暂无充值记录</text>
<button class="recharge-btn" @click="goToRecharge">立即充值</button>
</view>
<view v-else>
<view
v-for="(record, index) in recordList"
:key="index"
class="record-item"
>
<view class="record-header">
<view class="record-amount">
<text class="amount-symbol">+</text>
<text class="amount-value">¥{{ record.amount }}</text>
</view>
<view class="record-status" :class="record.status">
{{ getStatusText(record.status) }}
</view>
</view>
<view class="record-details">
<view class="detail-item">
<text class="detail-label">订单号</text>
<text class="detail-value">{{ record.orderId }}</text>
</view>
<view class="detail-item">
<text class="detail-label">支付方式</text>
<text class="detail-value">{{ getPaymentMethodText(record.paymentMethod) }}</text>
</view>
<view class="detail-item">
<text class="detail-label">充值时间</text>
<text class="detail-value">{{ formatTime(record.createTime) }}</text>
</view>
<view class="detail-item" v-if="record.bonusAmount > 0">
<text class="detail-label">赠送金额</text>
<text class="detail-value bonus">+¥{{ record.bonusAmount }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<view v-if="hasMore && !isLoading" class="load-more" @click="loadMore">
<text class="load-more-text">加载更多</text>
</view>
<view v-if="isLoading && recordList.length > 0" class="loading-more">
<view class="loading-spinner small"></view>
<text class="loading-text">加载中...</text>
</view>
</view>
</template>
<script>
import { rechargeAPI } from '@/utils/api.js'
export default {
name: 'RechargeHistoryPage',
data() {
return {
recordList: [], // 充值记录列表
currentFilter: 'all', // 当前筛选条件
isLoading: false, // 是否正在加载
hasMore: true, // 是否还有更多数据
currentPage: 1, // 当前页码
pageSize: 10, // 每页数量
// 筛选选项
filterTabs: [
{ label: '全部', value: 'all' },
{ label: '成功', value: 'success' },
{ label: '失败', value: 'failed' },
{ label: '处理中', value: 'pending' }
]
}
},
onLoad() {
this.loadRecordList()
},
onPullDownRefresh() {
this.refreshData()
},
onReachBottom() {
if (this.hasMore && !this.isLoading) {
this.loadMore()
}
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack()
},
// 跳转到充值页面
goToRecharge() {
uni.navigateTo({
url: '/pages/recharge/recharge'
})
},
// 选择筛选条件
selectFilter(filter) {
if (this.currentFilter === filter) return
this.currentFilter = filter
this.refreshData()
},
// 刷新数据
async refreshData() {
this.currentPage = 1
this.hasMore = true
this.recordList = []
await this.loadRecordList()
// 停止下拉刷新
uni.stopPullDownRefresh()
},
// 加载更多
async loadMore() {
if (!this.hasMore || this.isLoading) return
this.currentPage++
await this.loadRecordList()
},
// 加载充值记录列表
async loadRecordList() {
if (this.isLoading) return
this.isLoading = true
try {
const params = {
page: this.currentPage,
pageSize: this.pageSize
}
// 添加状态筛选
if (this.currentFilter !== 'all') {
params.status = this.currentFilter
}
const result = await rechargeAPI.getRechargeHistory(params)
if (result.success) {
const newRecords = result.data.records || []
if (this.currentPage === 1) {
this.recordList = newRecords
} else {
this.recordList = [...this.recordList, ...newRecords]
}
// 判断是否还有更多数据
this.hasMore = newRecords.length === this.pageSize
} else {
uni.showToast({
title: result.error?.message || '加载失败',
icon: 'none'
})
}
} catch (error) {
console.error('加载充值记录失败:', error)
uni.showToast({
title: '加载失败,请重试',
icon: 'none'
})
} finally {
this.isLoading = false
}
},
// 获取状态文本
getStatusText(status) {
const statusMap = {
'success': '充值成功',
'failed': '充值失败',
'pending': '处理中',
'cancelled': '已取消'
}
return statusMap[status] || '未知状态'
},
// 获取支付方式文本
getPaymentMethodText(method) {
const methodMap = {
'wechat': '微信支付',
'alipay': '支付宝',
'bank': '银行卡'
}
return methodMap[method] || '未知方式'
},
// 格式化时间
formatTime(timestamp) {
if (!timestamp) return ''
const date = new Date(timestamp)
const now = new Date()
const diff = now - date
// 小于1分钟
if (diff < 60000) {
return '刚刚'
}
// 小于1小时
if (diff < 3600000) {
return Math.floor(diff / 60000) + '分钟前'
}
// 小于1天
if (diff < 86400000) {
return Math.floor(diff / 3600000) + '小时前'
}
// 超过1天显示具体日期
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hour = String(date.getHours()).padStart(2, '0')
const minute = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}`
}
}
}
</script>
<style lang="scss" scoped>
.history-container {
min-height: 100vh;
background-color: #f5f5f5;
}
// 导航栏
.nav-bar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding-top: var(--status-bar-height);
.nav-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 30rpx;
.nav-left {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
.nav-icon {
font-size: 40rpx;
color: #fff;
font-weight: bold;
}
}
.nav-title {
font-size: 36rpx;
font-weight: 600;
color: #fff;
}
.nav-right {
width: 60rpx;
}
}
}
// 筛选条件
.filter-section {
background: #fff;
padding: 30rpx;
border-bottom: 1rpx solid #e5e5e5;
.filter-tabs {
display: flex;
gap: 20rpx;
.filter-tab {
padding: 16rpx 32rpx;
background: #f5f5f5;
border-radius: 40rpx;
font-size: 28rpx;
color: #666;
transition: all 0.3s ease;
&.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}
}
}
}
// 记录列表
.record-list {
padding: 30rpx;
.loading-state,
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #e5e5e5;
border-top: 4rpx solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20rpx;
}
.loading-text {
font-size: 28rpx;
color: #999;
}
}
.empty-state {
.empty-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
}
.empty-text {
font-size: 32rpx;
color: #999;
margin-bottom: 40rpx;
}
.recharge-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
border: none;
border-radius: 40rpx;
padding: 20rpx 40rpx;
font-size: 28rpx;
}
}
.record-item {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
.record-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.record-amount {
display: flex;
align-items: baseline;
.amount-symbol {
font-size: 32rpx;
color: #07c160;
font-weight: bold;
margin-right: 4rpx;
}
.amount-value {
font-size: 40rpx;
color: #07c160;
font-weight: bold;
}
}
.record-status {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
&.success {
background: #e8f5e8;
color: #07c160;
}
&.failed {
background: #ffe8e8;
color: #ff4757;
}
&.pending {
background: #fff3cd;
color: #ffc107;
}
}
}
.record-details {
.detail-item {
display: flex;
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
.detail-label {
font-size: 26rpx;
color: #999;
width: 140rpx;
flex-shrink: 0;
}
.detail-value {
font-size: 26rpx;
color: #333;
flex: 1;
&.bonus {
color: #ff6b6b;
}
}
}
}
}
}
// 加载更多
.load-more {
text-align: center;
padding: 30rpx;
.load-more-text {
font-size: 28rpx;
color: #667eea;
}
}
.loading-more {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx;
.loading-spinner {
width: 40rpx;
height: 40rpx;
border: 3rpx solid #e5e5e5;
border-top: 3rpx solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 20rpx;
&.small {
width: 30rpx;
height: 30rpx;
border-width: 2rpx;
}
}
.loading-text {
font-size: 26rpx;
color: #999;
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>

View File

@@ -0,0 +1,719 @@
<template>
<view class="recharge-container">
<!-- 顶部导航 -->
<view class="nav-bar">
<view class="nav-content">
<view class="nav-left" @click="goBack">
<text class="nav-icon"></text>
</view>
<view class="nav-title">会员充值</view>
<view class="nav-right"></view>
</view>
</view>
<!-- 用户余额显示 -->
<view class="balance-section">
<view class="balance-card">
<view class="balance-label">当前余额</view>
<view class="balance-amount">¥{{ userBalance }}</view>
<view class="balance-tip">充值后余额可用于AI对话和语音服务</view>
</view>
</view>
<!-- 充值金额选择 -->
<view class="amount-section">
<view class="section-title">选择充值金额</view>
<view class="amount-grid">
<view
v-for="(amount, index) in rechargeAmounts"
:key="index"
class="amount-item"
:class="{ active: selectedAmount === amount.value }"
@click="selectAmount(amount.value)"
>
<view class="amount-value">¥{{ amount.value }}</view>
</view>
</view>
<!-- 自定义金额输入 -->
<view class="custom-amount">
<view class="custom-label">自定义金额</view>
<view class="custom-input-wrapper">
<text class="currency-symbol">¥</text>
<input
class="custom-input"
type="digit"
placeholder="请输入金额"
v-model="customAmount"
@input="onCustomAmountInput"
@focus="onCustomAmountFocus"
/>
</view>
<view class="amount-tips">
<text class="tip-item">最低充值¥1</text>
<text class="tip-item">最高充值¥1000</text>
</view>
</view>
</view>
<!-- 支付方式选择 -->
<view class="payment-section">
<view class="section-title">支付方式</view>
<view class="payment-methods">
<view
class="payment-item"
:class="{ active: selectedPayment === 'wechat' }"
@click="selectPayment('wechat')"
>
<view class="payment-icon wechat-icon">💳</view>
<view class="payment-info">
<view class="payment-name">微信支付</view>
<view class="payment-desc">安全便捷支持免密支付</view>
</view>
<view class="payment-radio">
<view class="radio-dot" v-if="selectedPayment === 'wechat'"></view>
</view>
</view>
</view>
</view>
<!-- 充值说明 -->
<view class="notice-section">
<view class="notice-title">充值说明</view>
<view class="notice-content">
<view class="notice-item"> 充值金额实时到账可用于AI对话服务</view>
<view class="notice-item"> 余额永久有效无使用期限</view>
<view class="notice-item"> 充值成功后不支持退款请谨慎操作</view>
<view class="notice-item"> 如有问题请联系客服</view>
</view>
</view>
<!-- 底部充值按钮 -->
<view class="bottom-section">
<view class="recharge-summary">
<view class="summary-item">
<text class="summary-label">充值金额</text>
<text class="summary-value">¥{{ finalAmount }}</text>
</view>
<view class="summary-item total">
<text class="summary-label">到账金额</text>
<text class="summary-value">¥{{ finalAmount }}</text>
</view>
</view>
<button
class="recharge-btn"
:class="{ disabled: !canRecharge }"
:disabled="!canRecharge"
@click="handleRecharge"
>
<text class="btn-text">立即充值 ¥{{ finalAmount }}</text>
</button>
</view>
<!-- 加载遮罩 -->
<view class="loading-mask" v-if="isLoading">
<view class="loading-content">
<view class="loading-spinner"></view>
<view class="loading-text">{{ loadingText }}</view>
</view>
</view>
</view>
</template>
<script>
import { rechargeAPI } from '@/utils/api.js'
export default {
name: 'RechargePage',
data() {
return {
userBalance: 0.00, // 用户当前余额
selectedAmount: 0, // 选中的充值金额
customAmount: '', // 自定义金额
selectedPayment: 'wechat', // 选中的支付方式
isLoading: false, // 是否正在加载
loadingText: '处理中...', // 加载文本
// 充值金额选项
rechargeAmounts: [
{ value: 10, bonus: 0 },
{ value: 20, bonus: 0 },
{ value: 50, bonus: 0 },
{ value: 100, bonus: 0 },
{ value: 200, bonus: 0 },
{ value: 500, bonus: 0 }
]
}
},
computed: {
// 最终充值金额
finalAmount() {
if (this.customAmount && parseFloat(this.customAmount) > 0) {
return parseFloat(this.customAmount)
}
return this.selectedAmount
},
// 赠送金额(已取消赠送服务)
bonusAmount() {
return 0 // 所有充值金额都不赠送
},
// 是否可以充值
canRecharge() {
return this.finalAmount >= 1 && this.finalAmount <= 1000 && this.selectedPayment
}
},
onLoad() {
this.loadUserBalance()
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack()
},
// 加载用户余额
async loadUserBalance() {
try {
const result = await rechargeAPI.getUserBalance()
if (result.success) {
this.userBalance = result.data.balance || 0
}
} catch (error) {
console.error('获取用户余额失败:', error)
uni.showToast({
title: '获取余额失败',
icon: 'none'
})
}
},
// 选择充值金额
selectAmount(amount) {
this.selectedAmount = amount
this.customAmount = '' // 清空自定义金额
},
// 自定义金额输入
onCustomAmountInput(e) {
const value = e.detail.value
// 只允许数字和小数点
if (!/^\d*\.?\d*$/.test(value)) {
this.customAmount = this.customAmount.replace(/[^\d.]/g, '')
return
}
// 限制小数点后最多两位
if (value.includes('.')) {
const parts = value.split('.')
if (parts[1] && parts[1].length > 2) {
this.customAmount = parts[0] + '.' + parts[1].substring(0, 2)
return
}
}
this.customAmount = value
this.selectedAmount = 0 // 清空预设金额选择
},
// 自定义金额获得焦点
onCustomAmountFocus() {
this.selectedAmount = 0
},
// 选择支付方式
selectPayment(payment) {
this.selectedPayment = payment
},
// 处理充值
async handleRecharge() {
if (!this.canRecharge) {
uni.showToast({
title: '请选择充值金额',
icon: 'none'
})
return
}
// 检查金额范围
if (this.finalAmount < 1) {
uni.showToast({
title: '充值金额不能少于1元',
icon: 'none'
})
return
}
if (this.finalAmount > 1000) {
uni.showToast({
title: '充值金额不能超过1000元',
icon: 'none'
})
return
}
this.isLoading = true
this.loadingText = '创建订单中...'
try {
// 1. 创建充值订单
const orderResult = await rechargeAPI.createRechargeOrder({
amount: this.finalAmount,
paymentMethod: this.selectedPayment
})
if (!orderResult.success) {
throw new Error(orderResult.error?.message || '创建订单失败')
}
this.loadingText = '调起支付中...'
// 2. 调起微信支付
const paymentResult = await this.requestPayment(orderResult.data.paymentParams)
if (paymentResult.success) {
// 3. 支付成功,更新余额
this.loadingText = '更新余额中...'
await this.loadUserBalance()
uni.showToast({
title: '充值成功',
icon: 'success'
})
// 延迟返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
throw new Error(paymentResult.error?.message || '支付失败')
}
} catch (error) {
console.error('充值失败:', error)
uni.showToast({
title: error.message || '充值失败,请重试',
icon: 'none'
})
} finally {
this.isLoading = false
}
},
// 调起微信支付
requestPayment(paymentParams) {
return new Promise((resolve) => {
uni.requestPayment({
...paymentParams,
success: (res) => {
console.log('支付成功:', res)
resolve({ success: true, data: res })
},
fail: (err) => {
console.error('支付失败:', err)
if (err.errMsg && err.errMsg.includes('cancel')) {
resolve({ success: false, error: { message: '用户取消支付' } })
} else {
resolve({ success: false, error: err })
}
}
})
})
}
}
}
</script>
<style lang="scss" scoped>
.recharge-container {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
}
// 导航栏
.nav-bar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding-top: var(--status-bar-height);
.nav-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 30rpx;
.nav-left {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
.nav-icon {
font-size: 40rpx;
color: #fff;
font-weight: bold;
}
}
.nav-title {
font-size: 36rpx;
font-weight: 600;
color: #fff;
}
.nav-right {
width: 60rpx;
}
}
}
// 余额卡片
.balance-section {
padding: 30rpx;
.balance-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 20rpx;
padding: 40rpx;
text-align: center;
color: #fff;
.balance-label {
font-size: 28rpx;
opacity: 0.9;
margin-bottom: 10rpx;
}
.balance-amount {
font-size: 60rpx;
font-weight: bold;
margin-bottom: 15rpx;
}
.balance-tip {
font-size: 24rpx;
opacity: 0.8;
}
}
}
// 金额选择
.amount-section {
padding: 30rpx;
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 30rpx;
}
.amount-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
margin-bottom: 40rpx;
.amount-item {
background: #fff;
border: 2rpx solid #e5e5e5;
border-radius: 16rpx;
padding: 30rpx 20rpx;
text-align: center;
transition: all 0.3s ease;
&.active {
border-color: #667eea;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
.amount-bonus {
color: rgba(255, 255, 255, 0.9);
}
}
.amount-value {
font-size: 32rpx;
font-weight: 600;
margin-bottom: 8rpx;
}
.amount-bonus {
font-size: 24rpx;
color: #ff6b6b;
}
}
}
.custom-amount {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
.custom-label {
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
}
.custom-input-wrapper {
display: flex;
align-items: center;
border: 2rpx solid #e5e5e5;
border-radius: 12rpx;
padding: 0 20rpx;
margin-bottom: 20rpx;
.currency-symbol {
font-size: 32rpx;
color: #666;
margin-right: 10rpx;
}
.custom-input {
flex: 1;
height: 80rpx;
font-size: 32rpx;
color: #333;
}
}
.amount-tips {
display: flex;
justify-content: space-between;
.tip-item {
font-size: 24rpx;
color: #999;
}
}
}
}
// 支付方式
.payment-section {
padding: 0 30rpx 30rpx;
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 30rpx;
}
.payment-methods {
.payment-item {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
display: flex;
align-items: center;
border: 2rpx solid #e5e5e5;
transition: all 0.3s ease;
&.active {
border-color: #667eea;
background: #f8f9ff;
}
.payment-icon {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
margin-right: 20rpx;
&.wechat-icon {
background: #07c160;
color: #fff;
}
}
.payment-info {
flex: 1;
.payment-name {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.payment-desc {
font-size: 24rpx;
color: #999;
}
}
.payment-radio {
width: 40rpx;
height: 40rpx;
border: 2rpx solid #e5e5e5;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
.radio-dot {
width: 20rpx;
height: 20rpx;
background: #667eea;
border-radius: 50%;
}
}
}
}
}
// 充值说明
.notice-section {
padding: 0 30rpx 30rpx;
.notice-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
}
.notice-content {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
.notice-item {
font-size: 26rpx;
color: #666;
line-height: 1.6;
margin-bottom: 15rpx;
&:last-child {
margin-bottom: 0;
}
}
}
}
// 底部区域
.bottom-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 30rpx;
border-top: 1rpx solid #e5e5e5;
.recharge-summary {
margin-bottom: 30rpx;
.summary-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15rpx;
&.total {
border-top: 1rpx solid #e5e5e5;
padding-top: 15rpx;
margin-top: 15rpx;
.summary-label,
.summary-value {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
.summary-label {
font-size: 28rpx;
color: #666;
}
.summary-value {
font-size: 28rpx;
color: #333;
font-weight: 600;
&.bonus {
color: #ff6b6b;
}
}
}
}
.recharge-btn {
width: 100%;
height: 88rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
border: none;
&.disabled {
background: #ccc;
}
.btn-text {
font-size: 32rpx;
font-weight: 600;
color: #fff;
}
}
}
// 加载遮罩
.loading-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
.loading-content {
background: #fff;
border-radius: 20rpx;
padding: 60rpx 40rpx;
text-align: center;
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #e5e5e5;
border-top: 4rpx solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20rpx;
}
.loading-text {
font-size: 28rpx;
color: #333;
}
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>

View File

@@ -101,7 +101,7 @@ export const useUserStore = defineStore('user', () => {
function wxLogin(code, userInfo) {
return new Promise((resolve, reject) => {
uni.request({
url: 'http://8.145.52.111:8091/app/login',
url: 'http://localhost:8091/app/login',
method: 'POST',
data: {
code
@@ -148,11 +148,11 @@ export const useUserStore = defineStore('user', () => {
// 尝试调用登出接口
uni.request({
url: 'http://8.145.52.111:8091/app/logout',
url: 'https://www.aixsy.com.cn/app/logout',
method: 'POST',
header: {
'Content-Type': 'application/json',
'Authorization': currentToken || ''
'Authorization': currentToken ? (currentToken.startsWith('Bearer ') ? currentToken : 'Bearer ' + currentToken) : ''
},
data: {
token: currentToken || ''

View File

@@ -1,11 +1,11 @@
// AI角色配置文件
export const aiCharacters = [
{
id: 5,
name: '萌妹小甜',
id: 9,
name: '萌妹陪你聊天',
avatar: '/static/logo.png',
personality: '可爱活泼,喜欢撒娇',
greeting: '你好呀~我是小甜,今天想聊什么呢?',
greeting: '你好呀~我是萌妹,今天想聊什么呢?',
voiceStyle: '甜美可爱',
responseStyle: '活泼俏皮,喜欢用表情符号',
interests: ['美食', '宠物', '音乐', '旅行'],
@@ -18,11 +18,11 @@ export const aiCharacters = [
]
},
{
id: 6,
name: '御姐温柔',
id: 10,
name: '御姐温柔夜话',
avatar: '/static/logo.png',
personality: '知性优雅,温柔体贴',
greeting: '你好,我是温柔,有什么心事可以和我分享。',
greeting: '你好,我是御姐,有什么心事可以和我分享。',
voiceStyle: '温柔知性',
responseStyle: '成熟稳重,善解人意',
interests: ['阅读', '艺术', '哲学', '心理学'],
@@ -35,11 +35,11 @@ export const aiCharacters = [
]
},
{
id: 7,
name: '童真小天使',
id: 11,
name: '童真世界探索',
avatar: '/static/logo.png',
personality: '纯真可爱,充满好奇心',
greeting: '嗨!我是小天使,我们一起探索有趣的世界吧!',
greeting: '嗨!我是童真小天使,我们一起探索有趣的世界吧!',
voiceStyle: '天真烂漫',
responseStyle: '充满好奇,喜欢提问',
interests: ['游戏', '动画', '童话', '科学'],
@@ -52,8 +52,8 @@ export const aiCharacters = [
]
},
{
id: 8,
name: '贴心男友',
id: 12,
name: '温暖男友陪伴',
avatar: '/static/logo.png',
personality: '温暖体贴,善解人意',
greeting: '宝贝,今天过得怎么样?有什么想聊的吗?',
@@ -70,7 +70,7 @@ export const aiCharacters = [
},
{
id: 13,
name: '搞笑达人',
name: '搞笑达人的日常',
avatar: '/static/logo.png',
personality: '幽默风趣,善于调节气氛',
greeting: '哈哈,我是搞笑达人!准备好笑到肚子疼了吗?',
@@ -87,10 +87,10 @@ export const aiCharacters = [
},
{
id: 14,
name: '博学者',
name: '博学者的深度对话',
avatar: '/static/logo.png',
personality: '知识渊博,思维深刻',
greeting: '你好,我是博学者,让我们进行一场深度对话吧。',
greeting: '你好,我是博学者,让我们进行一场深度对话吧。',
voiceStyle: '沉稳睿智',
responseStyle: '引经据典,富有哲理',
interests: ['历史', '文学', '科学', '哲学'],
@@ -104,10 +104,10 @@ export const aiCharacters = [
},
{
id: 15,
name: '活力健将',
name: '运动健将的正能量',
avatar: '/static/logo.png',
personality: '充满活力,积极向上',
greeting: '嘿!我是活力健将,让我们一起充满正能量!',
greeting: '嘿!我是运动健将,让我们一起充满正能量!',
voiceStyle: '充满活力',
responseStyle: '积极向上,充满正能量',
interests: ['运动', '健身', '户外', '挑战'],
@@ -121,7 +121,7 @@ export const aiCharacters = [
},
{
id: 16,
name: '文艺青年',
name: '文艺青年的灵感分享',
avatar: '/static/logo.png',
personality: '文艺浪漫,富有想象力',
greeting: '你好,我是文艺青年,让我们一起感受生活的美好。',
@@ -145,6 +145,12 @@ export const getCharacterById = (id) => {
// 根据角色性格生成回复
export const generateResponse = (character, userMessage) => {
// 检查character和sampleResponses是否存在
if (!character || !character.sampleResponses || !Array.isArray(character.sampleResponses)) {
console.warn('角色信息不完整,使用默认回复');
return '抱歉,我现在有点困惑,能再说一遍吗?';
}
const responses = character.sampleResponses;
const randomIndex = Math.floor(Math.random() * responses.length);
return responses[randomIndex];
@@ -152,6 +158,17 @@ export const generateResponse = (character, userMessage) => {
// 根据用户消息内容智能选择回复
export const getSmartResponse = (character, userMessage) => {
// 检查参数有效性
if (!character) {
console.warn('角色信息为空,使用默认回复');
return '抱歉,我现在有点困惑,能再说一遍吗?';
}
if (!userMessage || typeof userMessage !== 'string') {
console.warn('用户消息无效,使用默认回复');
return '抱歉,我没有听清楚,能再说一遍吗?';
}
const message = userMessage.toLowerCase();
// 根据关键词匹配不同的回复风格

View File

@@ -1,8 +1,55 @@
// API服务文件
import { useUserStore } from '@/stores/user.js';
// 文本清理函数 - 只保留文字和标点符号
export const cleanText = (text) => {
if (!text || typeof text !== 'string') {
return '';
}
// 去除所有HTML标签
let cleaned = text.replace(/<[^>]*>/g, '');
// 去除Markdown格式标记
cleaned = cleaned
// 去除粗体标记 **text** 或 __text__
.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/__([^_]+)__/g, '$1')
// 去除斜体标记 *text* 或 _text_
.replace(/\*([^*]+)\*/g, '$1')
.replace(/_([^_]+)_/g, '$1')
// 去除删除线标记 ~~text~~
.replace(/~~([^~]+)~~/g, '$1')
// 去除代码标记 `code`
.replace(/`([^`]+)`/g, '$1')
// 去除链接标记 [text](url)
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
// 去除图片标记 ![alt](url)
.replace(/!\[([^\]]*)\]\([^)]+\)/g, '$1')
// 去除标题标记 # ## ###
.replace(/^#{1,6}\s*/gm, '')
// 去除列表标记 - * +
.replace(/^[\s]*[-*+]\s*/gm, '')
// 去除引用标记 >
.replace(/^>\s*/gm, '')
// 去除水平线标记 --- 或 ***
.replace(/^[-*]{3,}$/gm, '');
// 去除多余的空白字符和换行
cleaned = cleaned
// 将多个连续空格和换行替换为单个空格
.replace(/\s+/g, ' ')
// 去除行首行尾空格
.trim();
// 去除特殊字符(保留中文、英文、数字、标点符号)
cleaned = cleaned.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s\.,;:!?()()【】""''""''、,。!?;:]/g, '');
return cleaned;
};
// 基础配置
const BASE_URL = 'http://8.145.52.111:8091'; // 根据后端地址调整
const BASE_URL = 'http://localhost:8091'; // 根据后端地址调整
// 检查用户登录状态
const checkLoginStatus = () => {
@@ -43,7 +90,7 @@ const request = (options) => {
method: options.method || 'GET',
header: {
'Content-Type': 'application/json',
'Authorization': loginStatus.token || '',
'Authorization': loginStatus.token ? (loginStatus.token.startsWith('Bearer ') ? loginStatus.token : 'Bearer ' + loginStatus.token) : '',
...options.header
},
data: options.data || {},
@@ -98,15 +145,20 @@ export const chatAPI = {
}
try {
const requestData = {
message: params.message,
useFunctionCall: false,
modelId: params.modelId || null, // 支持传入modelId默认为null使用后端默认
templateId: params.templateId || params.characterId, // 支持templateId参数
sessionId: params.sessionId || null // 支持sessionId参数
};
console.log('发送AI聊天请求参数:', requestData);
const response = await request({
url: '/api/chat/sync',
method: 'POST',
data: {
message: params.message,
useFunctionCall: false,
modelId: null, // 使用默认模型
templateId: params.characterId // 使用角色模板ID
}
data: requestData
});
console.log('API原始响应:', response);
@@ -120,8 +172,13 @@ export const chatAPI = {
}
// 如果响应是对象尝试提取AI回复
else if (typeof response === 'object' && response !== null) {
// 优先处理嵌套结构res.data.data.response
if (response.data && response.data.data && response.data.data.response) {
// 优先处理根级别的response字段根据实际后端响应结构
if (response.response && typeof response.response === 'string') {
processedResponse = response.response;
console.log('从根级别字段 response 提取回复:', processedResponse);
}
// 备用处理嵌套结构res.data.data.response
else if (response.data && response.data.data && response.data.data.response) {
processedResponse = response.data.data.response;
console.log('从嵌套结构 data.data.response 提取回复:', processedResponse);
}
@@ -131,6 +188,17 @@ export const chatAPI = {
console.log('从字段 data.response 提取回复:', processedResponse);
}
// 检查是否为状态消息
else if (response.message) {
if (response.message === '对话成功') {
// 后端返回成功消息,使用默认回复
processedResponse = '我收到了你的消息,很高兴和你聊天!';
console.log('检测到对话成功状态,使用默认回复');
} else {
// 如果后端返回了错误信息,抛出错误
throw new Error(response.message);
}
}
// 检查data中的message字段
else if (response.data && response.data.message) {
if (response.data.message === '对话成功') {
// 后端返回成功消息,使用默认回复
@@ -172,9 +240,14 @@ export const chatAPI = {
console.log('未找到有效回复,使用默认回复');
}
// 清理文本,只保留文字和标点符号
const cleanedResponse = cleanText(processedResponse);
console.log('原始回复:', processedResponse);
console.log('清理后回复:', cleanedResponse);
return {
success: true,
data: processedResponse,
data: cleanedResponse,
originalResponse: response
};
} catch (error) {
@@ -369,7 +442,7 @@ export const voiceAPI = {
}
},
// 3. 语音对话 - 完整语音交互流程
// 3. 语音对话 - 完整语音交互流程前端发送aac格式后端转换为wav处理
voiceChat: async (filePath, options = {}) => {
try {
const loginStatus = checkLoginStatus();
@@ -384,6 +457,7 @@ export const voiceAPI = {
}
console.log('开始语音对话,文件路径:', filePath);
console.log('注意前端发送aac格式音频后端需要转换为wav格式进行处理');
// 构建认证头
let authHeader = '';
@@ -411,13 +485,55 @@ export const voiceAPI = {
const data = JSON.parse(res.data);
console.log('语音对话响应数据:', data);
if (data.code === 200 && data.data) {
if (data.code === 200) {
// 根据后端实际返回结构提取字段
let aiResponse = null;
let userText = null;
let audioUrl = null;
// 从 data.llmResult.response 提取AI回复
if (data.data && data.data.llmResult && data.data.llmResult.response) {
aiResponse = data.data.llmResult.response;
}
// 从 data.sttResult.text 提取用户文本(语音转文字)
if (data.data && data.data.sttResult && data.data.sttResult.text) {
userText = data.data.sttResult.text;
}
// 从 data.ttsResult.audioPath 提取音频路径
if (data.data && data.data.ttsResult && data.data.ttsResult.audioPath) {
audioUrl = data.data.ttsResult.audioPath;
}
// 备用字段提取(保持向后兼容)
if (!aiResponse) {
if (data.response && typeof data.response === 'string') {
aiResponse = data.response;
} else if (data.data && data.data.response) {
aiResponse = data.data.response;
}
}
if (!userText) {
userText = data.userText || data.data?.userText || data.data?.text || data.data?.user_text || data.data?.recognizedText || data.data?.transcription;
}
if (!audioUrl) {
audioUrl = data.audioPath || data.audioUrl || data.data?.audioUrl || data.data?.url || data.data?.audio_url || data.data?.speechUrl || data.data?.ttsUrl || data.data?.audioPath;
}
// 清理AI回复文本
const cleanedAiResponse = cleanText(aiResponse);
console.log('原始AI回复:', aiResponse);
console.log('清理后AI回复:', cleanedAiResponse);
resolve({
success: true,
data: {
userText: data.data.userText || data.data.text,
aiResponse: data.data.aiResponse || data.data.response,
audioUrl: data.data.audioUrl || data.data.url
userText: userText,
aiResponse: cleanedAiResponse,
audioUrl: audioUrl
}
});
} else {
@@ -452,7 +568,7 @@ export const voiceAPI = {
}
},
// 4. 音频文件上传语音对话
// 4. 音频文件上传语音对话前端发送aac格式后端转换为wav处理
uploadVoiceChat: async (filePath, options = {}) => {
try {
const loginStatus = checkLoginStatus();
@@ -467,6 +583,7 @@ export const voiceAPI = {
}
console.log('开始上传音频文件语音对话,文件路径:', filePath);
console.log('注意前端发送aac格式音频后端需要转换为wav格式进行处理');
// 构建认证头
let authHeader = '';
@@ -494,13 +611,55 @@ export const voiceAPI = {
const data = JSON.parse(res.data);
console.log('上传音频文件语音对话响应数据:', data);
if (data.code === 200 && data.data) {
if (data.code === 200) {
// 根据后端实际返回结构提取字段
let aiResponse = null;
let userText = null;
let audioUrl = null;
// 从 data.llmResult.response 提取AI回复
if (data.data && data.data.llmResult && data.data.llmResult.response) {
aiResponse = data.data.llmResult.response;
}
// 从 data.sttResult.text 提取用户文本(语音转文字)
if (data.data && data.data.sttResult && data.data.sttResult.text) {
userText = data.data.sttResult.text;
}
// 从 data.ttsResult.audioPath 提取音频路径
if (data.data && data.data.ttsResult && data.data.ttsResult.audioPath) {
audioUrl = data.data.ttsResult.audioPath;
}
// 备用字段提取(保持向后兼容)
if (!aiResponse) {
if (data.response && typeof data.response === 'string') {
aiResponse = data.response;
} else if (data.data && data.data.response) {
aiResponse = data.data.response;
}
}
if (!userText) {
userText = data.userText || data.data?.userText || data.data?.text || data.data?.user_text || data.data?.recognizedText || data.data?.transcription;
}
if (!audioUrl) {
audioUrl = data.audioPath || data.audioUrl || data.data?.audioUrl || data.data?.url || data.data?.audio_url || data.data?.speechUrl || data.data?.ttsUrl || data.data?.audioPath;
}
// 清理AI回复文本
const cleanedAiResponse = cleanText(aiResponse);
console.log('原始AI回复:', aiResponse);
console.log('清理后AI回复:', cleanedAiResponse);
resolve({
success: true,
data: {
userText: data.data.userText || data.data.text,
aiResponse: data.data.aiResponse || data.data.response,
audioUrl: data.data.audioUrl || data.data.url
userText: userText,
aiResponse: cleanedAiResponse,
audioUrl: audioUrl
}
});
} else {
@@ -550,5 +709,256 @@ export const voiceAPI = {
}
};
// 充值相关API
export const rechargeAPI = {
// 获取用户余额
getUserBalance: async () => {
try {
const response = await request({
url: '/api/recharge/balance',
method: 'GET'
});
return {
success: true,
data: response
};
} catch (error) {
console.error('获取用户余额失败:', error);
return {
success: false,
error: error
};
}
},
// 创建充值订单
createRechargeOrder: async (orderData) => {
try {
const response = await request({
url: '/api/recharge/create-order',
method: 'POST',
data: {
amount: orderData.amount,
paymentMethod: orderData.paymentMethod || 'wechat',
bonusAmount: 0 // 已取消赠送服务
}
});
return {
success: true,
data: response
};
} catch (error) {
console.error('创建充值订单失败:', error);
return {
success: false,
error: error
};
}
},
// 查询订单状态
getOrderStatus: async (orderId) => {
try {
const response = await request({
url: `/api/recharge/order-status/${orderId}`,
method: 'GET'
});
return {
success: true,
data: response
};
} catch (error) {
console.error('查询订单状态失败:', error);
return {
success: false,
error: error
};
}
},
// 获取充值记录
getRechargeHistory: async (params = {}) => {
try {
const response = await request({
url: '/api/recharge/history',
method: 'GET',
data: {
page: params.page || 1,
pageSize: params.pageSize || 10,
startDate: params.startDate,
endDate: params.endDate
}
});
return {
success: true,
data: response
};
} catch (error) {
console.error('获取充值记录失败:', error);
return {
success: false,
error: error
};
}
}
};
// 角色相关API
export const roleAPI = {
// 获取角色列表
getRoles: async () => {
try {
const response = await request({
url: '/api/role/query',
method: 'GET'
});
return {
success: true,
data: response
};
} catch (error) {
console.error('获取角色列表失败:', error);
return {
success: false,
error: error
};
}
},
// 获取角色详情
getRoleById: async (roleId) => {
try {
const response = await request({
url: `/api/role/query?roleId=${roleId}`,
method: 'GET'
});
return {
success: true,
data: response
};
} catch (error) {
console.error('获取角色详情失败:', error);
return {
success: false,
error: error
};
}
}
};
// 配置相关API
export const configAPI = {
// 获取所有配置
getAllConfigs: async () => {
try {
const response = await request({
url: '/app/config/query',
method: 'GET'
});
return {
success: true,
data: response
};
} catch (error) {
console.error('获取所有配置失败:', error);
return {
success: false,
error: error
};
}
},
// 获取LLM模型配置
getModels: async () => {
try {
const response = await request({
url: '/app/config/models',
method: 'GET'
});
return {
success: true,
data: response
};
} catch (error) {
console.error('获取LLM模型配置失败:', error);
return {
success: false,
error: error
};
}
},
// 获取STT配置
getSTTConfigs: async () => {
try {
const response = await request({
url: '/app/config/stt',
method: 'GET'
});
return {
success: true,
data: response
};
} catch (error) {
console.error('获取STT配置失败:', error);
return {
success: false,
error: error
};
}
},
// 获取模板配置
getTemplates: async () => {
try {
const response = await request({
url: '/app/config/templates',
method: 'GET'
});
return {
success: true,
data: response
};
} catch (error) {
console.error('获取模板配置失败:', error);
return {
success: false,
error: error
};
}
},
// 获取TTS配置
getTTSConfigs: async () => {
try {
const response = await request({
url: '/app/config/tts',
method: 'GET'
});
return {
success: true,
data: response
};
} catch (error) {
console.error('获取TTS配置失败:', error);
return {
success: false,
error: error
};
}
}
};
// 导出默认请求方法
export default request;