|
|
<template>
|
|
|
<view>
|
|
|
<scroll-view class="scroll-view" scroll-y scroll-with-animation :scroll-top="top">
|
|
|
<view style="padding: 30rpx 30rpx 240rpx;">
|
|
|
<view class="message" :class="[item.userType]" v-for="(item, index) in list" :key="index"
|
|
|
@click="msgClick(item, index)">
|
|
|
<image :src="item.avatar" v-if="item.userType === 'friend'" class="avatar" mode="widthFix"></image>
|
|
|
<view class="content" v-if="item.messageType === 'image'">
|
|
|
<image :src="item.content" mode="widthFix"></image>
|
|
|
</view>
|
|
|
|
|
|
<!-- 加载状态消息 -->
|
|
|
<view class="content loading-content" v-else-if="item.isLoading">
|
|
|
<view class="loading-dots">
|
|
|
<view class="dot" v-for="i in 3" :key="i"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<view class="content voice-content" v-else-if="item.messageType === 'voice'">
|
|
|
<a-trumpet :isPlay="item.isplay" style="margin-right: 4rpx;"></a-trumpet>
|
|
|
{{ item.content }}
|
|
|
</view>
|
|
|
<view class="content" v-else>
|
|
|
{{ item.content }}
|
|
|
</view>
|
|
|
<image :src="item.avatar" v-if="item.userType === 'self'" class="avatar" mode="widthFix"></image>
|
|
|
</view>
|
|
|
|
|
|
|
|
|
</view>
|
|
|
</scroll-view>
|
|
|
|
|
|
<view class="tool">
|
|
|
<block v-if="messageType === 'text'">
|
|
|
<image src="../../static/voice.png" mode="widthFix" class="left-icon" @click="messageType = 'voice'">
|
|
|
</image>
|
|
|
<input type="text" v-model="content" class="input" @confirm="send" />
|
|
|
<image src="../../static/thumb.png" mode="widthFix" class="thumb" @click="send"></image>
|
|
|
</block>
|
|
|
<block v-else-if="messageType === 'voice'">
|
|
|
<image src="../../static/text.png" mode="widthFix" class="left-icon" @click="messageType = 'text'">
|
|
|
</image>
|
|
|
<text class="voice-crl" @touchstart="touchstart" @touchend="touchend">{{ recordStart ? '松开 发送' : '按住 说话'
|
|
|
}}</text>
|
|
|
</block>
|
|
|
</view>
|
|
|
|
|
|
<view v-if="recordStart" class="audio-animation">
|
|
|
<view class="audio-wave">
|
|
|
<text class="audio-wave-text" v-for="item in 10" :style="{ 'animation-delay': `${item / 10}s` }"></text>
|
|
|
<view class="text">松开 发送</view>
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
const innerAudioContext = uni.createInnerAudioContext();
|
|
|
const plugin = requirePlugin("WechatSI")
|
|
|
const manager = plugin.getRecordRecognitionManager()
|
|
|
|
|
|
import permision from '../../common/permission.js';
|
|
|
|
|
|
import {
|
|
|
getAIReply,
|
|
|
getChatLog
|
|
|
} from '@/services/api.js';
|
|
|
|
|
|
export default {
|
|
|
data() {
|
|
|
return {
|
|
|
person_id: '',
|
|
|
permisionState: false,
|
|
|
content: '',
|
|
|
list: [],
|
|
|
top: 0,
|
|
|
messageType: 'voice', // text 发送文本;voice 发送语音
|
|
|
recordStart: false,
|
|
|
isLoading: false, // 添加loading状态变量
|
|
|
playingIndex: -1,
|
|
|
};
|
|
|
},
|
|
|
onShow() {
|
|
|
// uni.hideHomeButton()
|
|
|
},
|
|
|
onLoad(options) {
|
|
|
|
|
|
this.person_id = uni.getStorageSync('person_id');
|
|
|
|
|
|
|
|
|
uni.setNavigationBarTitle({
|
|
|
title: '芽芽星语'
|
|
|
})
|
|
|
this._friendAvatar = 'https://ww1.sinaimg.cn/mw690/a1a9fb72gy1hl1cd1znplj20j60j6n0h.jpg'
|
|
|
this._selfAvatar = 'https://ask.dcloud.net.cn/uploads/avatar/001/43/07/62_avatar_max.jpg?=1705916918'
|
|
|
|
|
|
|
|
|
getChatLog({
|
|
|
page: '1',
|
|
|
page_size: '10',
|
|
|
person_id: this.person_id
|
|
|
}).then((res) => {
|
|
|
const messages = [];
|
|
|
res.data.data.forEach(item => {
|
|
|
// 添加用户消息
|
|
|
messages.push({
|
|
|
content: item.user_input,
|
|
|
userType: 'self',
|
|
|
avatar: this._selfAvatar
|
|
|
});
|
|
|
|
|
|
// 添加AI回复消息
|
|
|
messages.push({
|
|
|
content: `${Math.ceil(item.duration)}''`,
|
|
|
audioSrc: item.audio_url,
|
|
|
userType: 'friend',
|
|
|
avatar: this._friendAvatar,
|
|
|
messageType: 'voice',
|
|
|
isplay: false // 明确设置初始值
|
|
|
});
|
|
|
|
|
|
// 使用数组赋值而不是push,确保响应式更新
|
|
|
this.list = messages;
|
|
|
|
|
|
});
|
|
|
}).catch((err) => {
|
|
|
console.log(err)
|
|
|
}).finally(() => {
|
|
|
this.scrollToBottom();
|
|
|
})
|
|
|
|
|
|
|
|
|
// this.list = [{
|
|
|
// content: '历史消息',
|
|
|
// userType: 'friend',
|
|
|
// messageType: 'voice',
|
|
|
// avatar: this._friendAvatar
|
|
|
// },
|
|
|
// {
|
|
|
// content: '历史消息',
|
|
|
// userType: 'self',
|
|
|
// avatar: this._selfAvatar
|
|
|
// }
|
|
|
// ]
|
|
|
|
|
|
manager.onStart = () => {
|
|
|
console.log('==onStart==')
|
|
|
this.recordStart = true
|
|
|
};
|
|
|
|
|
|
manager.onStop = (res) => {
|
|
|
console.log('==onStop==', res)
|
|
|
this.recordStart = false
|
|
|
this.voicePath = res.tempFilePath;
|
|
|
this.voiceText = res.result;
|
|
|
if (this.voiceText == '') {
|
|
|
this.voiceText = '周围太安静啦~再试试~';
|
|
|
}
|
|
|
|
|
|
// 创建新的消息数组
|
|
|
const newList = [...this.list];
|
|
|
|
|
|
// 添加用户消息
|
|
|
newList.push({
|
|
|
content: this.voiceText,
|
|
|
userType: 'self',
|
|
|
avatar: this._selfAvatar
|
|
|
});
|
|
|
|
|
|
// 添加loading消息
|
|
|
const loadingIndex = newList.length;
|
|
|
newList.push({
|
|
|
content: 'loading',
|
|
|
userType: 'friend',
|
|
|
avatar: this._friendAvatar,
|
|
|
isLoading: true,
|
|
|
isplay: false
|
|
|
});
|
|
|
|
|
|
// 更新整个列表
|
|
|
this.list = newList;
|
|
|
this.scrollToBottom();
|
|
|
|
|
|
// this.list.push({
|
|
|
// content: this.voiceText,
|
|
|
// userType: 'self',
|
|
|
// avatar: this._selfAvatar
|
|
|
// })
|
|
|
|
|
|
// this.scrollToBottom();
|
|
|
|
|
|
// // 先添加一个loading状态的消息
|
|
|
// this.isLoading = true;
|
|
|
// this.list.push({
|
|
|
// content: 'loading',
|
|
|
// userType: 'friend',
|
|
|
// avatar: this._friendAvatar,
|
|
|
// isLoading: true,
|
|
|
// isplay: false
|
|
|
// })
|
|
|
// this.loadingMessageIndex = this.list.length - 1;
|
|
|
// this.scrollToBottom();
|
|
|
|
|
|
getAIReply({
|
|
|
prompt: this.voiceText,
|
|
|
person_id: this.person_id
|
|
|
}).then((res) => {
|
|
|
// 再次创建新数组以更新loading消息
|
|
|
const updatedList = [...this.list];
|
|
|
updatedList[loadingIndex] = {
|
|
|
content: `${Math.ceil(res.data.duration)}''`,
|
|
|
audioSrc: res.data.url,
|
|
|
userType: 'friend',
|
|
|
avatar: this._friendAvatar,
|
|
|
messageType: 'voice',
|
|
|
isplay: false
|
|
|
};
|
|
|
this.list = updatedList;
|
|
|
|
|
|
innerAudioContext.src = res.data.url;
|
|
|
innerAudioContext.play();
|
|
|
|
|
|
// 监听播放结束事件
|
|
|
innerAudioContext.onEnded(() => {
|
|
|
this.$set(this.list[loadingIndex], 'isplay', false);
|
|
|
});
|
|
|
|
|
|
// 监听错误事件
|
|
|
innerAudioContext.onError(() => {
|
|
|
this.$set(this.list[loadingIndex], 'isplay', false);
|
|
|
});
|
|
|
}).catch((err) => {
|
|
|
// 错误处理:将loading消息更新为错误提示
|
|
|
this.list[this.loadingMessageIndex] = {
|
|
|
content: '抱歉,获取回复失败,请重试',
|
|
|
userType: 'friend',
|
|
|
avatar: this._friendAvatar,
|
|
|
isLoading: false
|
|
|
};
|
|
|
}).finally(() => {
|
|
|
this.isLoading = false;
|
|
|
this.scrollToBottom();
|
|
|
})
|
|
|
};
|
|
|
// 识别错误事件
|
|
|
manager.onError = (err) => {
|
|
|
uni.showToast({
|
|
|
title: '错误(' + err.retcode + '):' + err.msg,
|
|
|
icon: 'none',
|
|
|
duration: 2000, //持续时间为 2秒
|
|
|
})
|
|
|
};
|
|
|
},
|
|
|
onHide() {
|
|
|
if (this._innerAudioContext) {
|
|
|
this._innerAudioContext.stop()
|
|
|
}
|
|
|
},
|
|
|
created() {
|
|
|
this.checkPermission();
|
|
|
},
|
|
|
methods: {
|
|
|
|
|
|
upx2px(upx) {
|
|
|
return uni.upx2px(upx) + 'px';
|
|
|
},
|
|
|
send() {
|
|
|
|
|
|
if (this.content.length == 0) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const _content = this.content;
|
|
|
|
|
|
// 创建新的消息数组
|
|
|
const newList = [...this.list];
|
|
|
|
|
|
// 添加用户消息
|
|
|
newList.push({
|
|
|
content: this.content,
|
|
|
userType: 'self',
|
|
|
avatar: this._selfAvatar
|
|
|
});
|
|
|
|
|
|
// 添加loading消息
|
|
|
const loadingIndex = newList.length;
|
|
|
newList.push({
|
|
|
content: 'loading',
|
|
|
userType: 'friend',
|
|
|
avatar: this._friendAvatar,
|
|
|
isLoading: true,
|
|
|
isplay: false
|
|
|
});
|
|
|
|
|
|
// 更新整个列表
|
|
|
this.list = newList;
|
|
|
this.scrollToBottom();
|
|
|
this.content = '';
|
|
|
|
|
|
|
|
|
getAIReply({
|
|
|
prompt: _content,
|
|
|
person_id: this.person_id
|
|
|
}).then((res) => {
|
|
|
// 再次创建新数组以更新loading消息
|
|
|
const updatedList = [...this.list];
|
|
|
updatedList[loadingIndex] = {
|
|
|
content: `${Math.ceil(res.data.duration)}''`,
|
|
|
audioSrc: res.data.url,
|
|
|
userType: 'friend',
|
|
|
avatar: this._friendAvatar,
|
|
|
messageType: 'voice',
|
|
|
isplay: true
|
|
|
};
|
|
|
this.list = updatedList;
|
|
|
|
|
|
innerAudioContext.src = res.data.url;
|
|
|
innerAudioContext.play();
|
|
|
|
|
|
// 监听播放结束事件
|
|
|
innerAudioContext.onEnded(() => {
|
|
|
this.$set(this.list[loadingIndex], 'isplay', false);
|
|
|
});
|
|
|
|
|
|
// 监听错误事件
|
|
|
innerAudioContext.onError(() => {
|
|
|
this.$set(this.list[loadingIndex], 'isplay', false);
|
|
|
});
|
|
|
}).catch((err) => {
|
|
|
console.log(err)
|
|
|
// 错误处理:将loading消息更新为错误提示
|
|
|
this.list[this.loadingMessageIndex] = {
|
|
|
content: '抱歉,获取回复失败,请重试',
|
|
|
userType: 'friend',
|
|
|
avatar: this._friendAvatar,
|
|
|
isLoading: false
|
|
|
};
|
|
|
}).finally(() => {
|
|
|
this.isLoading = false;
|
|
|
this.scrollToBottom();
|
|
|
})
|
|
|
},
|
|
|
|
|
|
scrollToBottom() {
|
|
|
this.top = this.list.length * 1000
|
|
|
},
|
|
|
|
|
|
msgClick(data, index) {
|
|
|
if (data.messageType === 'voice') {
|
|
|
// 先停止之前可能在播放的音频
|
|
|
innerAudioContext.stop();
|
|
|
|
|
|
// 重置所有消息的播放状态
|
|
|
this.list.forEach((msg, i) => {
|
|
|
if (msg.messageType === 'voice') {
|
|
|
this.$set(this.list[i], 'isplay', false);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 设置当前消息的播放状态
|
|
|
this.$set(this.list[index], 'isplay', true);
|
|
|
|
|
|
// 播放音频
|
|
|
innerAudioContext.src = data.audioSrc;
|
|
|
innerAudioContext.play();
|
|
|
|
|
|
// 监听播放结束事件
|
|
|
innerAudioContext.onEnded(() => {
|
|
|
this.$set(this.list[index], 'isplay', false);
|
|
|
});
|
|
|
|
|
|
// 监听错误事件
|
|
|
innerAudioContext.onError(() => {
|
|
|
this.$set(this.list[index], 'isplay', false);
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
|
|
|
authTips() {
|
|
|
uni.showModal({
|
|
|
title: '提示',
|
|
|
content: '您拒绝了麦克风权限,将导致功能不能正常使用,去设置权限?',
|
|
|
confirmText: '去设置',
|
|
|
cancelText: '取消',
|
|
|
success: (res) => {
|
|
|
if (res.confirm) {
|
|
|
uni.openSetting({
|
|
|
success: (res) => {
|
|
|
if (res.authSetting['scope.record']) {
|
|
|
console.log("已授权麦克风");
|
|
|
this._recordAuth = true
|
|
|
} else {
|
|
|
// 未授权
|
|
|
wx.showModal({
|
|
|
title: '提示',
|
|
|
content: '您未授权麦克风,功能将无法使用',
|
|
|
showCancel: false,
|
|
|
confirmText: '知道了'
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
|
|
|
touchstart() {
|
|
|
if (!this.permisionState) {
|
|
|
this.checkPermission();
|
|
|
return;
|
|
|
}
|
|
|
manager.start({
|
|
|
duration: 60000,
|
|
|
lang: "zh_CN"
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
async checkPermission() {
|
|
|
var that = this;
|
|
|
// #ifdef APP-PLUS
|
|
|
// 先判断os
|
|
|
let os = uni.getSystemInfoSync().osName;
|
|
|
if (os == 'ios') {
|
|
|
this.permisionState = await permision.judgeIosPermission('record');
|
|
|
} else {
|
|
|
this.permisionState = await permision.requestAndroidPermission('android.permission.RECORD_AUDIO');
|
|
|
}
|
|
|
if (this.permisionState !== true && this.permisionState !== 1) {
|
|
|
uni.showToast({
|
|
|
title: '请先授权使用录音',
|
|
|
icon: 'none'
|
|
|
});
|
|
|
return;
|
|
|
}
|
|
|
// #endif
|
|
|
|
|
|
// #ifdef MP-WEIXIN
|
|
|
uni.authorize({
|
|
|
scope: 'scope.record',
|
|
|
success(e) {
|
|
|
that.permisionState = true;
|
|
|
},
|
|
|
fail() {
|
|
|
uni.showToast({
|
|
|
title: '请授权使用录音',
|
|
|
icon: 'none'
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
// #endif
|
|
|
},
|
|
|
|
|
|
touchend() {
|
|
|
if (!this.recordStart) return
|
|
|
manager.stop();
|
|
|
},
|
|
|
|
|
|
//播放声音
|
|
|
play(src) {
|
|
|
this._innerAudioContext = wx.createInnerAudioContext()
|
|
|
this._innerAudioContext.src = src
|
|
|
this._innerAudioContext.play()
|
|
|
this._innerAudioContext.onPlay(() => {
|
|
|
console.log('开始播放')
|
|
|
})
|
|
|
this._innerAudioContext.onEnded(() => {
|
|
|
// 播放完毕,清除音频链接
|
|
|
console.log('播放完毕');
|
|
|
})
|
|
|
this._innerAudioContext.onError((res) => {
|
|
|
console.log('audio play error', res)
|
|
|
})
|
|
|
},
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.scroll-view {
|
|
|
/* #ifdef H5 */
|
|
|
height: calc(100vh - 44px);
|
|
|
/* #endif */
|
|
|
/* #ifndef H5 */
|
|
|
height: 100vh;
|
|
|
/* #endif */
|
|
|
background: #eee;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
.message {
|
|
|
display: flex;
|
|
|
align-items: flex-start;
|
|
|
margin-bottom: 30rpx;
|
|
|
|
|
|
.avatar {
|
|
|
width: 80rpx;
|
|
|
height: 80rpx;
|
|
|
border-radius: 10rpx;
|
|
|
margin-right: 30rpx;
|
|
|
}
|
|
|
|
|
|
.content {
|
|
|
min-height: 86rpx;
|
|
|
max-width: 60vw;
|
|
|
box-sizing: border-box;
|
|
|
font-size: 28rpx;
|
|
|
line-height: 1.3;
|
|
|
padding: 20rpx;
|
|
|
border-radius: 10rpx;
|
|
|
background: #fff;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
|
|
|
image {
|
|
|
width: 200rpx;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.voice-content {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
width: 24vw;
|
|
|
}
|
|
|
|
|
|
&.self {
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
.avatar {
|
|
|
margin: 0 0 0 30rpx;
|
|
|
}
|
|
|
|
|
|
.content {
|
|
|
position: relative;
|
|
|
|
|
|
&::after {
|
|
|
position: absolute;
|
|
|
content: '';
|
|
|
width: 0;
|
|
|
height: 0;
|
|
|
border: 16rpx solid transparent;
|
|
|
border-left: 16rpx solid #fff;
|
|
|
right: -28rpx;
|
|
|
top: 24rpx;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
&.friend {
|
|
|
.content {
|
|
|
position: relative;
|
|
|
|
|
|
&::after {
|
|
|
position: absolute;
|
|
|
content: '';
|
|
|
width: 0;
|
|
|
height: 0;
|
|
|
border: 16rpx solid transparent;
|
|
|
border-right: 16rpx solid #fff;
|
|
|
left: -28rpx;
|
|
|
top: 24rpx;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.tool {
|
|
|
position: fixed;
|
|
|
width: 100%;
|
|
|
min-height: 120rpx;
|
|
|
left: 0;
|
|
|
bottom: 0;
|
|
|
background: #fff;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
box-sizing: border-box;
|
|
|
padding: 20rpx 24rpx 20rpx 40rpx;
|
|
|
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom)/2) !important;
|
|
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom)/2) !important;
|
|
|
|
|
|
.left-icon {
|
|
|
width: 56rpx;
|
|
|
height: 56rpx;
|
|
|
margin-right: 10rpx;
|
|
|
}
|
|
|
|
|
|
.input,
|
|
|
.voice-crl {
|
|
|
background: #eee;
|
|
|
border-radius: 10rpx;
|
|
|
height: 70rpx;
|
|
|
margin-left: 10rpx;
|
|
|
margin-right: 20rpx;
|
|
|
flex: 1;
|
|
|
padding: 0 20rpx;
|
|
|
box-sizing: border-box;
|
|
|
font-size: 28rpx;
|
|
|
}
|
|
|
|
|
|
.thumb {
|
|
|
width: 64rpx;
|
|
|
height: 64rpx;
|
|
|
}
|
|
|
|
|
|
.voice-crl {
|
|
|
text-align: center;
|
|
|
line-height: 70rpx;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.audio-animation {
|
|
|
position: fixed;
|
|
|
// width: 100vw;
|
|
|
// height: 100vh;
|
|
|
left: 50%;
|
|
|
top: 50%;
|
|
|
transform: translate(-50%, -50%);
|
|
|
z-index: 202410;
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
|
|
|
.text {
|
|
|
text-align: center;
|
|
|
font-size: 28rpx;
|
|
|
color: #333;
|
|
|
margin-top: 60rpx;
|
|
|
}
|
|
|
|
|
|
.audio-wave {
|
|
|
padding: 50rpx;
|
|
|
|
|
|
.audio-wave-text {
|
|
|
background-color: blue;
|
|
|
width: 7rpx;
|
|
|
height: 12rpx;
|
|
|
margin: 0 6rpx;
|
|
|
border-radius: 5rpx;
|
|
|
display: inline-block;
|
|
|
border: none;
|
|
|
animation: wave 0.25s ease-in-out;
|
|
|
animation-iteration-count: infinite;
|
|
|
animation-direction: alternate;
|
|
|
}
|
|
|
|
|
|
/* 声波动画 */
|
|
|
@keyframes wave {
|
|
|
from {
|
|
|
transform: scaleY(1);
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
transform: scaleY(4);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.loading-content {
|
|
|
min-width: 100rpx;
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.loading-dots {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.dot {
|
|
|
width: 12rpx;
|
|
|
height: 12rpx;
|
|
|
background-color: #999;
|
|
|
border-radius: 50%;
|
|
|
margin: 0 6rpx;
|
|
|
animation: dot-animation 1.4s infinite ease-in-out both;
|
|
|
}
|
|
|
|
|
|
.dot:nth-child(1) {
|
|
|
animation-delay: -0.32s;
|
|
|
}
|
|
|
|
|
|
.dot:nth-child(2) {
|
|
|
animation-delay: -0.16s;
|
|
|
}
|
|
|
|
|
|
@keyframes dot-animation {
|
|
|
|
|
|
0%,
|
|
|
80%,
|
|
|
100% {
|
|
|
transform: scale(0);
|
|
|
}
|
|
|
|
|
|
40% {
|
|
|
transform: scale(1);
|
|
|
}
|
|
|
}
|
|
|
</style> |