diff --git a/app.js b/app.js
index 56eef3a..e509415 100644
--- a/app.js
+++ b/app.js
@@ -70,29 +70,6 @@ App({
}
];
},
- onLaunch(option){
- const { query } = option;
- // 获取渠道值
- this.globalData.cid = query.cid || '';
- // 挂载 request
- const { domain, appid, cid } = this.globalData;
- this.request = createRequest({ domain, appid, cid });
-
- // 获取系统信息,计算安全区域
- const windowInfo = wx.getWindowInfo ? wx.getWindowInfo() : wx.getSystemInfoSync();
- const { statusBarHeight, safeArea, windowHeight, screenHeight } = windowInfo;
- this.globalData.statusBarHeight = statusBarHeight;
- this.globalData.safeAreaTop = safeArea.top;
- this.globalData.safeAreaBottom = screenHeight - safeArea.bottom;
- // 右上角胶囊位置信息
- const menuButtonInfo = wx.getMenuButtonBoundingClientRect();
- // 导航栏高度,是胶囊以及胶囊的上下边距所包含的高度,通常是 32+4+4
- this.globalData.navBarHeight = menuButtonInfo.height + (menuButtonInfo.top - statusBarHeight) * 2;
- this.globalData.totalNavHeight = statusBarHeight + this.globalData.navBarHeight;
- this.globalData.contentHeight = windowHeight - this.globalData.totalNavHeight;
- this.globalData.totalTabBarHeight = this.globalData.tabBarHeight + this.globalData.safeAreaBottom;
- this.globalData.contentWithoutTabbarHeight = this.globalData.contentHeight - this.globalData.totalTabBarHeight;
- },
// TTS接口请求方法
async requestTTS(text, voice) {
return new Promise((resolve, reject) => {
@@ -165,5 +142,34 @@ App({
}
return list;
}
+ },
+ onLaunch(option){
+ const { query } = option;
+ // 获取渠道值
+ this.globalData.cid = query.cid || '';
+ // 挂载 request
+ const { domain, appid, cid } = this.globalData;
+ this.request = createRequest({ domain, appid, cid });
+
+ // 获取系统信息,计算安全区域
+ const windowInfo = wx.getWindowInfo ? wx.getWindowInfo() : wx.getSystemInfoSync();
+ const { statusBarHeight, safeArea, windowHeight, screenHeight } = windowInfo;
+ this.globalData.statusBarHeight = statusBarHeight;
+ this.globalData.safeAreaTop = safeArea.top;
+ this.globalData.safeAreaBottom = screenHeight - safeArea.bottom;
+ // 右上角胶囊位置信息
+ const menuButtonInfo = wx.getMenuButtonBoundingClientRect();
+ // 导航栏高度,是胶囊以及胶囊的上下边距所包含的高度,通常是 32+4+4
+ this.globalData.navBarHeight = menuButtonInfo.height + (menuButtonInfo.top - statusBarHeight) * 2;
+ this.globalData.totalNavHeight = statusBarHeight + this.globalData.navBarHeight;
+ this.globalData.contentHeight = windowHeight - this.globalData.totalNavHeight;
+ this.globalData.totalTabBarHeight = this.globalData.tabBarHeight + this.globalData.safeAreaBottom;
+ this.globalData.contentWithoutTabbarHeight = this.globalData.contentHeight - this.globalData.totalTabBarHeight;
+
+ // 移除 2022-12-05 之前的本地存储,因为可能有敏感词存在
+ const timestamp = new Date('2022-12-05').getTime();
+ const audioList = this.globalData.getAudioList().filter(v => v.time > timestamp);
+ const storageKey = 'audioList';
+ wx.setStorageSync(storageKey, audioList);
}
});
diff --git a/app.json b/app.json
index cf3e0b4..f08005d 100644
--- a/app.json
+++ b/app.json
@@ -1,14 +1,15 @@
{
"pages": [
"pages/index/index",
- "pages/result/result"
+ "pages/text-to-audio/text-to-audio",
+ "pages/text-to-audio-result/text-to-audio-result"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "文字转语音",
- "navigationBarTextStyle": "black",
+ "navigationBarTextStyle": "white",
"navigationStyle": "custom"
},
"sitemapLocation": "sitemap.json"
-}
\ No newline at end of file
+}
diff --git a/app.wxss b/app.wxss
index 9105d95..89c2b87 100644
--- a/app.wxss
+++ b/app.wxss
@@ -48,13 +48,9 @@ page {
padding-bottom: env(safe-area-inset-bottom);
}
-/* 保留原有的 container 类以兼容其他页面 */
-.container {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: space-between;
- box-sizing: border-box;
-}
+::-webkit-scrollbar {
+ display: none;
+ width: 0;
+ height: 0;
+ color: transparent;
+}
\ No newline at end of file
diff --git a/components/custom-nav-bar/index.js b/components/custom-nav-bar/index.js
new file mode 100644
index 0000000..22045c0
--- /dev/null
+++ b/components/custom-nav-bar/index.js
@@ -0,0 +1,94 @@
+Component({
+ properties: {
+ // 导航栏标题
+ title: {
+ type: String,
+ value: ''
+ },
+ // 背景颜色
+ background: {
+ type: String,
+ value: 'transparent'
+ },
+ // 标题颜色
+ color: {
+ type: String,
+ value: '#333333'
+ },
+ // 是否显示返回按钮
+ showBack: {
+ type: Boolean,
+ value: true
+ },
+ // 返回按钮图标颜色
+ backIconColor: {
+ type: String,
+ value: '#ffffff'
+ },
+ // 是否显示首页按钮
+ showHome: {
+ type: Boolean,
+ value: false
+ },
+ // 特殊模式,不占高度
+ noPlaceholder: {
+ type: Boolean,
+ value: false
+ }
+ },
+
+ data: {
+ statusBarHeight: 0,
+ navBarHeight: 44,
+ totalHeight: 0
+ },
+
+ lifetimes: {
+ attached() {
+ const app = getApp()
+
+ // 直接使用 app.globalData 中已经计算好的数据
+ this.setData({
+ statusBarHeight: app.globalData.statusBarHeight,
+ navBarHeight: app.globalData.navBarHeight,
+ totalHeight: app.globalData.totalNavHeight
+ })
+ }
+ },
+
+ methods: {
+ // 返回上一页
+ onBack() {
+ const pages = getCurrentPages()
+ if (pages.length > 1) {
+ wx.navigateBack({
+ delta: 1
+ })
+ } else {
+ // 如果是第一个页面,尝试返回首页
+ wx.switchTab({
+ url: '/pages/index/index',
+ fail: () => {
+ wx.reLaunch({
+ url: '/pages/index/index'
+ })
+ }
+ })
+ }
+ this.triggerEvent('back')
+ },
+
+ // 返回首页
+ onHome() {
+ wx.switchTab({
+ url: '/pages/index/index',
+ fail: () => {
+ wx.reLaunch({
+ url: '/pages/index/index'
+ })
+ }
+ })
+ this.triggerEvent('home')
+ }
+ }
+})
diff --git a/components/custom-nav-bar/index.json b/components/custom-nav-bar/index.json
new file mode 100644
index 0000000..a89ef4d
--- /dev/null
+++ b/components/custom-nav-bar/index.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+}
diff --git a/components/custom-nav-bar/index.wxml b/components/custom-nav-bar/index.wxml
new file mode 100644
index 0000000..f054dea
--- /dev/null
+++ b/components/custom-nav-bar/index.wxml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ ⌂
+
+
+
+
+
+ {{title}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/custom-nav-bar/index.wxss b/components/custom-nav-bar/index.wxss
new file mode 100644
index 0000000..78df547
--- /dev/null
+++ b/components/custom-nav-bar/index.wxss
@@ -0,0 +1,89 @@
+.nav-bar {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 9999;
+ background: transparent;
+}
+
+.status-bar {
+ width: 100%;
+}
+
+.nav-bar-content {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ position: relative;
+}
+
+.nav-bar-left {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+ z-index: 2;
+}
+
+.nav-bar-btn {
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-left: 8px;
+ margin-right: 4px;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.2);
+ backdrop-filter: blur(10px);
+ transition: all 0.3s ease;
+}
+
+.nav-bar-btn:active {
+ background: rgba(255, 255, 255, 0.4);
+ transform: scale(0.95);
+}
+
+/* 返回按钮箭头 */
+.back-icon {
+ width: 10px;
+ height: 10px;
+ margin-left: 4px;
+ border-left: 1.5px solid #fff;
+ border-bottom: 1.5px solid #fff;
+ transform: rotate(45deg);
+}
+
+/* 首页图标 */
+.home-icon {
+ font-size: 20px;
+ font-weight: 600;
+ line-height: 1;
+}
+
+.nav-bar-title {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ font-size: 16px;
+ font-weight: bold;
+ max-width: 60%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ z-index: 1;
+}
+
+.nav-bar-right {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+ min-width: 32px;
+ justify-content: flex-end;
+ z-index: 2;
+}
+
+.nav-bar-placeholder {
+ width: 100%;
+}
diff --git a/pages/index/index.js b/pages/index/index.js
index 59c4c69..c49a680 100644
--- a/pages/index/index.js
+++ b/pages/index/index.js
@@ -1,290 +1,7 @@
-//index.js
-//获取应用实例
-const app = getApp();
-const typeList = app.globalData.getTypeList();
-const selectedIndex = typeList.length > 1 ? app.globalData.getSelectedTypeIndex() : 0;
-
-let audio = null;
-let videoAd = null;
-
+// index.js
Page({
- data: {
- placeholder: '点击此处输入文字',
- text: '',
- typeList,
- selectedIndex: String(selectedIndex) ? selectedIndex : 0,
- isAudioPlaying: false,
- currentAudioText: ''
- },
- onLoad(){
+ data: {},
+ onLoad() {
wx.showShareMenu();
- this.initVideoAd();
- },
-
- // 初始化激励视频广告
- initVideoAd() {
- // 若在开发者工具中无法预览广告,请切换开发者工具中的基础库版本
- if (wx.createRewardedVideoAd) {
- videoAd = wx.createRewardedVideoAd({
- adUnitId: 'adunit-aedf0933f69de926'
- });
-
- videoAd.onLoad(() => {
- console.log('激励视频广告加载成功');
- });
-
- videoAd.onError((err) => {
- console.error('激励视频广告加载失败', err);
- });
-
- videoAd.onClose((res) => {
- if (res && res.isEnded) {
- // 用户看完广告,重置使用次数
- this.resetTtsCount();
- } else {
- // 用户中途退出
- wx.showToast({
- title: '请看完广告获取次数',
- icon: 'none'
- });
- }
- });
- }
- },
-
- // 显示激励视频广告
- showVideoAd() {
- if (videoAd) {
- videoAd.show().catch(() => {
- // 失败重试
- videoAd.load()
- .then(() => videoAd.show())
- .catch(err => {
- console.error('激励视频广告显示失败', err);
- wx.showToast({
- title: '广告加载失败,请稍后重试',
- icon: 'none'
- });
- });
- });
- } else {
- wx.showToast({
- title: '广告功能暂不可用',
- icon: 'none'
- });
- }
- },
-
- // 重置TTS使用次数
- async resetTtsCount() {
- wx.showLoading({
- title: '正在重置次数...',
- mask: true
- });
-
- try {
- const result = await new Promise((resolve, reject) => {
- app.request({
- url: `${app.globalData.domain}/open-api/wx330e54aa6000516d/tts-count-reset`,
- success(res) {
- resolve(res.data);
- },
- fail(err) {
- reject(err);
- }
- });
- });
-
- wx.hideLoading();
-
- if (result.code === 2000000) {
- wx.showToast({
- title: '获得10次使用机会!',
- icon: 'success'
- });
- } else {
- wx.showToast({
- title: result.msg || '重置失败',
- icon: 'none'
- });
- }
- } catch (error) {
- wx.hideLoading();
- wx.showToast({
- title: '网络错误',
- icon: 'none'
- });
- }
- },
-
- // 显示次数用完弹窗
- showLimitDialog() {
- wx.showModal({
- title: '使用次数已用完',
- content: '今日免费次数已用完,观看广告可获得额外10次使用机会',
- confirmText: '观看广告',
- cancelText: '明日再试',
- success: (res) => {
- if (res.confirm) {
- this.showVideoAd();
- }
- }
- });
- },
- inputText(e){
- this.data.text = e.detail.value;
- },
- async play(){
- const text = this.data.text || this.data.placeholder;
- const selectedType = this.data.typeList[this.data.selectedIndex];
- const type = selectedType.desc;
-
- this.resetAudio();
- audio = wx.createInnerAudioContext({useWebAudioImplement: true});
- audio.autoplay = true;
-
- // 显示播放蒙层
- this.setData({
- isAudioPlaying: true,
- currentAudioText: text
- });
-
- // 初始化蒙层组件的音频监听
- setTimeout(() => {
- const overlayComponent = this.selectComponent('audio-overlay');
- if (overlayComponent) {
- overlayComponent.initAudio(audio);
- }
- }, 100);
-
- // 检查是否播放过
- const audioList = app.globalData.getAudioList();
- let played = audioList.find(v => v.text === text && v.type === type);
-
- // 播放过,直接使用本地文件,无需检查合规性
- if(played){
- played.time = Date.now();
- app.globalData.setAudioList('delete', played.path);
- app.globalData.setAudioList('add', played);
- // 播放失败要移除缓存内容
- audio.onError(err => {
- app.globalData.setAudioList('delete', played.path);
- this.setData({ isAudioPlaying: false });
- wx.showModal({
- title: '提示',
- content: '该语音播放过但缓存失效,点击确定重新播放',
- showCancel: false,
- success: res => {
- this.play();
- }
- });
- });
- return audio.src = played.path;
- }
-
- // 未播放过,先检查文本是否合规
- wx.showLoading({
- title: '正在合成语音...',
- mask: true
- });
- const result = await new Promise((resolve, reject) => {
- app.request({
- url: `${app.globalData.domain}/open-api/mp/msg-sec-check`,
- data: {
- appid: 'wx330e54aa6000516d',
- content: text,
- openid: wx.getStorageSync('openid')
- },
- success(res){
- resolve(JSON.parse(res.data?.data)?.result);
- },
- fail(err){
- reject(err);
- }
- });
- });
- // 不合规结束
- if(result.suggest !== 'pass'){
- wx.hideLoading();
- this.setData({ isAudioPlaying: false });
- return wx.showToast({
- title: '内容不合规',
- icon: 'error',
- mask: true,
- duration: 2000
- });
- }
- // 未播放过,调用TTS接口生成语音
- try {
- const url = await selectedType.createUrl(text);
- // 下载并播放
- wx.downloadFile({
- url,
- success (res) {
- wx.hideLoading();
- audio.src = res.tempFilePath;
- app.globalData.setAudioList('add', {
- // 音频文本
- text,
- // 音频音效
- type,
- // 音频网络地址
- url,
- // 音频本地地址
- path: res.tempFilePath,
- // 音频创建时间
- time: Date.now()
- });
- },
- fail(err) {
- console.log(err);
- wx.hideLoading();
- this.setData({ isAudioPlaying: false });
- wx.showToast({
- title: '下载失败',
- icon: 'none'
- });
- }
- });
- } catch (error) {
- wx.hideLoading();
- this.setData({ isAudioPlaying: false });
- // TTS接口调用失败的错误已经在app.js中处理了
- }
- },
- clear(){
- this.resetAudio();
- this.setData({
- text: ''
- });
- },
- resetAudio(){
- if(audio){
- audio.destroy();
- audio = null;
- }
- this.setData({
- isAudioPlaying: false,
- currentAudioText: ''
- });
- },
-
- // 音频停止播放的回调
- onAudioStop() {
- this.resetAudio();
- },
- selectIndex(e){
- const { index } = e.target.dataset;
- this.setData({
- selectedIndex: index
- }, () => {
- this.play();
- });
- app.globalData.setSelectedTypeIndex(index);
- },
- gotoAudioList(){
- wx.navigateTo({
- url: '/pages/result/result'
- });
}
});
diff --git a/pages/index/index.wxml b/pages/index/index.wxml
index 7c6a0c7..1d44a93 100644
--- a/pages/index/index.wxml
+++ b/pages/index/index.wxml
@@ -5,41 +5,12 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+ 欢迎使用文字转语音
+
+ 开始使用
+
-
-
-
-
-
\ No newline at end of file
diff --git a/pages/index/index.wxss b/pages/index/index.wxss
index 11748cd..8034014 100644
--- a/pages/index/index.wxss
+++ b/pages/index/index.wxss
@@ -27,14 +27,14 @@ page {
width: 400rpx;
height: 400rpx;
background-color: #4472c4;
- animation: move-1 5s infinite;
+ animation: move-1 8s ease-in-out infinite;
}
.ball-2 {
width: 500rpx;
height: 500rpx;
background-color: #00b050;
- animation: move-2 5s infinite;
+ animation: move-2 10s ease-in-out infinite;
}
@keyframes move-1 {
@@ -42,13 +42,13 @@ page {
transform: translate(0, 0);
}
25% {
- transform: translate(200rpx, 150rpx);
+ transform: translate(500rpx, 600rpx);
}
50% {
- transform: translate(0, 300rpx);
+ transform: translate(-300rpx, 1000rpx);
}
75% {
- transform: translate(-100rpx, 150rpx);
+ transform: translate(-400rpx, 400rpx);
}
100% {
transform: translate(0, 0);
@@ -57,234 +57,77 @@ page {
@keyframes move-2 {
0% {
- transform: translate(0, 0);
+ transform: translate(100rpx, 100rpx);
}
25% {
- transform: translate(-200rpx, -180rpx);
+ transform: translate(-500rpx, -200rpx);
}
50% {
- transform: translate(0, -320rpx);
+ transform: translate(400rpx, 800rpx);
}
75% {
- transform: translate(100rpx, -180rpx);
+ transform: translate(600rpx, 200rpx);
}
100% {
- transform: translate(0, 0);
+ transform: translate(100rpx, 100rpx);
}
}
-/* 全局移除按钮边框 */
-button {
- border: none !important;
- outline: none !important;
-}
-
-button::after {
- border: none !important;
-}
-
-button::before {
- border: none !important;
-}
-
.container-custom {
position: relative;
width: 100%;
- max-width: 100vw;
- overflow-x: hidden;
- box-sizing: border-box;
- padding: 20rpx;
-}
-
-.text-view{
- width: 100%;
- margin-bottom: 20rpx;
- text-align: center;
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
box-sizing: border-box;
-}
-.text-area{
- width: 100%;
- max-width: 100%;
padding: 20rpx;
- text-align: left;
- box-sizing: border-box;
- display: block;
- border: none;
- border-radius: 16rpx;
- background-color: #fafafa;
- font-size: 32rpx;
- line-height: 1.5;
- transition: all 0.3s ease;
- box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
-}
-.text-area:focus {
- background-color: #ffffff;
- box-shadow: 0 4rpx 20rpx rgba(0, 122, 255, 0.15);
-}
-
-.button-view{
- width: 100%;
- margin-bottom: 40rpx;
- text-align: center;
- box-sizing: border-box;
}
-/* 主要按钮样式 */
-.button-view button{
- width: 100%;
- max-width: 100%;
- display: block;
- margin-bottom: 20rpx;
- height: 88rpx;
- line-height: 88rpx;
- font-size: 32rpx;
- text-align: center;
- border-radius: 44rpx;
- border: none !important;
- font-weight: 600;
- letter-spacing: 2rpx;
- transition: all 0.3s ease;
+/* 主内容区 */
+.main-content {
position: relative;
- overflow: hidden;
- box-sizing: border-box;
-}
-
-.button-view button::after {
- border: none !important;
-}
-
-.button-view button::before {
- border: none !important;
-}
-
-/* 播放按钮 - 主要操作 */
-.button-view button[bindtap="play"] {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.4), inset 0 2rpx 0 rgba(255, 255, 255, 0.2);
-}
-
-.button-view button[bindtap="play"]:active {
- transform: translateY(2rpx);
- box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.4);
-}
-
-/* 下载按钮 - 次要操作 */
-.goto-audio-list {
- background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
- color: white;
- box-shadow: 0 8rpx 24rpx rgba(240, 147, 251, 0.4), inset 0 2rpx 0 rgba(255, 255, 255, 0.2);
-}
-
-.goto-audio-list:active {
- transform: translateY(2rpx);
- box-shadow: 0 4rpx 12rpx rgba(240, 147, 251, 0.4);
-}
-
-/* 清空按钮 - 辅助操作 */
-.button-view button[bindtap="clear"] {
- background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
- color: #8b4513;
- box-shadow: 0 8rpx 24rpx rgba(252, 182, 159, 0.4), inset 0 2rpx 0 rgba(255, 255, 255, 0.3);
-}
-
-.button-view button[bindtap="clear"]:active {
- transform: translateY(2rpx);
- box-shadow: 0 4rpx 12rpx rgba(252, 182, 159, 0.4);
-}
-
-/* 语音类型选择区域 */
-.type-view{
- width: 100%;
- max-width: 100%;
- margin: 0 0 40rpx 0;
- padding: 20rpx;
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 250, 252, 0.95) 100%);
- border-radius: 20rpx;
- box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.08);
- backdrop-filter: blur(10rpx);
- box-sizing: border-box;
-}
-
-.type-view button{
- width: 100%;
- max-width: 100%;
- height: 60rpx;
- margin-bottom: 16rpx;
- padding: 0 20rpx;
- writing-mode: horizontal-tb;
- text-align: center;
- letter-spacing: 1rpx;
- font-size: 28rpx;
- font-weight: 500;
- border-radius: 30rpx;
- border: none !important;
- transition: all 0.3s ease;
- position: relative;
- overflow: hidden;
+ z-index: 2;
display: flex;
+ flex-direction: column;
align-items: center;
justify-content: center;
- white-space: nowrap;
- box-sizing: border-box;
-}
-
-.type-view button::after {
- border: none !important;
-}
-
-.type-view button::before {
- border: none !important;
}
-.type-view button:last-child {
- margin-bottom: 0;
-}
-
-/* 未选中的语音类型按钮 */
-.type-view button[type="default"] {
- background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
- color: #495057;
- box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1), inset 0 1rpx 0 rgba(255, 255, 255, 0.8);
+.title {
+ font-size: 48rpx;
+ font-weight: 600;
+ letter-spacing: 4rpx;
+ margin-bottom: 80rpx;
+ background: linear-gradient(45deg, #667eea, #764ba2, #f093fb, #f5576c);
+ background-size: 200% 100%;
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+ animation: text-shine 3s linear infinite;
}
-.type-view button[type="default"]:active {
- background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
- transform: translateY(2rpx);
- box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.15), inset 0 1rpx 0 rgba(255, 255, 255, 0.6);
+@keyframes text-shine {
+ 0% {
+ background-position: 200% 0;
+ }
+ 100% {
+ background-position: -200% 0;
+ }
}
-/* 选中的语音类型按钮 */
-.type-view button[type="primary"] {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+.nav-button {
+ padding: 24rpx 80rpx;
+ font-size: 32rpx;
+ font-weight: 600;
color: white;
- box-shadow: 0 6rpx 20rpx rgba(102, 126, 234, 0.4), inset 0 1rpx 0 rgba(255, 255, 255, 0.2);
- transform: scale(1.02);
-}
-
-.type-view button[type="primary"]:active {
- transform: scale(1.02) translateY(2rpx);
- box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.5), inset 0 1rpx 0 rgba(255, 255, 255, 0.1);
-}
-
-/* 按钮悬浮效果动画 */
-.button-view button::before,
-.type-view button::before {
- content: '';
- position: absolute;
- top: 0;
- left: -100%;
- width: 100%;
- height: 100%;
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
- transition: left 0.5s;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 50rpx;
+ box-shadow: 0 12rpx 32rpx rgba(102, 126, 234, 0.4), inset 0 2rpx 0 rgba(255, 255, 255, 0.2);
+ transition: all 0.3s ease;
}
-.button-view button:active::before,
-.type-view button:active::before {
- left: 100%;
+.nav-button:active {
+ transform: translateY(4rpx) scale(0.98);
+ box-shadow: 0 6rpx 16rpx rgba(102, 126, 234, 0.4);
}
-
-.ad-1{
- margin-bottom: 20rpx;
-}
\ No newline at end of file
diff --git a/pages/result/result.wxml b/pages/result/result.wxml
deleted file mode 100644
index bb0803f..0000000
--- a/pages/result/result.wxml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
- {{item.text}}
-
-
- {{item.type}} - {{item.date}}
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/pages/result/result.wxss b/pages/result/result.wxss
deleted file mode 100644
index 3b9a76a..0000000
--- a/pages/result/result.wxss
+++ /dev/null
@@ -1,35 +0,0 @@
-.text-view{
- margin-bottom: 10px;
- padding: 10px;
- background: #efefef;
-}
-.btn-view{
- padding-top: 10px;
- text-align: right;
- border-top: 0.5px solid #d0d0d0;
-}
-.button{
- margin-left: 10px;
-}
-.left{
- float: left;
- margin-left: 0;
-}
-.text{
- padding-top: 10px;
- padding-bottom: 20px;
-}
-.text-time{
- float: left;
- font-size: 11px;
- line-height: 2.6;
- color: #999;
-}
-.text-type{
- color: #999;
-}
-.ad-placeholder{
- min-height: 150px;
- margin-bottom: 10px;
- background: #efefef;
-}
\ No newline at end of file
diff --git a/pages/result/result.js b/pages/text-to-audio-result/text-to-audio-result.js
index 3e298fd..3e298fd 100644
--- a/pages/result/result.js
+++ b/pages/text-to-audio-result/text-to-audio-result.js
diff --git a/pages/result/result.json b/pages/text-to-audio-result/text-to-audio-result.json
index e273712..a5c90c3 100644
--- a/pages/result/result.json
+++ b/pages/text-to-audio-result/text-to-audio-result.json
@@ -1,5 +1,6 @@
{
"usingComponents": {
+ "custom-nav-bar": "../../components/custom-nav-bar/index",
"audio-overlay": "../../components/audio-overlay/audio-overlay"
}
}
\ No newline at end of file
diff --git a/pages/text-to-audio-result/text-to-audio-result.wxml b/pages/text-to-audio-result/text-to-audio-result.wxml
new file mode 100644
index 0000000..c4acaaf
--- /dev/null
+++ b/pages/text-to-audio-result/text-to-audio-result.wxml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.text}}
+
+
+
+
+ 🎵
+ 还没有音频
+ 去生成一些语音吧
+
+
+
+
+
+
diff --git a/pages/text-to-audio-result/text-to-audio-result.wxss b/pages/text-to-audio-result/text-to-audio-result.wxss
new file mode 100644
index 0000000..394b910
--- /dev/null
+++ b/pages/text-to-audio-result/text-to-audio-result.wxss
@@ -0,0 +1,210 @@
+/**text-to-audio-result.wxss**/
+
+/* 防止横向滚动 */
+page {
+ width: 100%;
+ height: 100%;
+ overflow-x: hidden;
+ box-sizing: border-box;
+}
+
+/* 动态背景样式 */
+.bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ filter: blur(120px);
+ z-index: -1;
+}
+
+.ball {
+ position: absolute;
+ border-radius: 50%;
+}
+
+.ball-1 {
+ width: 400rpx;
+ height: 400rpx;
+ background-color: #4472c4;
+ animation: move-1 8s ease-in-out infinite;
+}
+
+.ball-2 {
+ width: 500rpx;
+ height: 500rpx;
+ background-color: #00b050;
+ animation: move-2 10s ease-in-out infinite;
+}
+
+@keyframes move-1 {
+ 0% {
+ transform: translate(0, 0);
+ }
+ 25% {
+ transform: translate(500rpx, 600rpx);
+ }
+ 50% {
+ transform: translate(-300rpx, 1000rpx);
+ }
+ 75% {
+ transform: translate(-400rpx, 400rpx);
+ }
+ 100% {
+ transform: translate(0, 0);
+ }
+}
+
+@keyframes move-2 {
+ 0% {
+ transform: translate(100rpx, 100rpx);
+ }
+ 25% {
+ transform: translate(-500rpx, -200rpx);
+ }
+ 50% {
+ transform: translate(400rpx, 800rpx);
+ }
+ 75% {
+ transform: translate(600rpx, 200rpx);
+ }
+ 100% {
+ transform: translate(100rpx, 100rpx);
+ }
+}
+
+/* 全局移除按钮边框 */
+button {
+ border: none !important;
+ outline: none !important;
+}
+
+button::after {
+ border: none !important;
+}
+
+button::before {
+ border: none !important;
+}
+
+/* 音频卡片 */
+.audio-card {
+ margin-bottom: 20rpx;
+ padding: 30rpx;
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(10rpx);
+ border-radius: 24rpx;
+ box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
+ transition: all 0.3s ease;
+}
+
+.audio-card:active {
+ transform: translateY(-2rpx);
+ box-shadow: 0 12rpx 48rpx rgba(0, 0, 0, 0.12);
+}
+
+.audio-text {
+ font-size: 32rpx;
+ line-height: 1.6;
+ color: #333;
+ margin-bottom: 24rpx;
+ word-break: break-all;
+}
+
+.audio-footer {
+ display: flex;
+ flex-direction: column;
+ gap: 20rpx;
+}
+
+.audio-info {
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+ font-size: 24rpx;
+ color: #999;
+}
+
+.audio-type {
+ padding: 4rpx 12rpx;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ border-radius: 8rpx;
+ font-size: 22rpx;
+}
+
+.audio-date {
+ color: #999;
+}
+
+.audio-actions {
+ display: flex;
+ gap: 16rpx;
+}
+
+.btn-action {
+ flex: 1;
+ height: 64rpx;
+ line-height: 64rpx;
+ font-size: 26rpx;
+ border-radius: 32rpx;
+ transition: all 0.3s ease;
+}
+
+.btn-play {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.4);
+}
+
+.btn-play:active {
+ transform: scale(0.95);
+ box-shadow: 0 2rpx 8rpx rgba(102, 126, 234, 0.4);
+}
+
+.btn-download {
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+ color: white;
+ box-shadow: 0 4rpx 16rpx rgba(240, 147, 251, 0.4);
+}
+
+.btn-download:active {
+ transform: scale(0.95);
+ box-shadow: 0 2rpx 8rpx rgba(240, 147, 251, 0.4);
+}
+
+.btn-delete {
+ background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
+ color: white;
+ box-shadow: 0 4rpx 16rpx rgba(255, 107, 107, 0.4);
+}
+
+.btn-delete:active {
+ transform: scale(0.95);
+ box-shadow: 0 2rpx 8rpx rgba(255, 107, 107, 0.4);
+}
+
+/* 空状态 */
+.empty-state {
+ padding: 120rpx 60rpx;
+ text-align: center;
+}
+
+.empty-icon {
+ font-size: 120rpx;
+ margin-bottom: 32rpx;
+ opacity: 0.6;
+}
+
+.empty-text {
+ font-size: 32rpx;
+ color: #666;
+ margin-bottom: 16rpx;
+ font-weight: 600;
+}
+
+.empty-desc {
+ font-size: 28rpx;
+ color: #999;
+}
diff --git a/pages/text-to-audio/text-to-audio.js b/pages/text-to-audio/text-to-audio.js
new file mode 100644
index 0000000..f2787dd
--- /dev/null
+++ b/pages/text-to-audio/text-to-audio.js
@@ -0,0 +1,290 @@
+//text-to-audio.js
+//获取应用实例
+const app = getApp();
+const typeList = app.globalData.getTypeList();
+const selectedIndex = typeList.length > 1 ? app.globalData.getSelectedTypeIndex() : 0;
+
+let audio = null;
+let videoAd = null;
+
+Page({
+ data: {
+ placeholder: '点击此处输入文字',
+ text: '',
+ typeList,
+ selectedIndex: String(selectedIndex) ? selectedIndex : 0,
+ isAudioPlaying: false,
+ currentAudioText: ''
+ },
+ onLoad(){
+ wx.showShareMenu();
+ this.initVideoAd();
+ },
+
+ // 初始化激励视频广告
+ initVideoAd() {
+ // 若在开发者工具中无法预览广告,请切换开发者工具中的基础库版本
+ if (wx.createRewardedVideoAd) {
+ videoAd = wx.createRewardedVideoAd({
+ adUnitId: 'adunit-aedf0933f69de926'
+ });
+
+ videoAd.onLoad(() => {
+ console.log('激励视频广告加载成功');
+ });
+
+ videoAd.onError((err) => {
+ console.error('激励视频广告加载失败', err);
+ });
+
+ videoAd.onClose((res) => {
+ if (res && res.isEnded) {
+ // 用户看完广告,重置使用次数
+ this.resetTtsCount();
+ } else {
+ // 用户中途退出
+ wx.showToast({
+ title: '请看完广告获取次数',
+ icon: 'none'
+ });
+ }
+ });
+ }
+ },
+
+ // 显示激励视频广告
+ showVideoAd() {
+ if (videoAd) {
+ videoAd.show().catch(() => {
+ // 失败重试
+ videoAd.load()
+ .then(() => videoAd.show())
+ .catch(err => {
+ console.error('激励视频广告显示失败', err);
+ wx.showToast({
+ title: '广告加载失败,请稍后重试',
+ icon: 'none'
+ });
+ });
+ });
+ } else {
+ wx.showToast({
+ title: '广告功能暂不可用',
+ icon: 'none'
+ });
+ }
+ },
+
+ // 重置TTS使用次数
+ async resetTtsCount() {
+ wx.showLoading({
+ title: '正在重置次数...',
+ mask: true
+ });
+
+ try {
+ const result = await new Promise((resolve, reject) => {
+ app.request({
+ url: `${app.globalData.domain}/open-api/wx330e54aa6000516d/tts-count-reset`,
+ success(res) {
+ resolve(res.data);
+ },
+ fail(err) {
+ reject(err);
+ }
+ });
+ });
+
+ wx.hideLoading();
+
+ if (result.code === 2000000) {
+ wx.showToast({
+ title: '获得10次使用机会!',
+ icon: 'success'
+ });
+ } else {
+ wx.showToast({
+ title: result.msg || '重置失败',
+ icon: 'none'
+ });
+ }
+ } catch (error) {
+ wx.hideLoading();
+ wx.showToast({
+ title: '网络错误',
+ icon: 'none'
+ });
+ }
+ },
+
+ // 显示次数用完弹窗
+ showLimitDialog() {
+ wx.showModal({
+ title: '使用次数已用完',
+ content: '今日免费次数已用完,观看广告可获得额外10次使用机会',
+ confirmText: '观看广告',
+ cancelText: '明日再试',
+ success: (res) => {
+ if (res.confirm) {
+ this.showVideoAd();
+ }
+ }
+ });
+ },
+ inputText(e){
+ this.data.text = e.detail.value;
+ },
+ async play(){
+ const text = this.data.text || this.data.placeholder;
+ const selectedType = this.data.typeList[this.data.selectedIndex];
+ const type = selectedType.desc;
+
+ this.resetAudio();
+ audio = wx.createInnerAudioContext({useWebAudioImplement: true});
+ audio.autoplay = true;
+
+ // 显示播放蒙层
+ this.setData({
+ isAudioPlaying: true,
+ currentAudioText: text
+ });
+
+ // 初始化蒙层组件的音频监听
+ setTimeout(() => {
+ const overlayComponent = this.selectComponent('audio-overlay');
+ if (overlayComponent) {
+ overlayComponent.initAudio(audio);
+ }
+ }, 100);
+
+ // 检查是否播放过
+ const audioList = app.globalData.getAudioList();
+ let played = audioList.find(v => v.text === text && v.type === type);
+
+ // 播放过,直接使用本地文件,无需检查合规性
+ if(played){
+ played.time = Date.now();
+ app.globalData.setAudioList('delete', played.path);
+ app.globalData.setAudioList('add', played);
+ // 播放失败要移除缓存内容
+ audio.onError(err => {
+ app.globalData.setAudioList('delete', played.path);
+ this.setData({ isAudioPlaying: false });
+ wx.showModal({
+ title: '提示',
+ content: '该语音播放过但缓存失效,点击确定重新播放',
+ showCancel: false,
+ success: res => {
+ this.play();
+ }
+ });
+ });
+ return audio.src = played.path;
+ }
+
+ // 未播放过,先检查文本是否合规
+ wx.showLoading({
+ title: '正在合成语音...',
+ mask: true
+ });
+ const result = await new Promise((resolve, reject) => {
+ app.request({
+ url: `${app.globalData.domain}/open-api/mp/msg-sec-check`,
+ data: {
+ appid: 'wx330e54aa6000516d',
+ content: text,
+ openid: wx.getStorageSync('openid')
+ },
+ success(res){
+ resolve(JSON.parse(res.data?.data)?.result);
+ },
+ fail(err){
+ reject(err);
+ }
+ });
+ });
+ // 不合规结束
+ if(result.suggest !== 'pass'){
+ wx.hideLoading();
+ this.setData({ isAudioPlaying: false });
+ return wx.showToast({
+ title: '内容不合规',
+ icon: 'error',
+ mask: true,
+ duration: 2000
+ });
+ }
+ // 未播放过,调用TTS接口生成语音
+ try {
+ const url = await selectedType.createUrl(text);
+ // 下载并播放
+ wx.downloadFile({
+ url,
+ success (res) {
+ wx.hideLoading();
+ audio.src = res.tempFilePath;
+ app.globalData.setAudioList('add', {
+ // 音频文本
+ text,
+ // 音频音效
+ type,
+ // 音频网络地址
+ url,
+ // 音频本地地址
+ path: res.tempFilePath,
+ // 音频创建时间
+ time: Date.now()
+ });
+ },
+ fail(err) {
+ console.log(err);
+ wx.hideLoading();
+ this.setData({ isAudioPlaying: false });
+ wx.showToast({
+ title: '下载失败',
+ icon: 'none'
+ });
+ }
+ });
+ } catch (error) {
+ wx.hideLoading();
+ this.setData({ isAudioPlaying: false });
+ // TTS接口调用失败的错误已经在app.js中处理了
+ }
+ },
+ clear(){
+ this.resetAudio();
+ this.setData({
+ text: ''
+ });
+ },
+ resetAudio(){
+ if(audio){
+ audio.destroy();
+ audio = null;
+ }
+ this.setData({
+ isAudioPlaying: false,
+ currentAudioText: ''
+ });
+ },
+
+ // 音频停止播放的回调
+ onAudioStop() {
+ this.resetAudio();
+ },
+ selectIndex(e){
+ const { index } = e.target.dataset;
+ this.setData({
+ selectedIndex: index
+ }, () => {
+ this.play();
+ });
+ app.globalData.setSelectedTypeIndex(index);
+ },
+ gotoAudioList(){
+ wx.navigateTo({
+ url: '/pages/text-to-audio-result/text-to-audio-result'
+ });
+ }
+});
diff --git a/pages/text-to-audio/text-to-audio.json b/pages/text-to-audio/text-to-audio.json
new file mode 100644
index 0000000..fb44003
--- /dev/null
+++ b/pages/text-to-audio/text-to-audio.json
@@ -0,0 +1,6 @@
+{
+ "usingComponents": {
+ "custom-nav-bar": "../../components/custom-nav-bar/index",
+ "audio-overlay": "../../components/audio-overlay/audio-overlay"
+ }
+}
diff --git a/pages/text-to-audio/text-to-audio.wxml b/pages/text-to-audio/text-to-audio.wxml
new file mode 100644
index 0000000..1534941
--- /dev/null
+++ b/pages/text-to-audio/text-to-audio.wxml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 选择语音类型
+
+
+
+
+
+
+
diff --git a/pages/text-to-audio/text-to-audio.wxss b/pages/text-to-audio/text-to-audio.wxss
new file mode 100644
index 0000000..f3dd802
--- /dev/null
+++ b/pages/text-to-audio/text-to-audio.wxss
@@ -0,0 +1,232 @@
+/**text-to-audio.wxss**/
+
+/* 防止横向滚动 */
+page {
+ width: 100%;
+ height: 100%;
+ overflow-x: hidden;
+ box-sizing: border-box;
+}
+
+/* 动态背景样式 */
+.bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ filter: blur(120px);
+ z-index: -1;
+}
+
+.ball {
+ position: absolute;
+ border-radius: 50%;
+}
+
+.ball-1 {
+ width: 400rpx;
+ height: 400rpx;
+ background-color: #4472c4;
+ animation: move-1 8s ease-in-out infinite;
+}
+
+.ball-2 {
+ width: 500rpx;
+ height: 500rpx;
+ background-color: #00b050;
+ animation: move-2 10s ease-in-out infinite;
+}
+
+@keyframes move-1 {
+ 0% {
+ transform: translate(0, 0);
+ }
+ 25% {
+ transform: translate(500rpx, 600rpx);
+ }
+ 50% {
+ transform: translate(-300rpx, 1000rpx);
+ }
+ 75% {
+ transform: translate(-400rpx, 400rpx);
+ }
+ 100% {
+ transform: translate(0, 0);
+ }
+}
+
+@keyframes move-2 {
+ 0% {
+ transform: translate(100rpx, 100rpx);
+ }
+ 25% {
+ transform: translate(-500rpx, -200rpx);
+ }
+ 50% {
+ transform: translate(400rpx, 800rpx);
+ }
+ 75% {
+ transform: translate(600rpx, 200rpx);
+ }
+ 100% {
+ transform: translate(100rpx, 100rpx);
+ }
+}
+
+/* 全局移除按钮边框 */
+button {
+ border: none !important;
+ outline: none !important;
+}
+
+button::after {
+ border: none !important;
+}
+
+button::before {
+ border: none !important;
+}
+
+/* 文本输入区 */
+.text-view {
+ width: 100%;
+ margin-bottom: 30rpx;
+}
+
+.text-area {
+ width: 100%;
+ min-height: 300rpx;
+ padding: 30rpx;
+ box-sizing: border-box;
+ border-radius: 24rpx;
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(10rpx);
+ font-size: 32rpx;
+ line-height: 1.6;
+ color: #333;
+ box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
+ transition: all 0.3s ease;
+}
+
+.text-area:focus {
+ background: rgba(255, 255, 255, 1);
+ box-shadow: 0 12rpx 48rpx rgba(102, 126, 234, 0.15);
+ transform: translateY(-4rpx);
+}
+
+/* 按钮区域 */
+.button-view {
+ display: flex;
+ gap: 20rpx;
+ margin-bottom: 30rpx;
+}
+
+.button-view button {
+ flex: 1;
+ height: 96rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 48rpx;
+ font-size: 30rpx;
+ font-weight: 600;
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: visible;
+}
+
+.btn-icon {
+ margin-right: 8rpx;
+ font-size: 28rpx;
+}
+
+/* 播放按钮 */
+.btn-play {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.4);
+}
+
+.btn-play:active {
+ transform: translateY(4rpx) scale(0.98);
+ box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.4);
+}
+
+/* 下载按钮 */
+.btn-download {
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+ color: white;
+ box-shadow: 0 8rpx 24rpx rgba(240, 147, 251, 0.4);
+}
+
+.btn-download:active {
+ transform: translateY(4rpx) scale(0.98);
+ box-shadow: 0 4rpx 12rpx rgba(240, 147, 251, 0.4);
+}
+
+/* 清空按钮 */
+.btn-clear {
+ background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
+ color: #8b4513;
+ box-shadow: 0 8rpx 24rpx rgba(252, 182, 159, 0.3);
+}
+
+.btn-clear:active {
+ transform: translateY(4rpx) scale(0.98);
+ box-shadow: 0 4rpx 12rpx rgba(252, 182, 159, 0.3);
+}
+
+/* 语音类型选择区域 */
+.type-view {
+ width: 100%;
+ padding: 30rpx;
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 250, 252, 0.9) 100%);
+ backdrop-filter: blur(20rpx);
+ border-radius: 24rpx;
+ box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
+ box-sizing: border-box;
+}
+
+.type-title {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: #666;
+ margin-bottom: 20rpx;
+ text-align: center;
+}
+
+.type-button {
+ width: 100%;
+ height: 80rpx;
+ margin-bottom: 16rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 40rpx;
+ font-size: 28rpx;
+ font-weight: 500;
+ transition: all 0.3s ease;
+ background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
+ color: #666;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
+}
+
+.type-button:last-child {
+ margin-bottom: 0;
+}
+
+.type-button.selected {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.4);
+ transform: scale(1.02);
+}
+
+.type-button:active {
+ transform: translateY(2rpx) scale(0.98);
+}
+
+.type-button.selected:active {
+ transform: translateY(2rpx) scale(1.0);
+}