This commit is contained in:
2026-07-10 16:14:31 +08:00
commit 637b0bd144
158 changed files with 33269 additions and 0 deletions
@@ -0,0 +1,6 @@
{
"usingComponents": {
"custom-nav": "/components/custom-nav/custom-nav"
},
"navigationStyle": "custom"
}
+301
View File
@@ -0,0 +1,301 @@
import { taskinfo, uploadavatar, uploadrecord, checkavatar, checkvoice, saveHumanAssets } from "../../../utils/request"
const waveBars = [
{ id: 'b1', cls: 'bar-22' },
{ id: 'b2', cls: 'bar-34' },
{ id: 'b3', cls: 'bar-48' },
{ id: 'b4', cls: 'bar-34' },
{ id: 'b5', cls: 'bar-62' },
{ id: 'b6', cls: 'bar-42' },
{ id: 'b7', cls: 'bar-70' },
{ id: 'b8', cls: 'bar-52' },
{ id: 'b9', cls: 'bar-38' },
{ id: 'b10', cls: 'bar-58' },
{ id: 'b11', cls: 'bar-46' },
{ id: 'b12', cls: 'bar-30' },
]
const miniBars = [
{ id: 'm1', cls: 'mini-bar-18' },
{ id: 'm2', cls: 'mini-bar-30' },
{ id: 'm3', cls: 'mini-bar-46' },
{ id: 'm4', cls: 'mini-bar-28' },
{ id: 'm5', cls: 'mini-bar-56' },
{ id: 'm6', cls: 'mini-bar-36' },
]
function formatRecordTime(totalSeconds: number) {
const minutes = Math.floor(totalSeconds / 60)
const seconds = totalSeconds % 60
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
}
Page({
recordTimer: 0,
voiceCheckTimer: 0,
data: {
isRecording: false,
recordingClass: 'recording-active',
elapsedSeconds: 0,
recordTime: '00:00',
waveBars,
miniBars,
taskinfo: null as any,
avatar: '/static/images/my/image.png',
avatarUrl: '',
avatarChecked: false,
recordUrl: '',
recordDurationMs: 0,
voiceAsrTaskId: '',
voiceChecked: false,
voiceText: '',
voiceStatusText: ''
},
onLoad(ops: any) {
taskinfo(ops.taskid).then((res: any) => {
if (!res.code) {
this.setData({ taskinfo: res.data });
}
})
},
onShow() {
if (this.data.isRecording) {
this.startRecordTimer()
}
},
onHide() {
this.clearRecordTimer()
this.clearVoiceCheckTimer()
},
onUnload() {
this.clearRecordTimer()
this.clearVoiceCheckTimer()
},
handleBack() {
wx.navigateBack({
fail: () => wx.navigateTo({ url: '/pages/aiPackage/preview/index' }),
})
},
handleUploadPhoto() {
const that = this;
wx.chooseImage({
count: 1,
success(res: any) {
const file = res.tempFilePaths[0];
wx.showLoading({ mask: true, title: '上传照片中' });
uploadavatar(file, {}, '/api/upload/avatar').then((uploadRes: any) => {
if (uploadRes.code) {
wx.hideLoading();
wx.showModal({ title: '温馨提示', content: uploadRes.message || '照片上传失败', showCancel: false })
return;
}
wx.showLoading({ mask: true, title: '人脸检测中' });
checkavatar(uploadRes.data.url).then((checkRes: any) => {
wx.hideLoading();
if (!checkRes.code) {
that.setData({ avatar: uploadRes.data.url, avatarUrl: uploadRes.data.url, avatarChecked: true })
wx.showToast({ title: '照片检测通过', icon: 'success' })
} else {
that.setData({ avatarUrl: '', avatarChecked: false })
wx.showModal({ title: '温馨提示', content: checkRes.message || '未检测到人脸', showCancel: false })
}
}).catch(() => {
wx.hideLoading();
that.setData({ avatarUrl: '', avatarChecked: false })
wx.showModal({ title: '温馨提示', content: '人脸检测失败,请稍后再试', showCancel: false })
})
}).catch(() => {
wx.hideLoading();
wx.showModal({ title: '温馨提示', content: '网络异常,请稍后再试', showCancel: false })
})
}
})
},
handleUseDefaultAvatar() {
wx.showToast({ title: '暂未配置默认形象', icon: 'none' })
},
handleRecord() {
if (this.data.isRecording) return
if (this.recordTimer) {
clearTimeout(this.recordTimer);
clearInterval(this.recordTimer);
}
this.clearVoiceCheckTimer()
this.recordTimer = 0;
this.setData({
isRecording: true,
recordTime: '00:00',
elapsedSeconds: 0,
recordingClass: 'recording-active',
recordUrl: '',
recordDurationMs: 0,
voiceAsrTaskId: '',
voiceChecked: false,
voiceText: '',
voiceStatusText: ''
})
this.startRecordTimer()
},
handleUseDefaultVoice() {
this.clearRecordTimer()
this.clearVoiceCheckTimer()
this.setData({ isRecording: false, recordingClass: '' })
wx.showToast({ title: '暂未配置默认音色', icon: 'none' })
},
handleStopRecord() {
this.clearRecordTimer()
this.recordTimer = 0;
this.setData({ isRecording: false, recordingClass: '' })
wx.getRecorderManager().stop()
},
handleNext() {
if (!this.data.taskinfo || !this.data.taskinfo.task_no) {
wx.showModal({ title: '温馨提示', content: '创作记录不存在', showCancel: false })
return
}
if (!this.data.avatarChecked || !this.data.avatarUrl) {
wx.showModal({ title: '温馨提示', content: '请先上传并通过数字人照片检测', showCancel: false })
return
}
if (!this.data.voiceChecked || !this.data.recordUrl || !this.data.voiceText) {
wx.showModal({ title: '温馨提示', content: '请先录制并通过音色检测', showCancel: false })
return
}
wx.showLoading({ mask: true, title: '保存中' })
saveHumanAssets({
taskId: this.data.taskinfo.task_no,
avatarUrl: this.data.avatarUrl,
voiceUrl: this.data.recordUrl,
voiceText: this.data.voiceText,
durationMs: this.data.recordDurationMs,
asrTaskId: this.data.voiceAsrTaskId
}).then((res: any) => {
wx.hideLoading()
if (!res.code) {
wx.redirectTo({ url: '/pages/aiPackage/bg/index?taskId=' + encodeURIComponent(this.data.taskinfo.task_no) })
} else {
wx.showModal({ title: '温馨提示', content: res.message || '保存失败', showCancel: false })
}
}).catch(() => {
wx.hideLoading()
wx.showModal({ title: '温馨提示', content: '网络异常,请稍后再试', showCancel: false })
})
},
startRecordTimer() {
let that = this;
that.clearRecordTimer()
that.recordTimer = setInterval(() => {
const elapsedSeconds = that.data.elapsedSeconds + 1
that.setData({ elapsedSeconds, recordTime: formatRecordTime(elapsedSeconds) })
}, 1000) as unknown as number
let recorder = wx.getRecorderManager();
recorder.onError(that.onRecordError.bind(that));
recorder.onStop(that.onRecordStop.bind(that))
recorder.start({ duration: 10000, format: 'mp3' });
},
onRecordError(err: any) {
console.log(err)
this.clearRecordTimer()
this.setData({ isRecording: false, recordingClass: '' })
wx.showModal({ title: '温馨提示', content: '录音失败,请重新录制', showCancel: false })
},
onRecordStop(resource: { duration: number, fileSize: number, tempFilePath: string }) {
this.clearRecordTimer()
this.recordTimer = 0;
this.setData({ isRecording: false, recordingClass: '', voiceStatusText: '录音上传中' })
wx.showLoading({ mask: true, title: '上传录音中' })
uploadrecord(resource.tempFilePath).then((res: any) => {
if (res.code) {
wx.hideLoading()
this.setData({ voiceStatusText: '', voiceChecked: false })
wx.showModal({ title: '温馨提示', content: res.message || '录音上传失败', showCancel: false })
return
}
this.setData({
recordUrl: res.data.url,
recordDurationMs: res.data.durationMs || resource.duration || 0,
voiceAsrTaskId: '',
voiceChecked: false,
voiceText: '',
voiceStatusText: '录音识别中'
})
this.pollVoiceCheck('')
}).catch(() => {
wx.hideLoading()
this.setData({ voiceStatusText: '', voiceChecked: false })
wx.showModal({ title: '温馨提示', content: '网络异常,请稍后再试', showCancel: false })
})
},
pollVoiceCheck(asrTaskId: string) {
checkvoice(this.data.recordUrl, asrTaskId).then((res: any) => {
if (res.code === 200) {
wx.hideLoading()
const nextTaskId = res.data && res.data.asrTaskId ? res.data.asrTaskId : asrTaskId
this.setData({ voiceAsrTaskId: nextTaskId, voiceStatusText: '录音识别中' })
this.clearVoiceCheckTimer()
this.voiceCheckTimer = setTimeout(() => {
this.pollVoiceCheck(nextTaskId)
}, 1500) as unknown as number
return
}
wx.hideLoading()
if (!res.code) {
this.clearVoiceCheckTimer()
this.setData({
voiceAsrTaskId: res.data.asrTaskId,
voiceText: res.data.recognizedText,
voiceChecked: true,
voiceStatusText: '录音检测通过'
})
wx.showToast({ title: '音色检测通过', icon: 'success' })
} else {
this.clearVoiceCheckTimer()
this.setData({ voiceChecked: false, voiceStatusText: '' })
wx.showModal({ title: '温馨提示', content: res.message || '录音识别失败,请重新录制', showCancel: false })
}
}).catch(() => {
wx.hideLoading()
this.clearVoiceCheckTimer()
this.setData({ voiceChecked: false, voiceStatusText: '' })
wx.showModal({ title: '温馨提示', content: '录音识别失败,请稍后再试', showCancel: false })
})
},
clearRecordTimer() {
if (!this.recordTimer) return
clearInterval(this.recordTimer)
this.recordTimer = 0
},
clearVoiceCheckTimer() {
if (!this.voiceCheckTimer) return
clearTimeout(this.voiceCheckTimer)
this.voiceCheckTimer = 0
},
})
@@ -0,0 +1,100 @@
<view class="record-page">
<custom-nav content-height="86" padding-x="34">
<view class="record-nav">
<view class="nav-back" bindtap="handleBack"><text></text></view>
<view class="nav-title-wrap">
<text class="nav-title">设置数字人</text>
<text class="nav-subtitle">上传照片和录音,生成你的专属数字人</text>
</view>
<view class="nav-space"></view>
</view>
</custom-nav>
<view class="record-content">
<view class="panel photo-panel">
<text class="panel-title">上传数字人照片</text>
<view class="photo-body">
<view class="photo-preview">
<image class="portrait-bg icon-placeholder" src="{{ avatar }}" mode="aspectFit"></image>
<view class="camera-badge"><image class="camera-icon icon-placeholder" style="width: 60%;height: 60%;" src="/static/images/carram_white.png" mode="aspectFit"></image></view>
</view>
<view class="photo-actions">
<view class="primary-small" bindtap="handleUploadPhoto">上传照片</view>
<view class="ghost-small" bindtap="handleUseDefaultAvatar">使用默认形象</view>
</view>
</view>
<view class="hint-row">
<image class="hint-dot icon-placeholder" src="/static/images/zhuyi1.png" mode="aspectFit"></image>
<text>建议上传清晰正脸照片,生成效果更自然</text>
</view>
<!-- <view wx:if="{{avatarChecked}}" class="asset-status success-status">照片检测通过</view> -->
</view>
<view class="panel voice-panel">
<text class="panel-title">录取专属音色</text>
<view class="voice-body">
<view class="voice-visual {{recordingClass}}">
<view wx:if="{{isRecording}}" class="recording-pill"><text></text><text>录制中</text></view>
<view class="wave-row wave-left">
<text wx:for="{{waveBars}}" wx:key="id" class="{{item.cls}}"></text>
</view>
<view class="mic-wrap">
<view class="pulse-ring ring-one"></view>
<view class="pulse-ring ring-two"></view>
<view class="pulse-ring ring-three"></view>
<view class="mic-core">
<image class="mic-icon icon-placeholder" style="width: 60%;height: 60%;" src="/static/images/mic.png" mode="aspectFit"></image>
</view>
</view>
<view class="wave-row wave-right">
<text wx:for="{{waveBars}}" wx:key="id" class="{{item.cls}}"></text>
</view>
<text class="record-time">{{recordTime}}</text>
</view>
<view class="voice-actions">
<view class="primary-small" wx:if="{{!isRecording}}" bindtap="handleRecord">录取音色</view>
<view class="stop-small" wx:if="{{isRecording}}" bindtap="handleStopRecord">
<image class="stop-dot icon-placeholder" style="width: 40rpx;height: 40rpx;" src="/static/images/stop.png" mode="aspectFit"></image>
<text>停止录制</text>
</view>
<view class="ghost-small" bindtap="handleUseDefaultVoice">使用默认音色</view>
</view>
</view>
<view class="hint-row voice-hint">
<image class="hint-dot icon-placeholder" src="/static/images/zhuyi1.png" mode="aspectFit"></image>
<text>建议录制 10 秒以上,环境安静效果更佳</text>
</view>
<!-- <view wx:if="{{voiceStatusText}}" class="asset-status {{voiceChecked ? 'success-status' : 'pending-status'}}">{{voiceStatusText}}</view> -->
<view class="script-section">
<text class="script-title">朗读以下文案(约10秒)</text>
<view class="script-card">
<text>大家好,我是你的数字人口播助手。今天为大家带来一段精彩的内容介绍。</text>
</view>
</view>
</view>
<view class="default-section">
<text class="default-title">默认选项(可直接体验)</text>
<view class="default-row">
<view class="default-card">
<image class="mini-avatar icon-placeholder" src="https://cdn.zhuangb123.com/chat-square/EB3D71A23725-6CD76-956EFD1DB-0C25B0.jpg" mode="aspectFit"></image>
<text class="default-name">默认数字人</text>
<text class="trial-pill">可直接体验</text>
</view>
<view class="default-card">
<image class="mini-voice icon-placeholder" src="/static/images/ys.png" mode="aspectFit"></image>
<text class="default-name">默认音色</text>
<text class="trial-pill blue-pill">可直接体验</text>
</view>
</view>
</view>
<view class="next-action" bindtap="handleNext">下一步:选择视频背景</view>
</view>
</view>
@@ -0,0 +1,668 @@
page {
min-height: 100vh;
background: #f8faff;
color: #111827;
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.record-page {
width: 750rpx;
min-height: 100vh;
margin: 0 auto;
display: flex;
flex-direction: column;
box-sizing: border-box;
background:
radial-gradient(circle at 10% 0%, rgba(116, 98, 255, 0.08), transparent 34%),
radial-gradient(circle at 88% 10%, rgba(73, 132, 255, 0.08), transparent 30%),
linear-gradient(180deg, #fbfcff 0%, #f7f9ff 46%, #ffffff 100%);
}
.record-nav {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.nav-back,
.nav-space {
width: 60rpx;
height: 60rpx;
flex: 0 0 60rpx;
display: flex;
align-items: center;
}
.nav-back text {
width: 26rpx;
height: 26rpx;
display: block;
border-left: 5rpx solid #111827;
border-bottom: 5rpx solid #111827;
transform: rotate(45deg);
box-sizing: border-box;
}
.nav-title-wrap {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10rpx;
}
.nav-title {
font-size: 34rpx;
line-height: 40rpx;
font-weight: 900;
color: #111827;
}
.nav-subtitle {
max-width: 560rpx;
color: #687086;
font-size: 22rpx;
line-height: 28rpx;
font-weight: 600;
text-align: center;
white-space: nowrap;
}
.record-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 22rpx;
padding: 20rpx 32rpx 30rpx;
box-sizing: border-box;
}
.panel {
width: 100%;
display: flex;
flex-direction: column;
border: 2rpx solid #e6e9ff;
border-radius: 18rpx;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 14rpx 34rpx rgba(80, 92, 170, 0.08);
box-sizing: border-box;
}
.photo-panel {
padding: 24rpx 26rpx 24rpx;
}
.panel-title {
color: #111827;
font-size: 28rpx;
line-height: 36rpx;
font-weight: 900;
}
.photo-body {
margin-top: 14rpx;
display: flex;
align-items: center;
gap: 56rpx;
}
.photo-preview {
width: 306rpx;
height: 224rpx;
display: flex;
align-items: flex-end;
justify-content: center;
border: 2rpx solid #dce1ff;
border-radius: 14rpx;
background: linear-gradient(180deg, #f8fbff 0%, #eef3ff 100%);
overflow: hidden;
box-sizing: border-box;
position: relative;
}
.portrait-bg {
width: 190rpx;
height: 218rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.portrait-head {
width: 120rpx;
height: 138rpx;
display: flex;
align-items: flex-end;
justify-content: center;
border-radius: 60rpx 60rpx 44rpx 44rpx;
background: linear-gradient(160deg, #2e1e19 0%, #684235 56%, #9c6a58 100%);
box-shadow: 0 4rpx 12rpx rgba(85, 50, 44, 0.18);
}
.face {
width: 78rpx;
height: 98rpx;
margin-bottom: 10rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
border-radius: 42rpx 42rpx 36rpx 36rpx;
background: linear-gradient(180deg, #ffe8dc 0%, #ffd0c2 100%);
}
.eye-row {
width: 48rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.eye-row text {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: #251918;
}
.nose {
width: 8rpx;
height: 12rpx;
border-radius: 8rpx;
background: rgba(196, 116, 101, 0.46);
}
.mouth {
width: 24rpx;
height: 7rpx;
border-radius: 0 0 16rpx 16rpx;
background: #ca6975;
}
.hair,
.hair-main {
display: none;
}
.portrait-neck {
width: 32rpx;
height: 22rpx;
background: #ffd3c4;
}
.portrait-cloth {
width: 178rpx;
height: 68rpx;
border-radius: 40rpx 40rpx 0 0;
background: linear-gradient(120deg, #ffffff 0%, #f7f6f2 50%, #ffffff 100%);
}
.camera-badge {
width: 60rpx;
height: 60rpx;
/* margin-bottom: 20rpx; */
display: flex;
align-items: center;
justify-content: center;
border: 4rpx solid rgba(255, 255, 255, 0.88);
border-radius: 50%;
background: rgba(66, 56, 49, 0.72);
box-sizing: border-box;
flex: 0 0 60rpx;
position: absolute;
bottom: 20rpx;
}
.camera-icon {
width: 28rpx;
height: 21rpx;
border: 4rpx solid #ffffff;
border-radius: 5rpx;
box-sizing: border-box;
}
.camera-icon::after {
content: '';
width: 8rpx;
height: 8rpx;
margin: 2rpx auto 0;
display: block;
border: 3rpx solid #ffffff;
border-radius: 50%;
box-sizing: border-box;
}
.photo-actions,
.voice-actions {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 24rpx;
}
.primary-small,
.ghost-small,
.stop-small {
height: 70rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 16rpx;
font-size: 26rpx;
line-height: 32rpx;
font-weight: 900;
box-sizing: border-box;
}
.primary-small {
color: #ffffff;
background: linear-gradient(100deg, #4e84ff 0%, #7d3ff3 100%);
box-shadow: 0 12rpx 24rpx rgba(91, 86, 245, 0.22);
}
.ghost-small,
.stop-small {
border: 2rpx solid #d9dcf2;
background: #fafbff;
color: #596083;
}
.hint-row {
margin-top: 18rpx;
display: flex;
align-items: center;
gap: 8rpx;
color: #7a8196;
font-size: 21rpx;
line-height: 28rpx;
font-weight: 600;
}
.hint-dot {
width: 18rpx;
height: 18rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #b9bfd2;
color: #ffffff;
font-size: 12rpx;
line-height: 16rpx;
font-weight: 900;
}
.voice-panel {
padding: 24rpx 26rpx 24rpx;
}
.voice-body {
margin-top: 12rpx;
display: flex;
align-items: center;
gap: 32rpx;
}
.voice-visual {
width: 340rpx;
min-height: 240rpx;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
.recording-pill {
width: 116rpx;
height: 44rpx;
margin-right: 224rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
border-radius: 22rpx;
color: #ffffff;
background: linear-gradient(90deg, #7380ff 0%, #8a55ed 100%);
box-shadow: 0 8rpx 18rpx rgba(117, 94, 239, 0.3);
font-size: 21rpx;
line-height: 26rpx;
font-weight: 800;
}
.recording-pill text:first-child {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #ffffff;
}
.recording-active .recording-pill text:first-child {
animation: blinkDot 1s ease-in-out infinite;
}
.wave-row {
width: 118rpx;
height: 82rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 7rpx;
}
.wave-row text {
width: 5rpx;
border-radius: 6rpx;
background: linear-gradient(180deg, #5f86ff 0%, #bd67f2 100%);
transform-origin: center;
}
.recording-active .wave-row text:nth-child(2n) {
animation: waveScaleA 0.78s ease-in-out infinite;
}
.recording-active .wave-row text:nth-child(2n + 1) {
animation: waveScaleB 0.92s ease-in-out infinite;
}
.recording-active .wave-row text:nth-child(3n) {
animation-delay: 0.18s;
}
.mic-wrap {
width: 98rpx;
height: 98rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: radial-gradient(circle, rgba(139, 102, 255, 0.13) 0%, rgba(139, 102, 255, 0.05) 68%, transparent 70%);
}
.pulse-ring {
width: 90rpx;
height: 90rpx;
margin-right: -90rpx;
border: 3rpx solid rgba(135, 98, 247, 0.28);
border-radius: 50%;
box-sizing: border-box;
}
.recording-active .ring-one { animation: pulseRing 1.5s ease-out infinite; }
.recording-active .ring-two { animation: pulseRing 1.5s ease-out 0.3s infinite; }
.recording-active .ring-three { animation: pulseRing 1.5s ease-out 0.6s infinite; }
.mic-core {
width: 90rpx;
height: 90rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: linear-gradient(160deg, #77a0ff 0%, #8346f5 100%);
box-shadow: 0 10rpx 24rpx rgba(117, 86, 243, 0.32);
}
.mic-icon {
width: 32rpx;
height: 45rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.mic-icon::before {
content: '';
width: 24rpx;
height: 34rpx;
border-radius: 14rpx;
background: #ffffff;
}
.mic-icon text {
width: 34rpx;
height: 14rpx;
display: block;
border-bottom: 5rpx solid #ffffff;
border-left: 5rpx solid #ffffff;
border-right: 5rpx solid #ffffff;
border-radius: 0 0 18rpx 18rpx;
box-sizing: border-box;
}
.record-time {
width: 100%;
margin-top: 2rpx;
color: #586085;
font-size: 31rpx;
line-height: 38rpx;
font-weight: 600;
text-align: center;
}
.stop-small {
gap: 16rpx;
}
.stop-dot {
width: 16rpx;
height: 16rpx;
border-radius: 3rpx;
background: #ff6b6b;
}
.voice-hint {
margin-top: 8rpx;
}
.script-section {
margin-top: 24rpx;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.script-title {
color: #111827;
font-size: 24rpx;
line-height: 32rpx;
font-weight: 900;
}
.script-card {
min-height: 92rpx;
padding: 18rpx 22rpx;
display: flex;
align-items: center;
border: 2rpx solid #dfe3ff;
border-radius: 10rpx;
background: linear-gradient(180deg, #fbfbff 0%, #f6f7ff 100%);
box-sizing: border-box;
color: #4d557d;
font-size: 24rpx;
line-height: 36rpx;
font-weight: 600;
}
.default-section {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.default-title {
color: #4f47ff;
font-size: 23rpx;
line-height: 30rpx;
font-weight: 900;
}
.default-row {
display: flex;
gap: 10rpx;
}
.default-card {
flex: 1;
height: 88rpx;
min-width: 0;
padding: 10rpx 12rpx;
display: flex;
align-items: center;
gap: 12rpx;
border: 2rpx solid #e4e7f6;
border-radius: 14rpx;
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 10rpx 24rpx rgba(80, 92, 170, 0.08);
box-sizing: border-box;
}
.mini-avatar {
width: 58rpx;
height: 58rpx;
border-radius: 12rpx;
background:
radial-gradient(circle at 50% 32%, #ffe0d2 0 16rpx, transparent 17rpx),
radial-gradient(circle at 50% 35%, #4b2d25 0 24rpx, transparent 25rpx),
linear-gradient(180deg, #eef4ff 0%, #ffffff 100%);
flex: 0 0 58rpx;
}
.mini-voice {
width: 58rpx;
height: 58rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 5rpx;
border-radius: 12rpx;
background: #eef2ff;
flex: 0 0 58rpx;
}
.mini-voice text {
width: 5rpx;
border-radius: 5rpx;
background: linear-gradient(180deg, #5f86ff 0%, #8550f2 100%);
}
.default-name {
flex: 1;
min-width: 0;
color: #111827;
font-size: 23rpx;
line-height: 30rpx;
font-weight: 900;
white-space: nowrap;
}
.trial-pill {
padding: 6rpx 12rpx;
border-radius: 18rpx;
background: #fff3db;
color: #f4a326;
font-size: 18rpx;
line-height: 22rpx;
font-weight: 800;
white-space: nowrap;
}
.blue-pill {
background: #e9f3ff;
color: #5a96f0;
}
.next-action {
height: 78rpx;
margin-top: -2rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 16rpx;
color: #ffffff;
background: linear-gradient(100deg, #4e84ff 0%, #7d3ff3 100%);
box-shadow: 0 12rpx 24rpx rgba(91, 86, 245, 0.24);
font-size: 31rpx;
line-height: 38rpx;
font-weight: 900;
}
@keyframes waveScaleA {
0%, 100% { transform: scaleY(0.62); opacity: 0.62; }
50% { transform: scaleY(1.18); opacity: 1; }
}
@keyframes waveScaleB {
0%, 100% { transform: scaleY(1); opacity: 0.88; }
50% { transform: scaleY(0.55); opacity: 0.55; }
}
@keyframes pulseRing {
0% { transform: scale(0.72); opacity: 0.7; }
100% { transform: scale(1.72); opacity: 0; }
}
@keyframes blinkDot {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.38; transform: scale(0.72); }
}
.bar-22 { height: 22rpx; }
.bar-30 { height: 30rpx; }
.bar-34 { height: 34rpx; }
.bar-38 { height: 38rpx; }
.bar-42 { height: 42rpx; }
.bar-46 { height: 46rpx; }
.bar-48 { height: 48rpx; }
.bar-52 { height: 52rpx; }
.bar-58 { height: 58rpx; }
.bar-62 { height: 62rpx; }
.bar-70 { height: 70rpx; }
.mini-bar-18 { height: 18rpx; }
.mini-bar-28 { height: 28rpx; }
.mini-bar-30 { height: 30rpx; }
.mini-bar-36 { height: 36rpx; }
.mini-bar-46 { height: 46rpx; }
.mini-bar-56 { height: 56rpx; }
.icon-placeholder {
display: block;
flex-shrink: 0;
background: none;
border: 0;
box-shadow: none;
transform: none;
}
.asset-status {
margin-top: 14rpx;
height: 44rpx;
padding: 0 18rpx;
display: inline-flex;
align-items: center;
align-self: flex-start;
border-radius: 22rpx;
font-size: 21rpx;
line-height: 28rpx;
font-weight: 800;
box-sizing: border-box;
}
.success-status {
color: #16854f;
background: #e9f8f0;
border: 2rpx solid #bdebd3;
}
.pending-status {
color: #5a5f89;
background: #f1f3ff;
border: 2rpx solid #dfe3ff;
}