20260325稳定版

This commit is contained in:
2026-03-25 15:59:24 +08:00
commit 0decd78604
470 changed files with 53100 additions and 0 deletions
+542
View File
@@ -0,0 +1,542 @@
<template>
<view class="coin-page" :style="{ paddingTop }">
<view class="coin-bg"></view>
<view class="coin-header">
<view class="header-left" @click="goBack">
<u-icon name="arrow-left" size="28" color="#1b1b1b"></u-icon>
</view>
<text class="header-title">金币中心</text>
<view class="header-right"></view>
</view>
<view class="purchase-card">
<view class="card-row between">
<text class="card-title">金币购买</text>
<view class="sign-info" @click="openSignPopup">
<u-icon name="calendar" size="24" color="#f37a5b"></u-icon>
<text class="sign-text">{{ todaySigned ? '今日已签到' : '签到领金币' }}</text>
</view>
</view>
<view class="amount-row">
<text class="amount-label">金币数量</text>
<u-number-box
v-model="coinAmount"
:step="1"
:min="1"
:integer="true"
></u-number-box>
</view>
<text class="tips">最少购买 1 枚金币,步长 1,可自由增减。</text>
<u-button
type="primary"
shape="circle"
:loading="submitting"
:disabled="submitting"
@click="handlePurchase"
>
立即购买
</u-button>
</view>
<view class="record-section">
<view class="record-header">
<text class="record-title">金币记录</text>
<text class="record-count">共 {{ recordList.length }} 条</text>
</view>
<view v-if="loading" class="record-empty">正在加载记录...</view>
<view v-else-if="!recordList.length" class="record-empty">暂无金币记录</view>
<view v-else class="record-list">
<view
v-for="item in recordList"
:key="item.id || item.coinLogId || item.createTime"
class="record-item"
>
<view class="record-left">
<view class="flex flex-col justify-start items-start">
<text class="record-name">{{ item.desc || item.title || '金币变动' }}</text>
<template v-if="item.type === '+' && item.desc === '金币购买'">
<u-tag type="error" v-if="item.status === 0" shape="circle" size="mini" text="未支付"></u-tag>
<u-tag type="success" v-else shape="circle" size="mini" text="已支付"></u-tag>
</template>
</view>
<text class="record-time">
{{ formatTime(item.createTime || item.createdAt || item.updateTime) }}
</text>
</view>
<text
class="record-coin"
:class="{ positive: item.type === '+' }"
>
{{ formatCoinChange(item) }}
</text>
</view>
</view>
</view>
<Login ref="loginPopup" :isHide="true" @done="handleLoginSuccess"/>
<u-popup :show="signPopupVisible" mode="bottom" round="20" @close="signPopupVisible = false">
<view class="sign-popup">
<view class="sign-header">
<text class="sign-title">连续签到</text>
<view class="sign-status" :class="{active: !todaySigned}">
{{ todaySigned ? '今日已签到' : '未签到' }}
</view>
</view>
<view class="sign-list">
<view
class="sign-day"
v-for="day in signCalendar"
:key="day.date"
:class="[{signed: day.signed}, {today: day.isToday}]"
>
<text class="day-text">{{ day.display }}</text>
<text class="status-text">{{ day.signed ? '已签' : '未签' }}</text>
</view>
</view>
<u-button
type="primary"
shape="circle"
:disabled="todaySigned || signing"
:loading="signing"
class="sign-btn"
@click="handleSign"
>
{{ todaySigned ? '今日已签到' : '立即签到' }}
</u-button>
</view>
</u-popup>
</view>
</template>
<script>
import {coinAddReq, coinListReq, signLogAddReq, signLogListReq} from '@/api/coin'
import {dayjs} from '@/uni_modules/iRainna-dayjs/js_sdk/dayjs.min.js'
import {getUserInfo} from '@/util/user'
import Login from '@/components/Login.vue'
export default {
components: {Login},
data() {
return {
coinAmount: 1,
recordList: [],
loading: false,
submitting: false,
paddingTop: '60px',
signPopupVisible: false,
signLogs: [],
todaySigned: false,
signing: false,
signCalendar: []
}
},
onShow() {
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 20
this.paddingTop = `${statusBarHeight + 40}px`
this.ensureLoginAndLoad()
},
methods: {
ensureLoginAndLoad() {
if (!this.ensureLogin()) {
this.recordList = []
return false
}
this.fetchRecords()
this.fetchSignLogs()
return true
},
ensureLogin() {
const {token} = getUserInfo()
if (!token) {
this.$nextTick(() => {
if (this.$refs.loginPopup) this.$refs.loginPopup.open()
})
return false
}
return true
},
async fetchRecords() {
this.loading = true
try {
const {data} = await coinListReq()
if (Array.isArray(data)) {
this.recordList = data
} else if (Array.isArray(data?.records)) {
this.recordList = data.records
} else {
this.recordList = []
}
} catch (error) {
uni.showToast({title: '加载失败,请稍后重试', icon: 'none'})
} finally {
this.loading = false
}
},
async handlePurchase() {
if (this.submitting) return
if (!this.ensureLogin()) return
if (!this.coinAmount || this.coinAmount < 1) {
uni.showToast({title: '最少购买 1 枚金币', icon: 'none'})
this.coinAmount = 1
return
}
this.submitting = true
try {
const {data} = await coinAddReq({
num: this.coinAmount,
type: '+',
desc: '金币购买'
})
const _this = this
uni.requestPayment({
provider: data.provider,
timeStamp: data.timeStamp, // 时间戳
nonceStr: data.nonceStr, // 随机字符串
package: data.package,
signType: data.signType, // 签名算法
paySign: data.paySign, // 签名
success: function (res) {
uni.showToast({
title: '支付成功',
icon: 'success'
});
_this.fetchRecords()
},
fail: function (err) {
console.log('支付失败', err);
}
})
} catch (error) {
const msg = error?.msg || error?.message || '购买失败,请稍后再试'
uni.showToast({title: msg, icon: 'none'})
} finally {
this.submitting = false
}
},
formatTime(value) {
if (!value) return '--'
const time = dayjs(value)
if (typeof time.isValid === 'function' && !time.isValid()) return value
return time.format('YYYY-MM-DD HH:mm')
},
getCoinValue(item) {
const fields = ['coin', 'coinNum', 'number', 'amount', 'value', 'change']
for (const field of fields) {
if (item[field] !== undefined && item[field] !== null) {
const num = Number(item[field])
if (!Number.isNaN(num)) return num
}
}
return 0
},
formatCoinChange(item) {
return `${item.type}${item.num}`
},
handleLoginSuccess() {
this.fetchRecords()
this.fetchSignLogs()
},
async fetchSignLogs() {
if (!this.ensureLogin()) return
try {
const {data} = await signLogListReq()
const list = Array.isArray(data) ? data : Array.isArray(data?.records) ? data.records : []
this.signLogs = list
this.todaySigned = list.some(item => dayjs(item.date).isSame(dayjs(), 'day'))
this.buildSignCalendar()
} catch (error) {
this.signLogs = []
this.todaySigned = false
}
},
buildSignCalendar() {
const today = dayjs()
const calendar = []
for (let i = 6; i >= 0; i--) {
const date = today.subtract(i, 'day')
const formatted = date.format('YYYY-MM-DD')
const signed = this.signLogs.some(item => dayjs(item.date).isSame(date, 'day'))
calendar.push({
date: formatted,
signed,
isToday: date.isSame(today, 'day'),
display: date.format('MM/DD')
})
}
this.signCalendar = calendar
},
openSignPopup() {
if (!this.ensureLogin()) return
this.signPopupVisible = true
if (!this.signCalendar.length) {
this.buildSignCalendar()
}
},
async handleSign() {
if (this.signing || this.todaySigned) return
if (!this.ensureLogin()) return
this.signing = true
const today = dayjs().format('YYYY-MM-DD')
try {
await signLogAddReq({date: today})
uni.showToast({title: '签到成功', icon: 'success'})
this.todaySigned = true
this.signLogs.push({date: today})
this.buildSignCalendar()
setTimeout(() => {
this.signPopupVisible = false
this.fetchRecords()
}, 1000)
} catch (error) {
const msg = error?.msg || error?.message || '签到失败'
uni.showToast({title: msg, icon: 'none'})
} finally {
this.signing = false
}
},
goBack() {
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack()
} else {
uni.switchTab({url: '/pages/user/index'})
}
}
}
}
</script>
<style scoped lang="scss">
.coin-page {
position: relative;
min-height: 100vh;
padding: 40rpx 30rpx 80rpx;
box-sizing: border-box;
background: linear-gradient(180deg, #e1ecff 0%, #f5f0ff 45%, #fef8ff 100%);
}
.coin-bg {
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.7);
filter: blur(40rpx);
z-index: 0;
}
.coin-header {
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 30rpx;
}
.header-left,
.header-right {
width: 80rpx;
display: flex;
align-items: center;
justify-content: flex-start;
}
.header-right {
justify-content: flex-end;
}
.header-title {
font-size: 34rpx;
font-weight: 600;
color: #1b1b1b;
}
.purchase-card {
position: relative;
z-index: 2;
background: rgba(255, 255, 255, 0.94);
border-radius: 32rpx;
padding: 40rpx 32rpx;
box-shadow: 0 20rpx 50rpx rgba(117, 148, 255, 0.25);
margin-bottom: 30rpx;
}
.card-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
justify-content: center;
}
.card-row.between {
justify-content: space-between;
}
.sign-info {
display: flex;
align-items: center;
gap: 10rpx;
padding: 10rpx 20rpx;
border-radius: 999rpx;
background: rgba(243, 122, 91, 0.12);
color: #f37a5b;
font-size: 24rpx;
}
.sign-popup {
padding: 40rpx 30rpx 60rpx;
}
.sign-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 30rpx;
}
.sign-title {
font-size: 30rpx;
font-weight: 600;
color: #1b1b1b;
}
.sign-status {
padding: 12rpx 30rpx;
border-radius: 999rpx;
font-size: 24rpx;
background: #f5f5f5;
color: #7a7a7a;
}
.sign-status.active {
background: rgba(52, 199, 89, 0.15);
color: #34c759;
}
.sign-list {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20rpx;
margin-bottom: 40rpx;
}
.sign-day {
background: #f7f8ff;
border-radius: 18rpx;
padding: 20rpx 10rpx;
text-align: center;
font-size: 24rpx;
color: #5e5f7a;
}
.sign-day.today {
border: 2rpx solid #f37a5b;
}
.sign-day.signed {
background: rgba(52, 199, 89, 0.15);
color: #1f7338;
}
.sign-btn {
width: 100%;
}
.card-title {
font-size: 30rpx;
font-weight: 600;
color: #1b1b1b;
}
.amount-row {
margin: 30rpx 0 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.amount-label {
font-size: 28rpx;
color: #5b5f7b;
}
.tips {
font-size: 24rpx;
color: #8f90aa;
margin-bottom: 30rpx;
}
.record-section {
position: relative;
z-index: 2;
background: rgba(255, 255, 255, 0.9);
border-radius: 32rpx;
padding: 30rpx;
box-shadow: 0 20rpx 40rpx rgba(150, 164, 255, 0.2);
}
.record-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
}
.record-title {
font-size: 30rpx;
font-weight: 600;
color: #1f2251;
}
.record-count {
font-size: 24rpx;
color: #a0a2c2;
}
.record-empty {
text-align: center;
color: #a0a2c2;
font-size: 26rpx;
padding: 40rpx 0;
}
.record-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 26rpx 0;
border-bottom: 1px solid rgba(226, 232, 255, 0.7);
}
.record-item:last-child {
border-bottom: none;
}
.record-left {
display: flex;
flex-direction: column;
gap: 10rpx;
}
.record-name {
font-size: 28rpx;
color: #1f2251;
}
.record-time {
font-size: 24rpx;
color: #a0a2c2;
}
.record-coin {
font-size: 30rpx;
font-weight: 600;
color: #f26c5b;
}
.record-coin.positive {
color: #00c78c;
}
</style>
+136
View File
@@ -0,0 +1,136 @@
<template>
<view class="collect-page">
<view v-if="loading" class="state-wrap">
<u-loading-icon mode="circle"></u-loading-icon>
</view>
<view v-else-if="articles.length" class="list-wrapper">
<view class="article-card" v-for="item in articles" :key="item.id" @click="goToDetail(item)">
<image class="cover" :src="item.article.image" mode="aspectFill"></image>
<view class="content">
<text class="title">{{ item.article.title }}</text>
<text class="desc">{{ item.article.source }}</text>
<!-- <view class="meta">-->
<!-- <text class="time">{{ item.time }}</text>-->
<!-- </view>-->
</view>
</view>
</view>
<view v-else class="state-wrap">
<u-empty mode="favor" text="暂无收藏"></u-empty>
</view>
</view>
</template>
<script>
import {listArticleCollectReq} from "@/api/common";
export default {
data() {
return {
articles: [],
loading: false,
defaultCover: '/static/icon/article_1.png'
}
},
onShow() {
this.fetchCollects()
},
onPullDownRefresh() {
this.fetchCollects()
},
methods: {
async fetchCollects() {
this.loading = true
try {
const {data} = await listArticleCollectReq()
const list = Array.isArray(data) ? data : []
this.articles = list
} catch (e) {
uni.showToast({title: '加载失败,请稍后重试', icon: 'none'})
} finally {
this.loading = false
uni.stopPullDownRefresh()
}
},
goToDetail(item) {
if (!item.articleId) {
uni.showToast({title: '文章信息缺失', icon: 'none'})
return
}
uni.navigateTo({
url: `/pages/index/detail?id=${item.articleId}`
})
}
}
}
</script>
<style lang="scss" scoped>
.collect-page {
min-height: 100vh;
padding: 30rpx;
box-sizing: border-box;
background: linear-gradient(180deg, #eef1ff 0%, #fdfdff 45%, #ffffff 100%);
}
.state-wrap {
min-height: 60vh;
display: flex;
align-items: center;
justify-content: center;
}
.list-wrapper {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.article-card {
display: flex;
background: #ffffff;
border-radius: 30rpx;
padding: 24rpx;
box-shadow: 0 16rpx 40rpx rgba(79, 92, 190, 0.12);
}
.cover {
width: 200rpx;
height: 160rpx;
border-radius: 24rpx;
margin-right: 24rpx;
background: #f0f2ff;
}
.content {
flex: 1;
display: flex;
flex-direction: column;
}
.title {
font-size: 30rpx;
font-weight: 600;
color: #1f2140;
line-height: 1.4;
}
.desc {
margin-top: 10rpx;
font-size: 24rpx;
color: #7377a5;
line-height: 1.4;
max-height: 70rpx;
overflow: hidden;
}
.meta {
margin-top: auto;
font-size: 22rpx;
color: #a0a4c3;
display: flex;
justify-content: space-between;
}
</style>
+259
View File
@@ -0,0 +1,259 @@
<template>
<view class="feedback-page">
<view class="top-header" :style="{ paddingTop: statusBarPadding }">
<u-icon name="arrow-left" size="32" color="#1e244f" @click="goBack"></u-icon>
<text class="header-title">意见反馈</text>
<view style="width:32rpx;height:32rpx;"></view>
</view>
<view class="feedback-card">
<view class="card-header">
<text class="card-title">意见反馈</text>
<text class="card-sub">多选</text>
</view>
<view class="section" v-for="group in feedbackOptions" :key="group.title">
<view class="section-title">{{ group.title }}</view>
<view class="option" v-for="option in group.options" :key="option.value" @tap="toggleOption(option)">
<view class="check" :class="{ active: option.selected }">
<view class="dot" v-if="option.selected"></view>
</view>
<text class="option-text">{{ option.label }}</text>
</view>
</view>
</view>
<view class="say-card">
<view class="say-header">
<text class="say-title">我还想说</text>
<text class="say-optional">选填</text>
</view>
<textarea
class="say-textarea"
placeholder="有了您的宝贵意见,我们才能变得更好哦~"
placeholder-style="color:#b4bad0"
v-model="extraMessage"
/>
</view>
<button class="submit-btn" @click="submitFeedback">提交反馈</button>
</view>
</template>
<script>
import {addFeedbackReq} from "@/api/common";
export default {
data() {
return {
statusBarPadding: '40rpx',
extraMessage: '',
feedbackOptions: [
{
title: '咨询体验',
options: [
{ label: '咨询过程不够流畅', value: 'flow' },
{ label: '咨询师答非所问', value: 'answer' },
{ label: '咨询结果不满意', value: 'result' },
{ label: '咨询耗费时间', value: 'time' },
{ label: '我对咨询体验过程无意见', value: 'noExperience' }
]
},
{
title: '功能体验',
options: [
{ label: '产品界面不够美观', value: 'ui' },
{ label: '不喜欢输入方式', value: 'input' },
{ label: '我对功能无意见', value: 'noFunction' }
]
}
]
}
},
onLoad() {
const info = uni.getSystemInfoSync()
this.statusBarPadding = `${(info.statusBarHeight || 20) + 10}px`
},
methods: {
goBack() {
uni.navigateBack().catch(() => {
uni.switchTab({ url: '/pages/user/index' })
})
},
toggleOption(option) {
this.$set(option, 'selected', !option.selected)
},
async submitFeedback() {
const advisory = []
const func = []
this.feedbackOptions.forEach(group => {
group.options.forEach(opt => {
if (opt.selected) {
if (group.title === '咨询体验') advisory.push(opt.label)
else if (group.title === '功能体验') func.push(opt.label)
}
})
})
if (!advisory.length && !func.length && !this.extraMessage.trim()) {
uni.showToast({ title: '请选择或填写反馈', icon: 'none' })
return
}
try {
await addFeedbackReq({
advisoryExp: advisory.join('、'),
funcExp: func.join('、'),
comments: this.extraMessage.trim()
})
uni.showToast({ title: '感谢反馈', icon: 'success' })
this.feedbackOptions.forEach(group => {
group.options.forEach(opt => (opt.selected = false))
})
this.extraMessage = ''
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
const msg = error?.msg || error?.message || '提交失败,请稍后再试'
uni.showToast({title: msg, icon: 'none'})
}
}
}
}
</script>
<style lang="scss" scoped>
.feedback-page {
min-height: 100vh;
padding: 40rpx 30rpx 120rpx;
background: linear-gradient(180deg, #e8f2ff 0%, #f4f5ff 100%);
box-sizing: border-box;
}
.top-header {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 30rpx;
}
.header-title {
font-size: 32rpx;
color: #1e244f;
font-weight: 600;
}
.feedback-card {
background: rgba(255, 255, 255, 0.92);
border-radius: 30rpx;
padding: 30rpx;
box-shadow: 0 20rpx 50rpx rgba(149, 170, 255, 0.2);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.card-title {
font-size: 34rpx;
font-weight: 600;
color: #3b437a;
}
.card-sub {
font-size: 26rpx;
color: #b3b8d3;
}
.section {
margin-top: 20rpx;
}
.section-title {
font-size: 28rpx;
color: #9ea4c5;
margin-bottom: 12rpx;
}
.option {
display: flex;
align-items: center;
padding: 18rpx 0;
}
.check {
width: 32rpx;
height: 32rpx;
border-radius: 16rpx;
border: 2rpx solid #ccd0e6;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.check.active {
border-color: #8d9cff;
background-color: #eef0ff;
}
.dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #7c8cff;
}
.option-text {
font-size: 28rpx;
color: #5a5f85;
}
.say-card {
margin-top: 30rpx;
padding: 30rpx;
background: rgba(255, 255, 255, 0.9);
border-radius: 30rpx;
box-shadow: 0 20rpx 50rpx rgba(149, 170, 255, 0.15);
}
.say-header {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
.say-title {
font-size: 32rpx;
color: #3b437a;
}
.say-optional {
font-size: 26rpx;
color: #b3b8d3;
}
.say-textarea {
width: 100%;
min-height: 200rpx;
background: #f5f6fb;
border-radius: 20rpx;
padding: 20rpx;
box-sizing: border-box;
font-size: 28rpx;
color: #4c5277;
}
.submit-btn {
margin-top: 40rpx;
height: 96rpx;
line-height: 96rpx;
border-radius: 48rpx;
background: linear-gradient(120deg, #7c8cff, #9da6ff);
color: #fff;
font-size: 30rpx;
letter-spacing: 4rpx;
}
</style>
+490
View File
@@ -0,0 +1,490 @@
<template>
<view class="profile-page">
<image class="profile-bg" src="/static/icon/轮播图 Copy kTtd4FG@1x.png" mode="aspectFill"></image>
<view class="profile-header">
<text class="header-title">我的</text>
</view>
<view class="profile-card">
<view class="profile-info" @click="$jump('/pages/user/profile')">
<image class="avatar" :src="user.avatar" mode="aspectFill"></image>
<view class="info-text">
<view class="user-name">{{ user.nickname }}</view>
<!-- <view class="badge">-->
<!-- <u-icon name="camera-fill" size="20" color="#ffc44d"></u-icon>-->
<!-- <text>小X会员</text>-->
<!-- </view>-->
</view>
</view>
<!-- <text class="profile-desc">为您提供更精准的推荐</text>-->
<view class="profile-stats">
<view class="stat" v-for="stat in stats" :key="stat.label" @click="handleMenu(stat)">
<view class="stat-value">{{ stat.value }}</view>
<view class="stat-label">{{ stat.label }}</view>
</view>
</view>
</view>
<view class="vip-card" v-if="!authing">
<view class="flex justify-start items-center">
<view class="vip-amount">剩余金币:
<text class="highlight">{{ user.coin }}</text>
</view>
<view class="vip-amount">,赠送金币:
<text class="highlight">{{ user.sendCoin }}</text>
</view>
</view>
<view class="vip-btn" @click="openRecharge">
<text>立即续费</text>
</view>
</view>
<view class="menu-section">
<view
class="menu-item"
v-for="item in menuList"
:key="item.label"
@click="handleMenu(item)"
>
<view class="menu-left">
<u-icon :name="item.icon" size="28" color="#8c8fad"></u-icon>
<text class="menu-text">{{ item.label }}</text>
</view>
<u-icon name="arrow-right" color="#a3a7c2" size="18"></u-icon>
<view class="menu-overlay" @click.stop="handleMenu(item)"></view>
</view>
</view>
<view class="logout-card" @click="logout" v-if="user">
<text class="logout-text">退出</text>
<u-icon name="arrow-right" color="#5e5f79" size="18"></u-icon>
</view>
<u-popup :show="showRecharge" mode="center" round="20" @close="showRecharge = false">
<view class="recharge-modal">
<view class="balance-card">
<text class="balance-label">账户余额</text>
<text class="balance-value">¥{{ user.balance.toFixed(2) }}</text>
</view>
<view class="modal-block">
<text class="modal-title">充值金额</text>
<input
class="amount-input"
type="digit"
v-model="rechargeAmount"
placeholder="可输入自定义充值金额"
/>
</view>
<view class="modal-block">
<text class="modal-title">支付方式</text>
<view class="pay-option" @click="paymentMethod = 'wechat'">
<view class="checkbox" :class="{ active: paymentMethod === 'wechat' }"></view>
<text class="pay-text">微信支付</text>
</view>
<text class="agreement">购买协议</text>
</view>
<button class="charge-btn" @click="confirmRecharge">立即充值</button>
<view class="charge-desc">
<text>充值说明</text>
<text>1. 账户充值仅限微信在线方式支付,充值金额实时到账;</text>
<text>2. 账户充值套餐赠送的金额即时到账;</text>
<text>3. 账户余额有效期:自充值日起至用完即止;</text>
<text>4. 若有其它疑问,可拨打客服电话400-000-1234。</text>
</view>
</view>
</u-popup>
<u-popup :show="showPopup" close-on-click-overlay round="10" mode="bottom" :safe-area-inset-bottom="false">
<view class="p-10 flex flex-col justify-center items-center">
<view class="my-10 flex justify-center items-center">
<u-image :src="qr" height="600rpx" width="509rpx" show-menu-by-longpress/>
</view>
<u-button shape="circle" type="primary" @click="closePopup">关闭</u-button>
</view>
</u-popup>
</view>
</template>
<script>
import {userInfoReq} from "@/api/user";
import {clearUserInfo, getUserInfo} from "@/util/user";
import {dictDataReq} from "@/api/common";
export default {
data() {
return {
user: {
nickname: '',
avatar: '',
coin: 0,
sendCoin: 0,
},
stats: [
{label: '收藏', value: 0, path: '/pages/user/collect'},
// {label: '赞过', value: 20},
{label: '评论过', value: 0}
],
menuList: [
{label: '客服入口', icon: 'server-fill', path: ''},
{label: '签到中心', icon: 'calendar', path: '/pages/user/coin'},
// {label: '金币中心', icon: 'red-packet-fill', path: '/pages/user/coin'},
{label: '意见反馈', icon: 'edit-pen', path: '/pages/user/feedback'},
{label: '设置(您还可以选择其他人物进行聊天)', icon: 'setting', path: '/pages/user/setting?noChat=true'},
// {label: '新的聊天', icon: 'chat', path: '/pages/chat/select'}
],
showRecharge: false,
rechargeAmount: '',
paymentMethod: 'wechat',
showPopup: false,
qr: '/static/service.png',
authing: true
}
},
methods: {
backHome() {
uni.switchTab({url: '/pages/index/index'})
},
async getDictList() {
const {data} = await dictDataReq({dictId: '171'})
this.authing = parseInt(data[0].dictDataCode) === 1
},
openRecharge() {
// this.showRecharge = true
uni.navigateTo({url: '/pages/user/coin'})
},
handleMenu(item) {
console.log(item)
if (item.path) {
uni.navigateTo({url: item.path})
} else {
console.log(item.label)
if (item.label === '客服入口')
this.showPopup = true
}
},
logout() {
clearUserInfo()
this.user = null
uni.showToast({title: '已退出', icon: 'success'})
this.$refs.Login.open()
},
confirmRecharge() {
if (!this.rechargeAmount) {
uni.showToast({title: '请输入金额', icon: 'none'})
return
}
uni.showToast({title: '充值成功', icon: 'success'})
this.showRecharge = false
this.rechargeAmount = ''
},
async getUserData(){
const {data} = await userInfoReq()
this.user = data
},
closePopup(){
this.showPopup = false
}
},
onShow() {
this.getDictList()
if (getUserInfo().token)
this.getUserData()
},
}
</script>
<style lang="scss" scoped>
.profile-page {
position: relative;
min-height: 100vh;
padding: 120rpx 30rpx 40rpx;
background: linear-gradient(180deg, #c6d5ff 0%, #eef0ff 45%, #fcfbff 100%);
box-sizing: border-box;
}
.profile-bg {
position: absolute;
inset: 0;
width: 100%;
height: 60%;
z-index: 0;
opacity: 0.35;
filter: blur(6rpx);
}
.profile-header {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 30rpx;
}
.header-title {
font-size: 34rpx;
color: #fff;
font-weight: 600;
}
.profile-card {
position: relative;
z-index: 1;
background: rgba(255, 255, 255, 0.18);
border-radius: 34rpx;
padding: 30rpx;
color: #353750;
text-align: center;
box-shadow: inset 0 0 0 1rpx rgba(255, 255, 255, 0.25);
}
.profile-info {
display: flex;
align-items: center;
justify-content: center;
gap: 24rpx;
}
.avatar {
width: 140rpx;
height: 140rpx;
border-radius: 70rpx;
border: 4rpx solid rgba(255, 255, 255, 0.9);
}
.info-text {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12rpx;
}
.user-name {
font-size: 34rpx;
font-weight: 600;
}
.badge {
display: inline-flex;
align-items: center;
gap: 8rpx;
background: rgba(255, 255, 255, 0.25);
border-radius: 30rpx;
padding: 6rpx 20rpx;
font-size: 24rpx;
}
.profile-desc {
margin-top: 20rpx;
color: rgba(255, 255, 255, 0.85);
font-size: 26rpx;
}
.profile-stats {
display: flex;
justify-content: space-around;
margin-top: 30rpx;
}
.stat-value {
font-size: 34rpx;
font-weight: 600;
}
.stat-label {
font-size: 24rpx;
margin-top: 8rpx;
opacity: 0.9;
}
.vip-card {
position: relative;
margin-top: 30rpx;
background: rgba(255, 255, 255, 0.8);
border-radius: 24rpx;
padding: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 20rpx 40rpx rgba(86, 107, 255, 0.15);
}
.vip-title {
font-size: 42rpx;
color: #f5a623;
font-weight: 600;
}
.vip-amount {
margin-top: 10rpx;
color: #606585;
font-size: 26rpx;
}
.highlight {
color: #ff4d4f;
font-weight: 600;
}
.vip-btn {
width: 180rpx;
height: 72rpx;
border-radius: 40rpx;
background: linear-gradient(120deg, #0f1f59, #1e31a6);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 500;
}
.menu-section {
margin-top: 30rpx;
background: rgba(255, 255, 255, 0.9);
border-radius: 24rpx;
box-shadow: 0 20rpx 40rpx rgba(118, 135, 255, 0.14);
}
.menu-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid rgba(215, 217, 245, 0.8);
position: relative;
}
.menu-overlay {
position: absolute;
inset: 0;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-left {
display: flex;
align-items: center;
gap: 20rpx;
}
.menu-text {
font-size: 28rpx;
color: #353750;
}
.logout-card {
margin-top: 30rpx;
background: rgba(233, 234, 255, 0.9);
border-radius: 24rpx;
padding: 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
color: #353750;
}
.logout-text {
font-size: 28rpx;
}
.recharge-modal {
width: 600rpx;
background: #fff;
border-radius: 30rpx;
padding: 30rpx;
box-sizing: border-box;
}
.balance-card {
background: linear-gradient(120deg, #a979ff, #7160ff);
border-radius: 26rpx;
padding: 30rpx;
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.balance-label {
font-size: 30rpx;
}
.balance-value {
font-size: 40rpx;
font-weight: 600;
}
.modal-block {
margin-bottom: 20rpx;
}
.modal-title {
font-size: 28rpx;
color: #4d4f6c;
margin-bottom: 10rpx;
display: block;
}
.amount-input {
width: 100%;
height: 80rpx;
border-radius: 20rpx;
background: #f4f5f9;
padding: 0 24rpx;
box-sizing: border-box;
font-size: 28rpx;
}
.pay-option {
display: flex;
align-items: center;
gap: 12rpx;
margin-top: 10rpx;
}
.checkbox {
width: 32rpx;
height: 32rpx;
border-radius: 10rpx;
border: 2rpx solid #b8bbd3;
}
.checkbox.active {
background: #32c26a;
border-color: #32c26a;
}
.pay-text {
font-size: 28rpx;
color: #3a3f62;
}
.agreement {
margin-top: 12rpx;
color: #3752b4;
font-size: 26rpx;
}
.charge-btn {
width: 100%;
height: 90rpx;
border-radius: 45rpx;
background: linear-gradient(120deg, #a979ff, #7160ff);
color: #fff;
font-size: 30rpx;
margin: 20rpx 0;
}
.charge-desc {
display: flex;
flex-direction: column;
gap: 6rpx;
font-size: 24rpx;
color: #8a8fae;
}
</style>
+119
View File
@@ -0,0 +1,119 @@
<template>
<view>
<view class="flex items-center justify-center" v-if="userInfo" style="padding: 130rpx 0">
<button open-type="chooseAvatar" @chooseavatar="chooseAvatar" class="btn">
<u-avatar :src="`${userInfo.avatar ? userInfo.avatar : ''}`" size="80"/>
</button>
</view>
<view class="p-20">
<u-cell-group :border="false">
<u-cell title="昵称">
<input type="nickname" class="text-right" v-model="userInfo.nickname"
slot="right-icon" @change="changeUsername"/>
</u-cell>
</u-cell-group>
</view>
<view class="p-20">
<u-button @click="save" type="primary" shape="circle">保存修改</u-button>
</view>
<u-action-sheet :actions="genderList" :show="showActionSheet" :closeOnClickAction="true"
round="10" cancelText="取消" @select="selectGender"
@close="showActionSheet = false"/>
</view>
</template>
<script>
import {userInfoReq, userInfoUpdateReq} from "@/api/user";
import {setUserInfo} from "@/util/user";
import {uploadImg} from "@/util";
import {MAIN_API} from "@/config";
export default {
name: "profile",
data() {
return {
cardTypeList: ['充值卡', '月卡', '次卡'],
uri: MAIN_API,
userInfo: {
avatar: null,
nickname: null,
phone: null,
gender: null,
},
genderList: [
{
name: '未知',
value: 0
},
{
name: '男',
value: 1
},
{
name: '女',
value: 2
}
],
showModal: false,
showActionSheet: false,
}
},
methods: {
async getUserInfo() {
const {data} = await userInfoReq()
this.userInfo = data
},
async chooseAvatar({detail}) {
const url = detail.avatarUrl
const {path} = await uploadImg(url)
console.log(path)
this.userInfo.avatar = path
},
selectGender(item) {
this.userInfo.gender = item.value
this.showActionSheet = false
},
changeUsername({detail}) {
const {value} = detail
this.userInfo.nickname = value
},
async save() {
uni.showLoading({
title: '保存中'
})
await userInfoUpdateReq({
avatar: this.userInfo.avatar,
nickname: this.userInfo.nickname,
})
uni.hideLoading()
uni.showToast({
icon: 'success',
title: '已保存',
complete: () => {
setTimeout(() => {
setUserInfo({
header: this.userInfo.avatar,
nickname: this.userInfo.nickname,
})
uni.navigateBack()
}, 1500)
}
})
}
},
async onShow() {
await this.getUserInfo()
}
}
</script>
<style scoped lang="scss">
.btn {
background: none;
}
.btn::after {
border: none;
}
</style>
+679
View File
@@ -0,0 +1,679 @@
<template>
<view class="setting-page">
<view class="top-bar" :style="{ paddingTop: statusBarPadding }">
<u-icon name="arrow-left" size="32" color="#fff" @click="goBack"></u-icon>
</view>
<view class="form-section">
<text class="form-title">请完善您的信息</text>
<view class="form-item" v-for="item in formItems" :key="item.key">
<template v-if="item.key !== 'time' || (item.key === 'time' && !authing) ">
<text class="label">{{ item.label }}</text>
<view class="input" v-if="item.type === 'input'">
<input class="w-100p" :placeholder="item.placeholder" v-model="form[item.key]"/>
</view>
<view v-else class="picker" @click="openPicker(item)">
<text class="picker-value">{{ displayValue(item.key, item.placeholder) }}</text>
<u-icon class="picker-arrow" name="arrow-down" size="20" color="#777"></u-icon>
</view>
</template>
</view>
</view>
<u-picker
:show="pickerVisible.gender"
:columns="genderColumns"
@confirm="onGenderConfirm"
@cancel="closePicker('gender')"
@close="closePicker('gender')"
></u-picker>
<u-datetime-picker
ref="birthRef"
mode="date"
:show="pickerVisible.birth"
v-model="birthPickerValue"
:minDate="minDate"
:maxDate="maxDate"
@change="onBirthChange"
@confirm="onBirthConfirm"
@cancel="closePicker('birth')"
@close="closePicker('birth')"
></u-datetime-picker>
<u-picker
:show="pickerVisible.time"
:columns="timeColumns"
@confirm="onTimeConfirm"
@cancel="closePicker('time')"
@close="closePicker('time')"
></u-picker>
<u-picker
ref="birthplaceRef"
:show="pickerVisible.birthplace"
:columns="areaColumns"
:defaultIndex="areaDefaultIndex"
@change="onBirthplaceChange"
@confirm="onBirthplaceConfirm"
@cancel="closePicker('birthplace')"
@close="closePicker('birthplace')"
></u-picker>
<view class="tips">我们会根据你的选择,提供更精准的推荐</view>
<view class="tips">您还可以选择其他人物进行聊天</view>
<view class="mentor-section">
<view class="mentor-card" v-for="mentor in mentors" :key="mentor.id"
:class="{ active: currentMentor === mentor.roleName }" @click="selectMentor(mentor)">
<view class="mentor-name">{{ mentor.roleName }}</view>
<image class="mentor-avatar" :src="mentor.avatar" mode="aspectFit"></image>
</view>
</view>
<view class="intro-card" v-if="currentMentorItem">
<text class="intro-text">
简介:{{ currentMentorItem.roleDesc }}
</text>
</view>
<!-- <u-gap height="200rpx"/>-->
<!-- <view class="chat-input" :style="{bottom: `${inputBottom}px`}">-->
<!-- <view class="m-40 flex justify-between items-center w-100p">-->
<!-- <view class="input-wrap">-->
<!-- <textarea-->
<!-- class="textarea"-->
<!-- v-model="content"-->
<!-- placeholder="迷茫困惑的话和我聊聊吧"-->
<!-- maxlength="-1"-->
<!-- placeholder-style="text-align:center;"-->
<!-- auto-height-->
<!-- fixed-->
<!-- :show-confirm-bar="false"-->
<!-- />-->
<!-- </view>-->
<!-- <button class="send-btn" @click="save">发送</button>-->
<!-- </view>-->
<!-- </view>-->
<button class="primary-btn" @click="save">保存</button>
<!-- <text class="skip" @click="skip" v-if="!noChat">跳过设置</text>-->
<Login ref="Login" @done="getUserData"/>
</view>
</template>
<script>
import {addChatListReq, chatListReq, listChatRoleReq} from "@/api/chat";
import {updateUserReq, userInfoReq} from "@/api/user";
import {getUserInfo} from "@/util/user";
import {dayjs} from '@/uni_modules/iRainna-dayjs/js_sdk/dayjs.min.js'
import Login from "@/components/Login.vue";
import {dictDataReq, sysAreaListReq} from "@/api/common";
const TIME_OPTIONS = [
'子时23~1点',
'丑时1~3点',
'寅时3~5点',
'卯时5~7点',
'辰时7~9点',
'巳时9~11点',
'午时11~13点',
'未时13~15点',
'申时15~17点',
'酉时17~19点',
'戌时19~21点',
'亥时21~23点'
]
export default {
components: {Login},
data() {
return {
statusBarPadding: '40rpx',
form: {
name: '',
gender: '',
birth: '',
time: '子时23~1点',
birthplace: '',
},
pickerVisible: {
gender: false,
birth: false,
time: false,
birthplace: false
},
genderColumns: [['男', '女']],
timeColumns: [TIME_OPTIONS],
birthPickerValue: Date.now(),
chatId: null,
roleName: null,
roleDesc: null,
avatar: null,
word: null,
formItems: [
{key: 'name', label: '*姓名', type: 'input', placeholder: '请输入'},
{key: 'gender', label: '*性别', type: 'picker', placeholder: '请选择', mode: 'selector', range: ['男', '女']},
{key: 'birth', label: '*出生年月日(阳历)', type: 'picker', placeholder: '请选择', mode: 'date', range: []},
{key: 'time', label: '时辰', type: 'picker', placeholder: '请选择', mode: 'selector', range: TIME_OPTIONS},
{key: 'birthplace', label: '出生地', type: 'picker', placeholder: '请选择', mode: 'selector'}
],
mentors: [],
currentMentor: '五师妹',
currentMentorItem: null,
minDate: dayjs('1900-01-01').valueOf(),
maxDate: dayjs().valueOf(),
noChat: false,
messageList: [],
defaultIndex: [],
areaColumns: [[], []],
areaDefaultIndex: [0, 0],
provinceList: [],
cityList: [],
selectedProvinceIndex: 0,
selectedCityIndex: 0,
inputBottom: 0,
content: '',
authing: true
}
},
onLoad(option) {
this.getDictList()
const info = uni.getSystemInfoSync()
this.inputBottom = uni.getSystemInfoSync().safeAreaInsets.bottom
this.statusBarPadding = `${(info.statusBarHeight || 20) + 10}px`
if (option.chatId) this.chatId = option.chatId
if (option.roleName) this.roleName = option.roleName
if (option.roleDesc) this.roleDesc = option.roleDesc
if (option.avatar) this.avatar = option.avatar
if (option.word) {
this.word = option.word
this.content = option.word
}
if (option.noChat) this.noChat = true
if (!getUserInfo().token) this.$refs.Login.open()
else {
this.getUserData()
}
},
methods: {
async getMessageList() {
const res = await chatListReq()
this.messageList = res.data
this.getRoleList()
},
async getDictList() {
const {data} = await dictDataReq({dictId: '171'})
this.authing = parseInt(data[0].dictDataCode) === 1
},
async getRoleList() {
const {data} = await listChatRoleReq()
const roleIdList = this.messageList.map(item => item.roleId)
// this.mentors = data.filter(item => {
// return !roleIdList.includes(item.id)
// })
this.mentors = data.map(item => {
const hasChat = roleIdList.includes(item.id)
let chatId = null
if (hasChat) {
chatId = this.messageList.find(msg => msg.roleId === item.id).id
}
return {
...item,
hasChat,
chatId
}
})
console.log(this.mentors)
this.currentMentor = ''
this.currentMentorItem = null
if (this.mentors.length) {
this.currentMentor = this.mentors[0].roleName
this.currentMentorItem = this.mentors[0]
}
// this.currentMentor = data[0].roleName
// this.currentMentorItem = data[0]
},
goBack() {
uni.navigateBack().catch(() => {
uni.switchTab({url: '/pages/user/index'})
})
},
openPicker(item) {
if (item.key === 'gender') {
this.pickerVisible.gender = true
return
}
if (item.key === 'birth') {
this.syncBirthPickerValue()
this.pickerVisible.birth = true
return
}
if (item.key === 'time') {
this.pickerVisible.time = true
return
}
if (item.key === 'birthplace') {
this.openAreaPicker()
}
},
closePicker(type) {
if (Object.prototype.hasOwnProperty.call(this.pickerVisible, type)) {
this.pickerVisible[type] = false
}
},
async openAreaPicker() {
await this.loadProvinceList()
if (!this.provinceList.length) {
uni.showToast({title: '暂无地区数据', icon: 'none'})
return
}
this.syncProvinceFromForm()
await this.loadCityList(this.selectedProvinceIndex)
this.syncCityFromForm()
this.updateAreaColumns()
this.areaDefaultIndex = [this.selectedProvinceIndex, this.selectedCityIndex]
this.pickerVisible.birthplace = true
},
parseBirthplace() {
const birthplace = (this.form.birthplace || '').trim()
if (!birthplace) return {provinceName: '', cityName: ''}
const parts = birthplace.split(/\s+/).filter(Boolean)
return {
provinceName: parts[0] || '',
cityName: parts[1] || ''
}
},
syncProvinceFromForm() {
const {provinceName} = this.parseBirthplace()
if (!provinceName) return
const idx = this.provinceList.findIndex(item => this.getAreaName(item) === provinceName)
if (idx !== -1) {
this.selectedProvinceIndex = idx
}
},
syncCityFromForm() {
const {cityName} = this.parseBirthplace()
if (!cityName || !this.cityList.length) return
const idx = this.cityList.findIndex(item => this.getAreaName(item) === cityName)
if (idx !== -1) {
this.selectedCityIndex = idx
}
},
async loadProvinceList() {
if (this.provinceList.length) {
this.updateAreaColumns(-1)
return
}
try {
const {data} = await sysAreaListReq({fid: 0})
this.provinceList = data || []
this.updateAreaColumns(0)
} catch (e) {
uni.showToast({title: '获取省份失败', icon: 'none'})
}
},
async loadCityList(provinceIndex = 0) {
const province = this.provinceList[provinceIndex]
if (!province) {
this.cityList = []
this.updateAreaColumns(1)
return
}
const fid = province.id || province.fid || province.value
if (fid === undefined || fid === null) {
this.cityList = []
this.updateAreaColumns(2)
return
}
try {
const {data} = await sysAreaListReq({fid})
this.cityList = data || []
this.updateAreaColumns(3)
} catch (e) {
this.cityList = []
this.updateAreaColumns(4)
uni.showToast({title: '获取城市失败', icon: 'none'})
}
},
updateAreaColumns(flag = null) {
console.log('this.selectedCityIndex', this.selectedCityIndex, flag)
if (this.selectedProvinceIndex >= this.provinceList.length) {
this.selectedProvinceIndex = 0
}
const provinceNames = this.provinceList.map(item => this.getAreaName(item))
this.$set(this.areaColumns, 0, provinceNames)
if (this.cityList.length) {
if (this.selectedCityIndex >= this.cityList.length) {
this.selectedCityIndex = 0
}
} else {
this.selectedCityIndex = 0
}
const cityNames = (this.cityList.length ? this.cityList : []).map(item => this.getAreaName(item))
this.$set(this.areaColumns, 1, cityNames.length ? cityNames : [''])
this.areaDefaultIndex = [this.selectedProvinceIndex, this.selectedCityIndex]
console.log('this.areaDefaultIndex', this.areaDefaultIndex)
},
onBirthplaceChange({columnIndex, index}) {
if (columnIndex === 0) {
this.selectedProvinceIndex = index
this.selectedCityIndex = 0
this.loadCityList(index)
} else if (columnIndex === 1) {
this.selectedCityIndex = index
}
},
onBirthplaceConfirm({value, indexs}) {
const provinceIndex = indexs && indexs.length ? indexs[0] : this.selectedProvinceIndex
const cityIndex = indexs && indexs.length > 1 ? indexs[1] : this.selectedCityIndex
this.selectedProvinceIndex = provinceIndex
this.selectedCityIndex = cityIndex
this.areaDefaultIndex = [this.selectedProvinceIndex, this.selectedCityIndex]
const province = this.provinceList[provinceIndex]
const city = this.cityList[cityIndex]
const provinceName = this.getAreaName(province)
const cityName = this.getAreaName(city)
this.form.birthplace = cityName ? `${provinceName} ${cityName}` : provinceName
this.closePicker('birthplace')
},
getAreaName(area) {
if (!area) return ''
return area.areaName || area.name || area.label || area.title || ''
},
syncBirthPickerValue() {
if (this.form.birth) {
this.birthPickerValue = dayjs(this.form.birth).valueOf()
this.$refs.birthRef.innerValue = this.birthPickerValue
} else {
this.birthPickerValue = Date.now()
this.birthPickerValue = Number(this.birthPickerValue)
}
console.log('this.birthPickerValue', this.birthPickerValue)
},
onGenderConfirm({value}) {
this.form.gender = value && value.length ? value[0] : ''
this.closePicker('gender')
},
onBirthConfirm({value}) {
this.form.birth = this.formatDate(value)
this.birthPickerValue = value
this.closePicker('birth')
},
onBirthChange({value}) {
this.form.birth = this.formatDate(value)
this.birthPickerValue = value
},
onTimeConfirm({value}) {
this.form.time = value && value.length ? value[0] : ''
this.closePicker('time')
},
formatDate(timestamp) {
const date = new Date(timestamp)
const year = date.getFullYear()
const month = `${date.getMonth() + 1}`.padStart(2, '0')
const day = `${date.getDate()}`.padStart(2, '0')
return `${year}-${month}-${day}`
},
displayValue(key, placeholder) {
return this.form[key] || placeholder
},
selectMentor(item) {
this.currentMentor = item.roleName
this.currentMentorItem = item
},
async save() {
// if (!this.content) return uni.showToast({title: '请输入内容', icon: 'none'})
await updateUserReq({
userId: getUserInfo().uid,
birthday: this.form.birth,
sex: this.form.gender === '男' ? 0 : 1,
birthAddress: this.form.birthplace,
birthTime: this.form.time,
realName: this.form.name,
})
uni.showToast({title: '已保存', icon: 'success'})
await this.toChat()
},
skip() {
uni.showToast({title: '已跳过设置', icon: 'none'})
this.toChat()
},
async toChat() {
if (!this.currentMentor) return
let url = '/pages/chat/index'
const mentor = this.mentors.find(item => item.roleName === this.currentMentor)
url += `?roleName=${mentor.roleName}&roleDesc=${mentor.roleDesc}&avatar=${mentor.avatar}`
let chatId = null
// if (!mentor.hasChat) {
const {data} = await addChatListReq({
roleName: mentor.roleName,
roleDesc: mentor.roleDesc,
roleId: mentor.id
})
chatId = data.id
// } else {
// chatId = mentor.chatId
// }
url += `&chatId=${chatId}&roleId=${mentor.id}`
if (this.content) url += `&word=${this.content}`
uni.navigateTo({url})
},
async getUserData() {
const {data} = await userInfoReq()
if (data.birthday) {
this.form.birth = dayjs(data.birthday).format('YYYY-MM-DD')
this.birthPickerValue = dayjs(data.birthday).toDate()
}
if (data.sex !== null && data.sex !== undefined)
this.form.gender = parseInt(data.sex) === 0 ? '男' : '女'
if (data.birthTime)
this.form.time = data.birthTime
if (data.birthAddress)
this.form.birthplace = data.birthAddress
if (data.realName)
this.form.name = data.realName
console.log(data.gender)
console.log(this.form)
this.getMessageList()
}
}
}
</script>
<style lang="scss" scoped>
.setting-page {
min-height: 100vh;
padding: 40rpx 30rpx 60rpx;
background: linear-gradient(180deg, #8f99dd 0%, #e3e8ff 60%, #ffffff 100%);
color: #fff;
box-sizing: border-box;
}
.top-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 20rpx;
}
.top-title {
font-size: 32rpx;
}
.top-right .pill {
display: flex;
align-items: center;
gap: 14rpx;
background: rgba(255, 255, 255, 0.8);
padding: 12rpx 30rpx;
border-radius: 40rpx;
}
.form-section {
margin-top: 40rpx;
background: rgba(255, 255, 255, 0.15);
border-radius: 30rpx;
padding: 30rpx;
}
.form-title {
font-size: 30rpx;
font-weight: 600;
color: #f2f2ff;
}
.form-item {
margin-top: 30rpx;
}
.label {
font-size: 26rpx;
color: #f3f5ff;
margin-bottom: 12rpx;
display: block;
}
.input,
.picker {
width: 100%;
height: 76rpx;
border-radius: 40rpx;
background: rgba(255, 255, 255, 0.45);
color: #333;
padding: 0 30rpx;
box-sizing: border-box;
display: flex;
align-items: center;
}
.picker {
justify-content: space-between;
}
.picker-value {
color: #51526b;
font-size: 28rpx;
flex: 1;
}
.picker-arrow {
margin-left: 20rpx;
}
.tips {
margin: 30rpx 0;
text-align: center;
color: #5f5f81;
font-size: 24rpx;
}
.mentor-section {
display: flex;
justify-content: space-around;
margin-bottom: 30rpx;
}
.mentor-card {
width: 45%;
border-radius: 32rpx;
background: rgba(255, 255, 255, 0.6);
padding: 20rpx;
text-align: center;
color: #4d4f7f;
}
.mentor-card.active {
background: linear-gradient(160deg, #7f8aff, #b0bdff);
color: #fff;
}
.mentor-name {
font-size: 30rpx;
margin-bottom: 20rpx;
}
.mentor-avatar {
width: 140rpx;
height: 140rpx;
border-radius: 70rpx;
margin: 0 auto;
}
.intro-card {
background: rgba(255, 255, 255, 0.7);
border-radius: 30rpx;
padding: 30rpx;
color: #3a406d;
font-size: 26rpx;
line-height: 1.6;
margin-bottom: 30rpx;
}
.primary-btn {
width: 100%;
height: 96rpx;
line-height: 96rpx;
border-radius: 48rpx;
background: linear-gradient(120deg, #1a2663, #293892);
color: #fff;
font-size: 30rpx;
letter-spacing: 4rpx;
}
.skip {
display: block;
margin-top: 20rpx;
text-align: center;
color: #454669;
}
.chat-input {
width: 100%;
padding: 40rpx 0;
//padding: 20rpx 30rpx 40rpx;
align-items: center;
gap: 20rpx;
//background: #f7f7fb;
display: flex;
justify-content: center;
position: fixed;
left: 0;
bottom: 0;
}
.chat-input .input-wrap {
flex: 1;
border-radius: 30rpx;
background: #21284f;
color: #fff;
padding: 20rpx 30rpx;
margin-left: 30rpx;
display: flex;
align-items: center;
justify-content: center;
//height: 100rpx;
min-height: 60rpx;
}
.chat-input .textarea {
width: 100%;
//height: 100%;
background: none !important;
color: #ffffff !important;
text-align: left;
line-height: 40rpx;
padding: 0;
min-height: 50rpx;
}
.send-btn {
width: 140rpx;
height: 100rpx;
border-radius: 999rpx;
background: linear-gradient(120deg, #fbc889, #f37a5b);
color: #000;
display: flex;
align-items: center;
justify-content: center;
margin: 0 30rpx;
}
</style>