ea
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"custom-nav": "/components/custom-nav/custom-nav"
|
||||
},
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user