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"
}
+103
View File
@@ -0,0 +1,103 @@
import { backgroundTemplates, updateTaskBackground } from "../../../utils/request"
type Category = {
id: string
name: string
}
type BgTemplate = {
id: number
name: string
scene: string
category: string
coverUrl?: string
imageUrl?: string
videoUrl?: string
isRecommend?: number
}
function filterTemplates(category: string, templates: BgTemplate[]) {
if (category === 'all') return templates
return templates.filter((item) => item.category === category)
}
Page({
data: {
taskId: '',
categories: [] as Category[],
activeCategory: 'all',
selectedTemplate: 0,
allTemplates: [] as BgTemplate[],
templates: [] as BgTemplate[],
loading: false,
},
onLoad(options: any) {
this.setData({ taskId: options.taskId || options.taskid || '' })
this.loadBackgroundTemplates()
},
loadBackgroundTemplates() {
this.setData({ loading: true })
backgroundTemplates().then((res: any) => {
const categories = res && !res.code && res.data ? (res.data.categories || []) : []
const templates = res && !res.code && res.data ? (res.data.templates || []) : []
const selectedTemplate = templates.length ? Number(templates[0].id) : 0
this.setData({
categories,
allTemplates: templates,
templates: filterTemplates(this.data.activeCategory, templates),
selectedTemplate,
loading: false,
})
}).catch(() => {
this.setData({ loading: false })
wx.showModal({ title: '温馨提示', content: '背景模板加载失败,请稍后再试', showCancel: false })
})
},
handleBack() {
wx.navigateBack({
fail: () => wx.navigateTo({ url: '/pages/aiPackage/record/index' }),
})
},
handleCategorySelect(e: WechatMiniprogram.TouchEvent) {
const activeCategory = e.currentTarget.dataset.id
if (!activeCategory) return
const templates = filterTemplates(activeCategory, this.data.allTemplates)
this.setData({ activeCategory, templates })
},
handleTemplateSelect(e: WechatMiniprogram.TouchEvent) {
const selectedTemplate = Number(e.currentTarget.dataset.id || 0)
if (selectedTemplate <= 0) return
this.setData({ selectedTemplate })
},
handleConfirm() {
if (!this.data.taskId) {
wx.showModal({ title: '温馨提示', content: '创作记录不存在', showCancel: false })
return
}
if (this.data.selectedTemplate <= 0) {
wx.showModal({ title: '温馨提示', content: '请选择背景模板', showCancel: false })
return
}
wx.showLoading({ mask: true, title: '保存背景中' })
updateTaskBackground(this.data.taskId, this.data.selectedTemplate).then((res: any) => {
wx.hideLoading()
if (!res.code) {
wx.redirectTo({ url: '/pages/aiPackage/gen/index?taskId=' + encodeURIComponent(this.data.taskId) })
} else {
wx.showModal({ title: '温馨提示', content: res.message || '背景保存失败', showCancel: false })
}
}).catch(() => {
wx.hideLoading()
wx.showModal({ title: '温馨提示', content: '网络异常,请稍后再试', showCancel: false })
})
},
})
+44
View File
@@ -0,0 +1,44 @@
<view class="bg-page">
<custom-nav padding-x="34">
<view class="bg-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="bg-content">
<scroll-view class="category-scroll" scroll-x="true" enhanced="true" show-scrollbar="false">
<view class="category-list">
<view wx:for="{{categories}}" wx:key="id"
class="category-item {{activeCategory === item.id ? 'category-active' : ''}}" data-id="{{item.id}}"
bindtap="handleCategorySelect">{{item.name}}</view>
</view>
</scroll-view>
<view class="template-grid">
<scroll-view class="auto-flex" enable-flex="true" scroll-y="true">
<view wx:for="{{templates}}" wx:key="id"
class="template-card {{selectedTemplate === item.id ? 'template-active' : ''}}" data-id="{{item.id}}"
bindtap="handleTemplateSelect">
<view class="scene-preview scene-{{item.scene}}">
<image class="scene-placeholder icon-placeholder" src="/static/images/my/image.png" mode="aspectFit">
</image>
<view class="select-circle top-circle {{selectedTemplate === item.id ? 'circle-active' : ''}}"><text></text>
</view>
</view>
<view class="template-foot">
<text>{{item.name}}</text>
<!-- <view class="select-circle foot-circle {{selectedTemplate === item.id ? 'circle-active' : ''}}">
<text></text></view> -->
</view>
</view>
</scroll-view>
</view>
<view class="confirm-action" bindtap="handleConfirm">确认背景,去生成</view>
</view>
</view>
+367
View File
@@ -0,0 +1,367 @@
page {
min-height: 100vh;
background: #f8faff;
color: #111827;
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.bg-page {
width: 750rpx;
height: 100vh;
margin: 0 auto;
display: flex;
flex-direction: column;
box-sizing: border-box;
background:
radial-gradient(circle at 12% 0%, rgba(104, 126, 255, 0.08), transparent 34%),
radial-gradient(circle at 88% 10%, rgba(130, 86, 255, 0.08), transparent 30%),
linear-gradient(180deg, #fbfcff 0%, #f6f8ff 44%, #ffffff 100%);
}
.bg-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 {
color: #111827;
font-size: 34rpx;
line-height: 40rpx;
font-weight: 900;
}
.nav-subtitle {
max-width: 560rpx;
color: #6c7284;
font-size: 22rpx;
line-height: 28rpx;
font-weight: 600;
text-align: center;
white-space: nowrap;
}
.bg-content {
flex: 1;
display: flex;
overflow-y: hidden;
flex-direction: column;
padding: 20rpx 18rpx 34rpx;
box-sizing: border-box;
}
.category-scroll {
width: 100%;
flex: 0 0 auto;
white-space: nowrap;
}
.auto-flex {
height: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.category-list {
display: flex;
align-items: center;
gap: 10rpx;
padding: 2rpx 0 26rpx;
}
.category-item {
height: 46rpx;
padding: 0 18rpx;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid #e0e4f2;
border-radius: 24rpx;
background: #ffffff;
color: #656d80;
font-size: 21rpx;
line-height: 28rpx;
font-weight: 800;
box-sizing: border-box;
white-space: nowrap;
flex: 0 0 auto;
}
.category-active {
border-color: #7462ff;
color: #ffffff;
background: linear-gradient(100deg, #6b83ff 0%, #754df3 100%);
box-shadow: 0 8rpx 18rpx rgba(101, 85, 246, 0.2);
}
.template-grid {
display: flex;
flex-wrap: wrap;
flex: 1;
overflow-y: auto;
justify-content: space-between;
row-gap: 30rpx;
padding: 0 0 34rpx;
box-sizing: border-box;
}
.template-card {
width: 326rpx;
display: flex;
flex-direction: column;
border: 2rpx solid transparent;
border-radius: 14rpx;
margin-bottom: 20rpx;
background: #ffffff;
box-shadow: 0 12rpx 30rpx rgba(83, 92, 151, 0.12);
box-sizing: border-box;
overflow: hidden;
}
.template-active {
border-color: #765eff;
box-shadow: 0 12rpx 30rpx rgba(101, 85, 246, 0.22);
}
.scene-preview {
height: 166rpx;
padding: 12rpx 14rpx;
display: flex;
flex-wrap: wrap;
align-content: space-between;
justify-content: space-between;
box-sizing: border-box;
overflow: hidden;
}
.scene-window,
.scene-light,
.scene-plant,
.scene-main {
flex: 0 0 auto;
}
.scene-window {
width: 96rpx;
height: 56rpx;
border-radius: 4rpx;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.88) 0%, rgba(225, 238, 250, 0.72) 100%);
box-shadow: inset 0 0 0 2rpx rgba(255, 255, 255, 0.5);
}
.scene-light {
width: 54rpx;
height: 66rpx;
border-radius: 28rpx 28rpx 10rpx 10rpx;
background: linear-gradient(180deg, rgba(255, 246, 210, 0.9), rgba(226, 170, 92, 0.35));
}
.scene-plant {
width: 44rpx;
height: 62rpx;
border-radius: 28rpx 28rpx 8rpx 8rpx;
background:
radial-gradient(circle at 30% 28%, #7db66d 0 10rpx, transparent 11rpx),
radial-gradient(circle at 68% 36%, #679f5d 0 12rpx, transparent 13rpx),
linear-gradient(180deg, transparent 0 42rpx, #b48354 43rpx 100%);
}
.plant-right {
transform: scale(0.82);
}
.scene-main {
width: 160rpx;
height: 54rpx;
border-radius: 10rpx 10rpx 4rpx 4rpx;
background: rgba(255, 255, 255, 0.72);
box-shadow: inset 0 -8rpx 0 rgba(188, 158, 120, 0.18);
}
.scene-live {
background:
linear-gradient(90deg, rgba(255, 255, 255, 0.28) 0 2rpx, transparent 2rpx 100%),
linear-gradient(180deg, #fff4df 0%, #f9ead4 48%, #dfc09a 100%);
}
.scene-office {
background:
linear-gradient(90deg, rgba(150, 168, 184, 0.16) 0 2rpx, transparent 2rpx 64rpx),
linear-gradient(180deg, #eff6fb 0%, #dfe7ec 52%, #c8d2d9 100%);
}
.scene-office .scene-main {
width: 178rpx;
background: linear-gradient(180deg, #f8fbfd 0%, #cbd7df 100%);
}
.scene-classroom {
background: linear-gradient(180deg, #fff4df 0%, #f4dfbf 52%, #d6b98c 100%);
}
.scene-classroom .scene-main {
width: 184rpx;
height: 58rpx;
background:
linear-gradient(180deg, #4d7555 0%, #315b3b 100%);
box-shadow: 0 18rpx 0 rgba(140, 98, 56, 0.32);
}
.scene-emotion {
background: linear-gradient(180deg, #ffe2be 0%, #e5b379 52%, #b57945 100%);
}
.scene-emotion .scene-main,
.scene-life .scene-main {
width: 178rpx;
height: 52rpx;
border-radius: 30rpx 30rpx 12rpx 12rpx;
background: linear-gradient(180deg, #f5dfc9 0%, #d1a78a 100%);
}
.scene-product {
background: linear-gradient(180deg, #f8dbb5 0%, #dfb076 55%, #b9844f 100%);
}
.scene-product .scene-main {
width: 174rpx;
height: 58rpx;
border-radius: 50%;
background: radial-gradient(circle at 50% 34%, #fff1d7 0%, #c8965e 66%, #9e6e3c 100%);
}
.scene-life {
background: linear-gradient(180deg, #fff6e9 0%, #ead8bd 54%, #c9ab81 100%);
}
.template-foot {
height: 62rpx;
padding: 0 18rpx;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12rpx;
box-sizing: border-box;
}
.template-foot text {
flex: 1;
min-width: 0;
color: #222633;
font-size: 25rpx;
line-height: 32rpx;
font-weight: 900;
white-space: nowrap;
}
.template-active .template-foot text {
color: #6559ff;
}
.select-circle {
width: 34rpx;
height: 34rpx;
display: flex;
align-items: center;
justify-content: center;
border: 3rpx solid #d4d8e8;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
box-sizing: border-box;
flex: 0 0 34rpx;
}
.top-circle {
margin-left: auto;
}
.foot-circle {
width: 32rpx;
height: 32rpx;
}
.circle-active {
border-color: #7363ff;
background: linear-gradient(135deg, #7089ff 0%, #7a4df4 100%);
}
.circle-active text {
width: 14rpx;
height: 8rpx;
display: block;
border-left: 4rpx solid #ffffff;
border-bottom: 4rpx solid #ffffff;
transform: rotate(-45deg);
box-sizing: border-box;
}
.confirm-action {
height: 86rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 16rpx;
color: #ffffff;
background: linear-gradient(100deg, #4e84ff 0%, #7d3ff3 100%);
box-shadow: 0 14rpx 28rpx rgba(91, 86, 245, 0.24);
font-size: 29rpx;
line-height: 36rpx;
font-weight: 900;
}
.scene-placeholder {
width: 100%;
height: 100%;
}
.icon-placeholder {
display: block;
flex-shrink: 0;
background: none;
border: 0;
box-shadow: none;
transform: none;
}
.scene-preview {
position: relative;
padding: 0;
background: #ffffff;
}
.top-circle {
position: absolute;
top: 12rpx;
right: 14rpx;
}
@@ -0,0 +1,6 @@
{
"usingComponents": {
"custom-nav": "/components/custom-nav/custom-nav"
},
"navigationStyle": "custom"
}
+74
View File
@@ -0,0 +1,74 @@
import { taskinfo } from "../../../utils/request"
function createTaskId() {
return `task_${Date.now()}`
}
Page({
data: {
taskinfo: null as any,
userinfo: null,
price: 40,
start: false
},
onLoad(ops: { taskId: string }) {
this.setData({ userinfo: wx.getStorageSync('userInfo') })
taskinfo(ops.taskId).then((res: any) => {
console.log(res);
if (!res.code) {
this.setData({ taskinfo: res.data });
}
})
},
handleBack() {
wx.navigateBack({
fail: () => wx.navigateTo({ url: '/pages/aiPackage/bg/index' }),
})
},
handleViewScript() {
wx.showToast({ title: '展示完整文案', icon: 'none' })
},
handleGenerate() {
if (!this.data.taskinfo) {
return;
}
let task_no = this.data.taskinfo.task_no || '';
const detailUrl = `/pages/historyPackage/historyDetail/index?status=processing&taskId=${encodeURIComponent(task_no)}`
let userinfo = wx.getStorageSync('userinfo');
if (userinfo.diamond < this.data.price) {
wx.showModal({ title: '温馨提示', content: '余额不足' })
return;
}
this.setData({ start: true })
// wx.redirectTo({ url: detailUrl })
},
handleDownload() {
// wx.showToast({ title: '开始下载', icon: 'none' })
wx.downloadFile({
url: '',
success(res) {
wx.showModal({ title: '温馨提示', content: '下载成功' })
}
})
},
handleRegenerate() {
// wx.showToast({ title: '重新生成', icon: 'none' })
},
handleHome() {
wx.switchTab({
url: '/pages/index/index',
})
},
})
+105
View File
@@ -0,0 +1,105 @@
<view class="gen-page">
<custom-nav content-height="62" padding-x="34">
<view class="gen-nav">
<view class="nav-back" bindtap="handleBack"><text></text></view>
<text class="nav-title">生成视频</text>
<view class="coin-pill">
<!-- <image class="coin-icon icon-placeholder" src="/static/images/my/image.png" mode="aspectFit"></image>
<text>金币</text>
<text class="coin-count">128</text> -->
</view>
</view>
</custom-nav>
<view class="gen-content">
<view class="confirm-card">
<text class="card-title">生成确认</text>
<view class="info-list">
<view class="info-row avatar-row">
<text class="info-label">数字人形象</text>
<image class="small-avatar icon-placeholder" src="{{ taskinfo.avatar.image_url }}" mode="aspectFit"></image>
</view>
<view class="info-row">
<text class="info-label">背景模板</text>
<text class="info-value">清新直播间</text>
</view>
<view class="info-row">
<text class="info-label">音色</text>
<view class="voice-value">
<image class="voice-icon icon-placeholder" src="/static/images/yinse.png" mode="aspectFit"></image>
<text>已上传专属音色</text>
</view>
</view>
<view class="info-row script-row">
<text class="info-label">文案摘要</text>
<view class="script-summary">
<text>{{ taskinfo.script_text }}</text>
<view class="view-script" bindtap="handleViewScript">
<text>查看完整文案</text>
<text class="right-arrow"></text>
</view>
</view>
</view>
</view>
</view>
<view class="cost-card" wx:if="{{!start}}">
<view class="cost-row">
<text>本次生成消耗</text>
<view class="amount"><text>{{price}}</text><text>金币</text></view>
</view>
<view class="cost-row">
<text>当前余额</text>
<view class="balance-line">
<view class="amount"><text>{{userinfo.diamond}}</text><text>金币</text></view>
<view class="{{ userinfo.diamond < price ?'enough-waring': 'enough'}}">
<text></text><text>{{userinfo.diamond < price? '余额不足' : '余额充足'}}</text>
</view>
</view>
</view>
<view class="generate-action" bindtap="handleGenerate">
<image class="sparkle icon-placeholder" src="/static/images/filter-3-filled.png" mode="aspectFit"></image>
<text>一键生成数字人视频</text>
</view>
</view>
<view wx:if="{{start}}" style="display:flex;flex-direction: column;gap: 30rpx;">
<view class="result-title">
<image class="result-icon icon-placeholder" src="/static/images/my/image.png" mode="aspectFit"></image>
<text>生成结果</text>
</view>
<view class="video-preview">
<view class="preview-bg">
<image class="preview-plant plant-left icon-placeholder" src="/static/images/my/image.png" mode="aspectFit">
</image>
<image class="preview-plant plant-right icon-placeholder" src="/static/images/my/image.png" mode="aspectFit">
</image>
<image class="preview-avatar icon-placeholder" src="/static/images/my/image.png" mode="aspectFit"></image>
<image class="play-btn icon-placeholder" src="/static/images/my/image.png" mode="aspectFit"></image>
</view>
</view>
<view class="bottom-actions">
<view class="action-primary" bindtap="handleDownload">
<image class="download-icon icon-placeholder" src="/static/images/download.png" mode="aspectFit"></image>
<text>下载视频</text>
</view>
<view class="action-outline" bindtap="handleRegenerate">
<image class="refresh-icon icon-placeholder" src="/static/images/currency-exchange.png" mode="aspectFit">
</image>
<text>重新生成</text>
</view>
<view class="action-outline" bindtap="handleHome">
<image class="home-icon icon-placeholder" src="/static/images/to_index.png" mode="aspectFit"></image>
<text>返回首页</text>
</view>
</view>
<view class="save-tip">
<image class="tip-icon icon-placeholder" src="/static/images/info-circle.png" mode="aspectFit"></image>
<text>视频已为你自动保存到【创作记录】</text>
</view>
</view>
</view>
</view>
+590
View File
@@ -0,0 +1,590 @@
page {
min-height: 100vh;
background: #f8faff;
color: #111827;
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.gen-page {
width: 750rpx;
min-height: 100vh;
margin: 0 auto;
display: flex;
flex-direction: column;
box-sizing: border-box;
background:
radial-gradient(circle at 12% 0%, rgba(104, 126, 255, 0.08), transparent 34%),
radial-gradient(circle at 88% 8%, rgba(130, 86, 255, 0.08), transparent 30%),
linear-gradient(180deg, #fbfcff 0%, #f6f8ff 46%, #ffffff 100%);
}
.gen-nav {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
flex: 0 0 60rpx;
}
.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 {
flex: 1;
min-width: 0;
color: #111827;
font-size: 34rpx;
line-height: 42rpx;
font-weight: 900;
text-align: center;
}
.coin-pill {
height: 50rpx;
padding: 0 16rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
/* border: 2rpx solid #eef0fb; */
border-radius: 28rpx;
/* background: rgba(255, 255, 255, 0.88); */
/* box-shadow: 0 8rpx 18rpx rgba(93, 102, 158, 0.12); */
color: #111827;
font-size: 22rpx;
line-height: 28rpx;
font-weight: 800;
flex: 0 0 auto;
}
.coin-icon {
width: 30rpx;
height: 30rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
color: #ffffff;
background: linear-gradient(135deg, #ffd45d 0%, #ffa51f 100%);
font-size: 17rpx;
font-weight: 900;
}
.coin-count {
color: #6657ff;
}
.gen-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 18rpx;
padding: 30rpx;
box-sizing: border-box;
justify-content: space-between;
}
.confirm-card,
.cost-card {
width: 100%;
display: flex;
flex-direction: column;
border: 2rpx solid #e6e9ff;
border-radius: 16rpx;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 14rpx 34rpx rgba(80, 92, 170, 0.08);
box-sizing: border-box;
}
.confirm-card {
padding: 22rpx 28rpx 10rpx;
}
.card-title {
color: #111827;
font-size: 27rpx;
line-height: 34rpx;
font-weight: 900;
padding-bottom: 18rpx;
border-bottom: 2rpx solid #edf0f8;
}
.info-list {
display: flex;
flex-direction: column;
}
.info-row {
min-height: 62rpx;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
border-bottom: 2rpx solid #edf0f8;
}
.info-row:last-child {
border-bottom: 0;
}
.avatar-row {
min-height: 72rpx;
}
.info-label {
flex: 0 0 160rpx;
color: #303748;
font-size: 24rpx;
line-height: 32rpx;
font-weight: 900;
}
.info-value,
.voice-value,
.script-summary {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
justify-content: flex-end;
color: #41495b;
font-size: 24rpx;
line-height: 32rpx;
font-weight: 700;
text-align: right;
}
.small-avatar {
width: 58rpx;
height: 58rpx;
border-radius: 10rpx;
background:
radial-gradient(circle at 50% 33%, #ffe1d2 0 17rpx, transparent 18rpx),
radial-gradient(circle at 50% 34%, #33201b 0 24rpx, transparent 25rpx),
linear-gradient(180deg, #eef4ff 0%, #ffffff 100%);
flex: 0 0 58rpx;
}
.voice-value {
gap: 10rpx;
}
.voice-icon {
width: 28rpx;
height: 28rpx;
display: flex;
align-items: center;
justify-content: center;
color: #6d58ff;
flex: 0 0 28rpx;
}
.voice-icon::before,
.voice-icon::after,
.voice-icon text {
content: '';
width: 5rpx;
border-radius: 5rpx;
background: #6d58ff;
}
.voice-icon::before { height: 18rpx; }
.voice-icon text { height: 28rpx; margin: 0 4rpx; display: block; }
.voice-icon::after { height: 20rpx; }
.script-row {
align-items: flex-start;
padding: 16rpx 0;
}
.script-summary {
flex-direction: column;
align-items: flex-end;
gap: 10rpx;
line-height: 34rpx;
}
.view-script {
display: flex;
align-items: center;
gap: 10rpx;
color: #7062ff;
font-size: 23rpx;
font-weight: 800;
}
.right-arrow {
width: 14rpx;
height: 14rpx;
border-right: 4rpx solid #7062ff;
border-top: 4rpx solid #7062ff;
transform: rotate(45deg);
box-sizing: border-box;
}
.cost-card {
padding: 12rpx 24rpx 22rpx;
background: linear-gradient(100deg, rgba(255, 255, 255, 0.98) 0%, rgba(239, 255, 248, 0.92) 100%);
margin-bottom: calc(10rpx + env(safe-area-inset-bottom));
}
.cost-row {
min-height: 52rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 2rpx solid rgba(224, 229, 242, 0.75);
color: #303748;
font-size: 24rpx;
line-height: 32rpx;
font-weight: 900;
}
.amount,
.balance-line,
.enough {
display: flex;
align-items: center;
}
.amount {
gap: 10rpx;
color: #111827;
}
.amount text:first-child {
font-size: 32rpx;
line-height: 38rpx;
font-weight: 900;
}
.amount text:last-child {
font-size: 23rpx;
font-weight: 800;
}
.balance-line {
gap: 22rpx;
}
.enough {
gap: 8rpx;
color: #40bf83;
font-size: 21rpx;
line-height: 28rpx;
font-weight: 900;
}
.enough-waring {
gap: 8rpx;
color: red;
font-size: 21rpx;
line-height: 28rpx;
font-weight: 900;
}
.enough text:first-child {
width: 26rpx;
height: 26rpx;
border-radius: 50%;
background: #42c982;
}
.enough text:first-child::after {
content: '';
width: 11rpx;
height: 7rpx;
margin: 7rpx auto 0;
display: block;
border-left: 3rpx solid #ffffff;
border-bottom: 3rpx solid #ffffff;
transform: rotate(-45deg);
box-sizing: border-box;
}
.generate-action {
height: 66rpx;
margin-top: 14rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
border-radius: 12rpx;
color: #ffffff;
background: linear-gradient(100deg, #4e84ff 0%, #7d3ff3 100%);
box-shadow: 0 12rpx 24rpx rgba(91, 86, 245, 0.22);
font-size: 27rpx;
line-height: 34rpx;
font-weight: 900;
}
.sparkle {
width: 28rpx;
height: 28rpx;
background:
linear-gradient(90deg, transparent 42%, #ffffff 43% 57%, transparent 58%),
linear-gradient(0deg, transparent 42%, #ffffff 43% 57%, transparent 58%);
transform: rotate(45deg);
}
.result-title {
margin-top: 14rpx;
display: flex;
align-items: center;
gap: 10rpx;
color: #3f4dff;
font-size: 25rpx;
line-height: 32rpx;
font-weight: 900;
}
.result-icon {
width: 25rpx;
height: 25rpx;
border: 4rpx solid #625aff;
border-radius: 8rpx;
box-sizing: border-box;
}
.video-preview {
width: 100%;
height: 274rpx;
border-radius: 14rpx;
overflow: hidden;
box-shadow: 0 12rpx 30rpx rgba(83, 92, 151, 0.13);
}
.preview-bg {
width: 100%;
height: 100%;
display: flex;
align-items: flex-end;
justify-content: center;
background:
linear-gradient(90deg, rgba(255,255,255,0.36) 0 3rpx, transparent 3rpx 64rpx),
linear-gradient(180deg, #fff4e3 0%, #f1dcc3 58%, #d9b98f 100%);
}
.preview-plant {
width: 86rpx;
height: 126rpx;
margin-bottom: 18rpx;
border-radius: 50rpx 50rpx 12rpx 12rpx;
background:
radial-gradient(circle at 30% 24%, #79ad65 0 22rpx, transparent 23rpx),
radial-gradient(circle at 66% 32%, #6aa05f 0 24rpx, transparent 25rpx),
radial-gradient(circle at 50% 50%, #7eb86a 0 25rpx, transparent 26rpx),
linear-gradient(180deg, transparent 0 82rpx, #a9794d 83rpx 100%);
}
.plant-left {
margin-right: 76rpx;
}
.plant-right {
margin-left: 76rpx;
}
.preview-avatar {
width: 176rpx;
height: 236rpx;
margin-bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
}
.avatar-head {
width: 116rpx;
height: 130rpx;
display: flex;
align-items: flex-end;
justify-content: center;
border-radius: 60rpx 60rpx 42rpx 42rpx;
background: linear-gradient(160deg, #241a18 0%, #604038 60%, #8a6258 100%);
}
.avatar-face {
width: 78rpx;
height: 92rpx;
margin-bottom: 10rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12rpx;
border-radius: 42rpx 42rpx 34rpx 34rpx;
background: linear-gradient(180deg, #ffe6d9 0%, #ffd0c2 100%);
}
.avatar-eyes {
width: 48rpx;
display: flex;
justify-content: space-between;
}
.avatar-eyes text {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: #241a18;
}
.avatar-mouth {
width: 26rpx;
height: 8rpx;
border-radius: 0 0 16rpx 16rpx;
background: #ca6975;
}
.avatar-body {
width: 160rpx;
height: 106rpx;
border-radius: 46rpx 46rpx 0 0;
background: linear-gradient(120deg, #ffffff 0%, #f5f4f1 48%, #ffffff 100%);
}
.play-btn {
width: 74rpx;
height: 74rpx;
margin-left: -126rpx;
margin-bottom: 54rpx;
display: flex;
align-items: center;
justify-content: center;
border: 4rpx solid rgba(255, 255, 255, 0.82);
border-radius: 50%;
background: rgba(49, 43, 38, 0.48);
box-sizing: border-box;
}
.play-btn text {
width: 0;
height: 0;
margin-left: 6rpx;
border-top: 16rpx solid transparent;
border-bottom: 16rpx solid transparent;
border-left: 24rpx solid #ffffff;
}
.bottom-actions {
display: flex;
gap: 18rpx;
}
.action-primary,
.action-outline {
height: 82rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
border-radius: 14rpx;
font-size: 24rpx;
line-height: 31rpx;
font-weight: 900;
box-sizing: border-box;
flex: 1;
}
.action-primary {
color: #ffffff;
background: linear-gradient(100deg, #5a80ff 0%, #764df3 100%);
box-shadow: 0 12rpx 24rpx rgba(91, 86, 245, 0.18);
}
.action-outline {
color: #625aff;
border: 2rpx solid #dce0f5;
background: #ffffff;
}
.download-icon {
width: 24rpx;
height: 24rpx;
border-left: 4rpx solid #ffffff;
border-bottom: 4rpx solid #ffffff;
transform: rotate(-45deg);
box-sizing: border-box;
}
.refresh-icon {
width: 24rpx;
height: 24rpx;
border: 4rpx solid #625aff;
border-right-color: transparent;
border-radius: 50%;
box-sizing: border-box;
}
.home-icon {
width: 25rpx;
height: 22rpx;
border: 4rpx solid #625aff;
border-top: 0;
border-radius: 4rpx;
box-sizing: border-box;
transform: translateY(4rpx);
}
.home-icon::before {
content: '';
width: 18rpx;
height: 18rpx;
display: block;
border-left: 4rpx solid #625aff;
border-top: 4rpx solid #625aff;
transform: rotate(45deg) translate(-4rpx, -13rpx);
box-sizing: border-box;
}
.save-tip {
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
color: #8d94a5;
font-size: 23rpx;
line-height: 30rpx;
font-weight: 600;
margin-bottom: calc(10rpx + env(safe-area-inset-bottom));
}
.tip-icon {
width: 24rpx;
height: 24rpx;
display: flex;
align-items: center;
justify-content: center;
border: 3rpx solid #9aa1b2;
border-radius: 50%;
font-size: 16rpx;
line-height: 18rpx;
font-weight: 900;
box-sizing: border-box;
}
.icon-placeholder {
display: block;
flex-shrink: 0;
background: none;
border: 0;
box-shadow: none;
transform: none;
}
@@ -0,0 +1,6 @@
{
"usingComponents": {
"custom-nav": "/components/custom-nav/custom-nav"
},
"navigationStyle": "custom"
}
@@ -0,0 +1,137 @@
import { polishScript, saveScript, taskinfo } from "../../../utils/request"
type PolishStyle = {
id: string
name: string
icon: string
activeIcon: string
}
const styles: PolishStyle[] = [
{ id: 'natural', name: '自然口播', icon: '/static/images/Frame-15355646363.png', activeIcon: '/static/images/Frame-1561268387.png' },
{ id: 'goods', name: '带货风格', icon: '/static/images/Frame-1561268385.png', activeIcon: '/static/images/Frame-1561268389.png' },
{ id: 'emotion', name: '情感风格', icon: '/static/images/Frame-1561268384.png', activeIcon: '/static/images/Frame-1561268388.png' },
{ id: 'teach', name: '专业讲解', icon: '/static/images/Frame-1561268383.png', activeIcon: '/static/images/Frame-1561268390.png' },
{ id: 'viral', name: '爆款短视频', icon: '/static/images/Frame-1561268382.png', activeIcon: '/static/images/Frame-1561268386.png' },
]
const styleMap: Record<string, string> = {
natural: 'friendly',
goods: 'product',
emotion: 'rewrite',
teach: 'professional',
viral: 'rewrite',
}
Page({
data: {
styles,
selectedStyle: 'natural',
taskinfo: null as any,
inEdit: false,
},
onLoad(ops: any) {
taskinfo(ops.taskId).then((res: any) => {
if (!res.code) {
this.setData({ taskinfo: res.data })
} else {
wx.showModal({ title: '温馨提示', content: res.message || '获取文案失败', showCancel: false })
}
})
},
handleBack() {
wx.navigateBack({
fail: () => wx.switchTab({ url: '/pages/index/index' }),
})
},
handleStyleSelect(e: WechatMiniprogram.TouchEvent) {
const selectedStyle = e.currentTarget.dataset.id
if (!selectedStyle) return
this.setData({ selectedStyle })
},
handleEdit() {
if (!this.data.taskinfo) return
this.setData({ inEdit: !this.data.inEdit })
},
changeContent(e: any) {
const taskinfo = this.data.taskinfo
if (!taskinfo) return
taskinfo.script_text = e.detail.value
this.setData({ taskinfo })
},
handleRegenerate() {
const taskinfo = this.data.taskinfo
if (!taskinfo) return
const content = taskinfo.script_text || taskinfo.original_script || ''
if (!content) {
wx.showToast({ title: '暂无可润色文案', icon: 'none' })
return
}
wx.showLoading({ title: '润色中' })
polishScript(taskinfo.task_no, content, styleMap[this.data.selectedStyle] || 'optimize').then((res: any) => {
wx.hideLoading()
if (res.code) {
wx.showModal({ title: '温馨提示', content: res.message || '润色失败', showCancel: false })
return
}
taskinfo.script_text = res.data.scriptText
this.setData({ taskinfo, inEdit: false })
wx.showToast({ title: '已重新润色', icon: 'success' })
}).catch(() => {
wx.hideLoading()
wx.showModal({ title: '温馨提示', content: '网络异常,请稍后再试', showCancel: false })
})
},
handleConfirm() {
this.saveCurrentScript(true)
},
saveCurrentScript(next: boolean) {
const taskinfo = this.data.taskinfo
if (!taskinfo) return
const content = (taskinfo.script_text || '').trim()
if (!content) {
wx.showToast({ title: '文案不能为空', icon: 'none' })
return
}
wx.showLoading({ title: '保存中' })
saveScript(taskinfo.task_no, content).then((res: any) => {
wx.hideLoading()
if (res.code) {
wx.showModal({ title: '温馨提示', content: res.message || '保存失败', showCancel: false })
return
}
taskinfo.script_text = res.data.scriptText
this.setData({ taskinfo, inEdit: false })
if (next) {
wx.redirectTo({
url: '/pages/aiPackage/record/index?taskid=' + taskinfo.task_no,
})
} else {
wx.showToast({ title: '已保存', icon: 'success' })
}
}).catch(() => {
wx.hideLoading()
wx.showModal({ title: '温馨提示', content: '网络异常,请稍后再试', showCancel: false })
})
},
})
@@ -0,0 +1,68 @@
<view class="preview-page">
<custom-nav content-height="86" padding-x="34">
<view class="preview-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="preview-content">
<view class="panel original-panel">
<text class="panel-title">原文案</text>
<view class="script-text original-text">
<text>{{taskinfo.original_script}}</text>
</view>
<text class="count-text">共 {{taskinfo.original_script.length}} 字</text>
</view>
<view class="panel ai-panel">
<view class="panel-head">
<view class="title-line">
<text class="panel-title">AI 优化后文案</text>
<text class="status-pill">已优化</text>
</view>
<view class="edit-btn" bindtap="handleEdit">
<image class="edit-icon icon-placeholder" src="/static/images/edit.png" mode="aspectFit"></image>
<text>{{inEdit?'保存':'编辑'}}</text>
</view>
</view>
<view class="optimized-box">
<text wx:if="{{inEdit == false}}">{{taskinfo.script_text}}</text>
<textarea bindinput="changeContent" wx:else value="{{taskinfo.script_text}}"></textarea>
<text class="optimized-count">共 {{taskinfo.script_text.length}} 字</text>
</view>
</view>
<view class="panel style-panel">
<text class="panel-title">选择润色风格</text>
<view class="style-list">
<view
wx:for="{{styles}}"
wx:key="id"
class="style-item {{selectedStyle === item.id ? 'style-item-active' : ''}}"
data-id="{{item.id}}"
bindtap="handleStyleSelect"
>
<image class="style-icon style-icon-{{item.id}} icon-placeholder" src="{{selectedStyle == item.id ? item.activeIcon :item.icon}}" mode="aspectFit"></image>
<text>{{item.name}}</text>
</view>
</view>
</view>
<view class="action-row">
<view class="outline-action" bindtap="handleRegenerate">重新润色</view>
<view class="confirm-action" bindtap="handleConfirm">确认使用此文案</view>
</view>
<view class="bottom-tip">
<image class="tip-dot icon-placeholder" src="/static/images/info-circle.png" mode="aspectFit"></image>
<text>新生成的视频将使用优化后的文案进行口播</text>
</view>
</view>
</view>
@@ -0,0 +1,335 @@
page {
min-height: 100vh;
background: #f8faff;
color: #111827;
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.preview-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(117, 93, 255, 0.08), transparent 32%),
radial-gradient(circle at 92% 8%, rgba(74, 132, 255, 0.08), transparent 30%),
linear-gradient(180deg, #fbfcff 0%, #f5f7ff 48%, #ffffff 100%);
}
.preview-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: 520rpx;
font-size: 22rpx;
line-height: 28rpx;
font-weight: 600;
color: #6d7484;
text-align: center;
white-space: nowrap;
}
.preview-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 26rpx;
padding: 22rpx 34rpx 30rpx;
box-sizing: border-box;
}
.panel {
width: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
border: 2rpx solid #e8eaff;
border-radius: 16rpx;
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 12rpx 34rpx rgba(80, 92, 170, 0.08);
}
.original-panel {
min-height: 326rpx;
padding: 25rpx 32rpx 22rpx;
}
.panel-title {
font-size: 28rpx;
line-height: 36rpx;
font-weight: 900;
color: #111827;
}
.script-text {
margin-top: 22rpx;
display: flex;
flex-direction: column;
color: #252a35;
font-size: 24rpx;
line-height: 42rpx;
font-weight: 700;
}
.original-text {
flex: 1;
}
.count-text,
.optimized-count {
align-self: flex-end;
font-size: 21rpx;
line-height: 28rpx;
font-weight: 600;
color: #9ba1af;
}
.ai-panel {
padding: 20rpx 18rpx 18rpx;
}
.panel-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
padding: 0 16rpx 16rpx;
}
.title-line {
min-width: 0;
display: flex;
align-items: center;
gap: 18rpx;
}
.status-pill {
padding: 4rpx 18rpx;
border-radius: 20rpx;
background: #ece9ff;
color: #7860ff;
font-size: 20rpx;
line-height: 28rpx;
font-weight: 800;
white-space: nowrap;
}
.edit-btn {
height: 48rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
border: 2rpx solid #d8d1ff;
border-radius: 14rpx;
color: #7254f4;
font-size: 23rpx;
line-height: 28rpx;
font-weight: 800;
box-sizing: border-box;
flex: 0 0 auto;
}
.edit-icon {
width: 21rpx;
height: 21rpx;
border: 4rpx solid #7254f4;
border-left: 0;
border-top: 0;
transform: skew(-12deg) rotate(-45deg);
box-sizing: border-box;
}
.optimized-box {
min-height: 286rpx;
padding: 20rpx 20rpx 18rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
border: 2rpx solid #dcd9ff;
border-radius: 14rpx;
box-sizing: border-box;
color: #252a35;
font-size: 24rpx;
line-height: 40rpx;
font-weight: 700;
background: #ffffff;
}
.style-panel {
padding: 25rpx 18rpx 22rpx;
gap: 22rpx;
}
.style-list {
display: flex;
align-items: stretch;
justify-content: space-between;
gap: 16rpx;
}
.style-item {
width: 112rpx;
height: 112rpx;
padding: 12rpx 8rpx 10rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
border: 2rpx solid #e1e4f4;
border-radius: 12rpx;
background: #ffffff;
box-sizing: border-box;
color: #555d6f;
font-size: 19rpx;
line-height: 24rpx;
font-weight: 800;
text-align: center;
flex: 0 0 112rpx;
}
.style-item-active {
border-color: #7461ff;
background: linear-gradient(180deg, #806bff 0%, #5f55f5 100%);
color: #ffffff;
box-shadow: 0 10rpx 22rpx rgba(98, 85, 245, 0.26);
}
.style-icon {
width: 44rpx;
height: 44rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 16rpx;
font-size: 18rpx;
line-height: 20rpx;
font-weight: 900;
color: #ffffff;
background: #83cfff;
}
.style-icon-natural { background: linear-gradient(135deg, #8c82ff 0%, #6b58ff 100%); }
.style-icon-goods { background: linear-gradient(135deg, #a7e8ff 0%, #69bde8 100%); }
.style-icon-emotion { background: linear-gradient(135deg, #ffd888 0%, #ffb750 100%); }
.style-icon-teach { background: linear-gradient(135deg, #dca8ff 0%, #9f66ef 100%); }
.style-icon-viral { background: linear-gradient(135deg, #ffbccd 0%, #ff7696 100%); }
.style-item-active .style-icon {
background: rgba(255, 255, 255, 0.16);
border: 1rpx solid rgba(255, 255, 255, 0.24);
}
.action-row {
display: flex;
align-items: center;
gap: 20rpx;
margin-top: 2rpx;
}
.outline-action,
.confirm-action {
height: 76rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 16rpx;
font-size: 27rpx;
line-height: 34rpx;
font-weight: 900;
box-sizing: border-box;
}
.outline-action {
flex: 0 0 278rpx;
border: 2rpx solid #d9d4ff;
background: rgba(255, 255, 255, 0.86);
color: #7254f4;
}
.confirm-action {
flex: 1;
color: #ffffff;
background: linear-gradient(100deg, #4e84ff 0%, #7d3ff3 100%);
box-shadow: 0 12rpx 24rpx rgba(91, 86, 245, 0.24);
}
.bottom-tip {
margin-top: 4rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
color: #8d94a5;
font-size: 23rpx;
line-height: 30rpx;
font-weight: 600;
}
.tip-dot {
width: 24rpx;
height: 24rpx;
display: flex;
align-items: center;
justify-content: center;
border: 3rpx solid #8d94a5;
border-radius: 50%;
font-size: 16rpx;
line-height: 18rpx;
font-weight: 900;
box-sizing: border-box;
}
.icon-placeholder {
display: block;
flex-shrink: 0;
background: none;
border: 0;
box-shadow: none;
transform: none;
}
@@ -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;
}