657 lines
19 KiB
Vue
657 lines
19 KiB
Vue
<template>
|
||
<view class="page" :class="{ big: isH5 }">
|
||
<view class="panel">
|
||
<view class="section-title">模拟识别</view>
|
||
<input class="input" v-model="simulateText" placeholder="输入模拟识别文本,例如:打开预警中心 / 去监区看板 / 打开个体画像" />
|
||
<view class="chips">
|
||
<view class="chip" v-for="t in quickTexts" :key="t" @click="useQuickText(t)">{{ t }}</view>
|
||
</view>
|
||
<view class="actions single">
|
||
<button class="btn full" type="primary" :disabled="!simulateText || loading" @click="onSimulate">模拟识别并跳转</button>
|
||
</view>
|
||
<view v-if="errorMsg" class="error">{{ errorMsg }}</view>
|
||
</view>
|
||
|
||
<view class="panel">
|
||
<view class="section-title">识别文本</view>
|
||
<view class="text-box" :class="{ empty: !recognizedText }">
|
||
<text v-if="recognizedText">{{ recognizedText }}</text>
|
||
<text v-else>暂无识别结果</text>
|
||
</view>
|
||
<view v-if="errorMsg" class="error">{{ errorMsg }}</view>
|
||
|
||
<view class="actions">
|
||
<button class="btn" type="primary" :disabled="!recognizedText || loading" @click="onExecute">执行</button>
|
||
<button class="btn" :disabled="recording || loading" @click="onClear">清空</button>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="record-wrap">
|
||
<view class="status">{{ recording ? '录音中…' : '按住说话' }}</view>
|
||
<view class="record-btn"
|
||
:class="{ recording: recording }"
|
||
@touchstart="startRecord"
|
||
@touchend="stopRecord"
|
||
@touchcancel="stopRecord">
|
||
<text class="record-btn-text">{{ loading ? '识别中…' : (recording ? '松开结束' : '按住说话') }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="panel">
|
||
<view class="section-title">执行结果</view>
|
||
<view class="text-box" :class="{ empty: !commandResult }">
|
||
<text v-if="commandResult">{{ commandResult }}</text>
|
||
<text v-else>暂无</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { request, uploadFile } from '../../utils/request'
|
||
|
||
const TEMPLATE_EXPIRE_MS = 30 * 1000
|
||
|
||
const SCENE_TEMPLATES = [
|
||
{
|
||
id: 'morning_brief',
|
||
name: '早会汇报模板',
|
||
triggers: ['调用早会汇报模板', '打开早会汇报模板', '早会汇报模板'],
|
||
options: [
|
||
{ label: '1. 汇报昨日预警情况', action: { type: 'voice_command', text: '打开预警中心' } },
|
||
{ label: '2. 展示本周测评完成率', action: { type: 'navigate', url: '/pages/chart/templates' } }
|
||
]
|
||
},
|
||
{
|
||
id: 'duty_patrol',
|
||
name: '值班巡查模板',
|
||
triggers: ['调用值班巡查模板', '打开值班巡查模板', '值班巡查模板', '值班巡查'],
|
||
options: [
|
||
{ label: '1. 打开监区看板', action: { type: 'navigate', url: '/pages/index/index' } },
|
||
{ label: '2. 查看预警中心', action: { type: 'voice_command', text: '打开预警中心' } }
|
||
]
|
||
}
|
||
]
|
||
|
||
function normalizeText(text) {
|
||
return String(text || '').trim()
|
||
}
|
||
|
||
function parseSelection(text) {
|
||
const s = normalizeText(text)
|
||
const m1 = s.match(/^(?:选|选择)?\s*(\d{1,2})\s*(?:项|号)?$/)
|
||
if (m1 && m1[1]) return parseInt(m1[1], 10)
|
||
const map = { '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10 }
|
||
const m2 = s.match(/^(?:选|选择)?\s*([一二三四五六七八九十])\s*(?:项|号)?$/)
|
||
if (m2 && m2[1] && map[m2[1]]) return map[m2[1]]
|
||
return null
|
||
}
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
isH5: false,
|
||
recording: false,
|
||
loading: false,
|
||
autoStart: false,
|
||
simulateText: '',
|
||
quickTexts: ['打开预警中心', '去监区看板', '打开个体画像', '打开图表模板', '打开干预任务'],
|
||
recognizedText: '',
|
||
commandResult: '',
|
||
errorMsg: '',
|
||
audioPath: '',
|
||
activeTemplateId: '',
|
||
activeTemplateOptions: [],
|
||
templateExpireAt: 0
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
try {
|
||
const info = uni.getSystemInfoSync()
|
||
const p = info ? (info.uniPlatform || info.platform) : ''
|
||
this.isH5 = p === 'web' || p === 'h5'
|
||
} catch (e) {
|
||
this.isH5 = false
|
||
}
|
||
this.autoStart = !!(options && (options.autostart === '1' || options.autostart === 1 || options.autostart === 'true'))
|
||
this.recorder = uni.getRecorderManager()
|
||
this.recorder.onStop((res) => {
|
||
this.onRecorderStop(res)
|
||
})
|
||
this.recorder.onError((err) => {
|
||
this.loading = false
|
||
this.recording = false
|
||
this.errorMsg = (err && err.errMsg) ? err.errMsg : '录音失败'
|
||
})
|
||
|
||
if (this.autoStart) {
|
||
setTimeout(() => {
|
||
if (!this.recording && !this.loading) this.startRecord()
|
||
}, 300)
|
||
}
|
||
},
|
||
methods: {
|
||
clearTemplateSession() {
|
||
this.activeTemplateId = ''
|
||
this.activeTemplateOptions = []
|
||
this.templateExpireAt = 0
|
||
},
|
||
getActiveTemplate() {
|
||
if (!this.activeTemplateId) return null
|
||
const now = Date.now()
|
||
if (!this.templateExpireAt || now > this.templateExpireAt) {
|
||
this.clearTemplateSession()
|
||
return null
|
||
}
|
||
for (let i = 0; i < SCENE_TEMPLATES.length; i++) {
|
||
if (SCENE_TEMPLATES[i].id === this.activeTemplateId) return SCENE_TEMPLATES[i]
|
||
}
|
||
return null
|
||
},
|
||
findTemplateByText(text) {
|
||
const s = normalizeText(text)
|
||
if (!s) return null
|
||
for (let i = 0; i < SCENE_TEMPLATES.length; i++) {
|
||
const t = SCENE_TEMPLATES[i]
|
||
const triggers = t && t.triggers ? t.triggers : []
|
||
for (let j = 0; j < triggers.length; j++) {
|
||
if (s.includes(triggers[j])) return t
|
||
}
|
||
}
|
||
return null
|
||
},
|
||
openTemplateMenu(tpl) {
|
||
if (!tpl || !tpl.options || !tpl.options.length) return
|
||
this.activeTemplateId = tpl.id
|
||
this.activeTemplateOptions = tpl.options
|
||
this.templateExpireAt = Date.now() + TEMPLATE_EXPIRE_MS
|
||
this.commandResult = `已进入${tpl.name},请说“选1/选2”…(${Math.floor(TEMPLATE_EXPIRE_MS / 1000)}秒内有效)`
|
||
|
||
uni.showActionSheet({
|
||
itemList: tpl.options.map(o => o.label),
|
||
success: (res) => {
|
||
const idx = (res && typeof res.tapIndex === 'number') ? res.tapIndex + 1 : null
|
||
if (!idx) return
|
||
this.executeTemplateSelection(idx)
|
||
}
|
||
})
|
||
},
|
||
executeVoiceCommandText(text) {
|
||
return request({
|
||
url: '/voice/command',
|
||
method: 'POST',
|
||
data: { text }
|
||
})
|
||
},
|
||
executeTemplateSelection(selIndex) {
|
||
const tpl = this.getActiveTemplate()
|
||
if (!tpl) {
|
||
this.commandResult = '模板已失效,请重新说“调用XX模板”'
|
||
return Promise.resolve()
|
||
}
|
||
const opt = this.activeTemplateOptions[selIndex - 1]
|
||
if (!opt) {
|
||
this.commandResult = `未找到选项${selIndex},请重试`
|
||
return Promise.resolve()
|
||
}
|
||
this.clearTemplateSession()
|
||
if (opt.action && opt.action.type === 'navigate' && opt.action.url) {
|
||
uni.navigateTo({ url: opt.action.url })
|
||
this.commandResult = '已跳转:' + opt.action.url
|
||
return Promise.resolve()
|
||
}
|
||
if (opt.action && opt.action.type === 'voice_command' && opt.action.text) {
|
||
this.loading = true
|
||
return this.executeVoiceCommandText(opt.action.text).then((res) => {
|
||
this.loading = false
|
||
return this.handleCommandResponse(res)
|
||
}).catch((e) => {
|
||
this.loading = false
|
||
this.errorMsg = e && e.message ? e.message : '网络错误'
|
||
})
|
||
}
|
||
this.commandResult = opt.label
|
||
return Promise.resolve()
|
||
},
|
||
handleCommandResponse(res) {
|
||
const data = res && res.data ? res.data : null
|
||
if (!data || data.code !== 200) {
|
||
this.errorMsg = (data && data.msg) ? data.msg : '执行失败'
|
||
return
|
||
}
|
||
const type = data.type
|
||
const payload = data.payload || {}
|
||
if (type === 'analysis') {
|
||
this.commandResult = payload.result || '暂无分析结果'
|
||
return
|
||
}
|
||
if (type === 'navigate' && payload.url) {
|
||
uni.navigateTo({ url: payload.url })
|
||
this.commandResult = '已跳转:' + payload.url
|
||
return
|
||
}
|
||
if (type === 'ask_clarify') {
|
||
this.commandResult = payload.message || '需要澄清指令'
|
||
return
|
||
}
|
||
this.commandResult = JSON.stringify(data, null, 2)
|
||
},
|
||
executeAnalyzeText(text) {
|
||
return request({
|
||
url: '/voice/analyze',
|
||
method: 'POST',
|
||
data: { text }
|
||
})
|
||
},
|
||
matchAnalyze(text) {
|
||
const s = normalizeText(text)
|
||
if (!s) return null
|
||
const m = s.match(/^分析\s*([\s\S]{1,64}?)(?:数据)?$/)
|
||
if (!m) return null
|
||
const topic = normalizeText(m[1])
|
||
return { topic: topic || '相关' }
|
||
},
|
||
tryHandleSceneTemplate(text) {
|
||
const s = normalizeText(text)
|
||
if (!s) return false
|
||
|
||
// 1) 识别到模板唤起
|
||
const tpl = this.findTemplateByText(s)
|
||
if (tpl) {
|
||
this.openTemplateMenu(tpl)
|
||
return true
|
||
}
|
||
|
||
// 2) 识别到“选1/选2”
|
||
const sel = parseSelection(s)
|
||
if (sel != null) {
|
||
const activeTpl = this.getActiveTemplate()
|
||
if (activeTpl) {
|
||
this.executeTemplateSelection(sel)
|
||
return true
|
||
}
|
||
}
|
||
|
||
// 3) 识别到“分析xx数据”
|
||
const analyze = this.matchAnalyze(s)
|
||
if (analyze) {
|
||
this.loading = true
|
||
this.executeAnalyzeText(s).then((res) => {
|
||
this.loading = false
|
||
this.handleCommandResponse(res)
|
||
}).catch((e) => {
|
||
this.loading = false
|
||
this.errorMsg = e && e.message ? e.message : '网络错误'
|
||
})
|
||
return true
|
||
}
|
||
|
||
return false
|
||
},
|
||
useQuickText(t) {
|
||
this.simulateText = t
|
||
},
|
||
onSimulate() {
|
||
this.errorMsg = ''
|
||
this.commandResult = ''
|
||
const text = (this.simulateText || '').trim()
|
||
if (!text) return
|
||
this.recognizedText = text
|
||
const analyze = this.matchAnalyze(text)
|
||
if (analyze) {
|
||
this.loading = true
|
||
this.executeAnalyzeText(text).then((res) => {
|
||
this.loading = false
|
||
this.handleCommandResponse(res)
|
||
}).catch((e) => {
|
||
this.loading = false
|
||
this.errorMsg = e && e.message ? e.message : '网络错误'
|
||
})
|
||
return
|
||
}
|
||
if (this.tryHandleSceneTemplate(text)) return
|
||
const match = this.matchNavigate(text)
|
||
if (!match) {
|
||
this.commandResult = '未匹配到跳转:' + text
|
||
return
|
||
}
|
||
uni.navigateTo({ url: match.url })
|
||
this.commandResult = '已跳转:' + match.name
|
||
},
|
||
matchNavigate(text) {
|
||
const s = String(text || '').toLowerCase()
|
||
const reportQueryMatch = String(text || '').match(/查询\s*([^\s的]{1,32})\s*的?报告/)
|
||
if (reportQueryMatch && reportQueryMatch[1]) {
|
||
const keyword = String(reportQueryMatch[1]).trim()
|
||
return { name: '报告列表', url: '/pages/report/index?keyword=' + encodeURIComponent(keyword) }
|
||
}
|
||
const rules = [
|
||
{ name: '预警中心', url: '/pages/warning/index', keys: ['预警', '风险', '告警', '预警中心'] },
|
||
{ name: '监区看板', url: '/pages/index/index', keys: ['看板', '监区', 'dashboard'] },
|
||
{ name: '个体画像', url: '/pages/profile/index', keys: ['画像', '个体', '人员', '档案', 'profile'] },
|
||
{ name: '图表模板', url: '/pages/chart/templates', keys: ['图表模板', '模板', '图表', 'charts'] },
|
||
{ name: '自定义图表', url: '/pages/chart/custom', keys: ['自定义图表', '自定义', 'custom'] },
|
||
{ name: '干预任务', url: '/pages/interventionTask/index', keys: ['干预', '任务', 'intervention'] },
|
||
{ name: '测评列表', url: '/pages/assessment/index', keys: ['测评', '量表', 'assessment'] },
|
||
{ name: '报告列表', url: '/pages/report/index', keys: ['报告', 'report'] },
|
||
{ name: '综合报告', url: '/pages/comprehensive/index', keys: ['综合报告', '综合', 'comprehensive'] }
|
||
]
|
||
for (let i = 0; i < rules.length; i++) {
|
||
const r = rules[i]
|
||
if (!r || !r.keys) continue
|
||
for (let j = 0; j < r.keys.length; j++) {
|
||
const k = String(r.keys[j]).toLowerCase()
|
||
if (k && s.includes(k)) return { name: r.name, url: r.url }
|
||
}
|
||
}
|
||
return null
|
||
},
|
||
startRecord() {
|
||
if (this.loading) return
|
||
this.errorMsg = ''
|
||
this.commandResult = ''
|
||
this.recording = true
|
||
this.recognizedText = ''
|
||
this.audioPath = ''
|
||
this.recorder.start({
|
||
duration: 60000,
|
||
sampleRate: 16000,
|
||
numberOfChannels: 1,
|
||
encodeBitRate: 96000,
|
||
format: 'mp3'
|
||
})
|
||
},
|
||
stopRecord() {
|
||
if (!this.recording) return
|
||
this.recording = false
|
||
this.loading = true
|
||
this.recorder.stop()
|
||
},
|
||
onRecorderStop(res) {
|
||
if (!res || !res.tempFilePath) {
|
||
this.loading = false
|
||
this.errorMsg = '未获取到录音文件'
|
||
return
|
||
}
|
||
this.uploadAndAsr(res.tempFilePath)
|
||
},
|
||
onExecute() {
|
||
this.errorMsg = ''
|
||
if (this.tryHandleSceneTemplate(this.recognizedText)) return
|
||
const analyze = this.matchAnalyze(this.recognizedText)
|
||
if (analyze) {
|
||
this.loading = true
|
||
this.executeAnalyzeText(this.recognizedText).then((res) => {
|
||
this.loading = false
|
||
this.handleCommandResponse(res)
|
||
}).catch((e) => {
|
||
this.loading = false
|
||
this.errorMsg = e && e.message ? e.message : '网络错误'
|
||
})
|
||
return
|
||
}
|
||
this.loading = true
|
||
this.executeVoiceCommandText(this.recognizedText).then((res) => {
|
||
this.loading = false
|
||
this.handleCommandResponse(res)
|
||
}).catch((e) => {
|
||
this.loading = false
|
||
this.errorMsg = e && e.message ? e.message : '网络错误'
|
||
})
|
||
},
|
||
onClear() {
|
||
this.simulateText = ''
|
||
this.recognizedText = ''
|
||
this.commandResult = ''
|
||
this.errorMsg = ''
|
||
this.audioPath = ''
|
||
this.clearTemplateSession()
|
||
},
|
||
uploadAndAsr(tempFilePath) {
|
||
uploadFile({
|
||
url: '/voice/asr',
|
||
filePath: tempFilePath,
|
||
name: 'file',
|
||
formData: { language: 'zh' }
|
||
}).then((res) => {
|
||
this.loading = false
|
||
let data = null
|
||
try {
|
||
data = JSON.parse(res.data)
|
||
} catch (e) {
|
||
this.errorMsg = '服务返回格式错误'
|
||
return
|
||
}
|
||
if (!data || data.code !== 200) {
|
||
this.errorMsg = (data && data.msg) ? data.msg : '识别失败'
|
||
return
|
||
}
|
||
this.recognizedText = data.text || ''
|
||
this.audioPath = data.audioPath || ''
|
||
if (!this.recognizedText) {
|
||
this.errorMsg = '未识别到有效文本'
|
||
}
|
||
}).catch((e) => {
|
||
this.loading = false
|
||
this.errorMsg = e && e.message ? e.message : '上传失败'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.page {
|
||
min-height: 100vh;
|
||
padding: 24rpx 24rpx 120rpx;
|
||
box-sizing: border-box;
|
||
background: #F4F6FB;
|
||
}
|
||
.page.big {
|
||
min-height: 100vh;
|
||
padding: 14rpx 14rpx 120rpx;
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
overflow: hidden;
|
||
--neon-a: rgba(0, 240, 255, 0.95);
|
||
--neon-b: rgba(60, 140, 255, 0.95);
|
||
--neon-soft: rgba(0, 240, 255, 0.18);
|
||
--glass: rgba(7, 13, 28, 0.42);
|
||
--glass-2: rgba(10, 18, 38, 0.52);
|
||
background-image:
|
||
radial-gradient(1100rpx 520rpx at 50% 14%, rgba(43, 107, 255, 0.30) 0%, rgba(6, 16, 40, 0.0) 65%),
|
||
linear-gradient(180deg, rgba(5, 11, 24, 0.90) 0%, rgba(8, 20, 45, 0.85) 42%, rgba(6, 16, 40, 0.92) 100%),
|
||
url('/static/bg.png');
|
||
background-size: auto, auto, cover;
|
||
background-position: center, center, center;
|
||
background-repeat: no-repeat, no-repeat, no-repeat;
|
||
color: rgba(242, 252, 255, 0.96);
|
||
font-size: 24rpx;
|
||
line-height: 1.5;
|
||
text-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.55);
|
||
}
|
||
.page.big:before {
|
||
content: none;
|
||
}
|
||
|
||
.panel {
|
||
background: rgba(255, 255, 255, 0.95);
|
||
border-radius: 18rpx;
|
||
padding: 24rpx;
|
||
border: 1px solid rgba(15, 23, 42, 0.06);
|
||
box-shadow: 0 10rpx 22rpx rgba(15, 23, 42, 0.05);
|
||
margin-bottom: 18rpx;
|
||
}
|
||
.page.big .panel {
|
||
position: relative;
|
||
z-index: 1;
|
||
border-radius: 18rpx;
|
||
padding: 26rpx 26rpx 22rpx;
|
||
border: 1px solid rgba(0, 240, 255, 0.16);
|
||
background-image:
|
||
linear-gradient(135deg, rgba(0, 240, 255, 0.08) 0%, rgba(60, 140, 255, 0.05) 35%, rgba(165, 90, 255, 0.06) 100%),
|
||
linear-gradient(180deg, rgba(7, 13, 28, 0.72) 0%, rgba(7, 13, 28, 0.56) 100%);
|
||
background-repeat: no-repeat;
|
||
background-size: cover;
|
||
backdrop-filter: blur(6px);
|
||
box-shadow: 0 14rpx 26rpx rgba(0, 0, 0, 0.42);
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #111827;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
.page.big .section-title {
|
||
color: rgba(242, 252, 255, 0.94);
|
||
font-weight: 900;
|
||
letter-spacing: 1rpx;
|
||
text-shadow: 0 0 16rpx rgba(0, 240, 255, 0.16);
|
||
}
|
||
|
||
.text-box {
|
||
min-height: 120rpx;
|
||
border-radius: 16rpx;
|
||
padding: 18rpx;
|
||
box-sizing: border-box;
|
||
background: rgba(15, 23, 42, 0.04);
|
||
color: #111827;
|
||
font-size: 26rpx;
|
||
line-height: 40rpx;
|
||
border: 1px solid rgba(15, 23, 42, 0.06);
|
||
}
|
||
.page.big .text-box {
|
||
background: rgba(7, 13, 28, 0.55);
|
||
color: rgba(242, 252, 255, 0.92);
|
||
border: 1px solid rgba(116, 216, 255, 0.14);
|
||
}
|
||
|
||
.text-box.empty {
|
||
color: #94A3B8;
|
||
}
|
||
.page.big .text-box.empty {
|
||
color: rgba(242, 252, 255, 0.62);
|
||
}
|
||
|
||
.error {
|
||
margin-top: 14rpx;
|
||
font-size: 24rpx;
|
||
color: #ff4d4f;
|
||
line-height: 36rpx;
|
||
}
|
||
|
||
.actions {
|
||
margin-top: 18rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 14rpx;
|
||
}
|
||
|
||
.actions.single {
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.btn {
|
||
width: 48%;
|
||
border-radius: 16rpx;
|
||
}
|
||
.page.big .btn {
|
||
border-radius: 14rpx;
|
||
}
|
||
|
||
.btn.full {
|
||
width: 100%;
|
||
}
|
||
|
||
.input {
|
||
width: 100%;
|
||
background: rgba(15, 23, 42, 0.04);
|
||
border-radius: 16rpx;
|
||
padding: 22rpx 20rpx;
|
||
font-size: 28rpx;
|
||
box-sizing: border-box;
|
||
border: 1px solid rgba(15, 23, 42, 0.06);
|
||
min-height: 92rpx;
|
||
line-height: 92rpx;
|
||
}
|
||
.page.big .input {
|
||
background: rgba(7, 13, 28, 0.78);
|
||
border-radius: 14rpx;
|
||
border: 1px solid rgba(116, 216, 255, 0.18);
|
||
color: rgba(242, 252, 255, 0.92);
|
||
}
|
||
|
||
.chips {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
margin-top: 14rpx;
|
||
gap: 14rpx;
|
||
}
|
||
|
||
.chip {
|
||
padding: 12rpx 18rpx;
|
||
border-radius: 999rpx;
|
||
font-size: 24rpx;
|
||
color: #64748B;
|
||
background: rgba(15, 23, 42, 0.04);
|
||
border: 1px solid rgba(15, 23, 42, 0.06);
|
||
}
|
||
.page.big .chip {
|
||
color: rgba(242, 252, 255, 0.82);
|
||
background: rgba(7, 13, 28, 0.55);
|
||
border: 1px solid rgba(0, 240, 255, 0.16);
|
||
box-shadow: 0 0 16rpx rgba(0, 166, 255, 0.08);
|
||
}
|
||
|
||
.record-wrap {
|
||
align-items: center;
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin: 30rpx 0 24rpx;
|
||
}
|
||
|
||
.status {
|
||
font-size: 24rpx;
|
||
color: #94A3B8;
|
||
margin-bottom: 18rpx;
|
||
}
|
||
.page.big .status {
|
||
color: rgba(242, 252, 255, 0.72);
|
||
}
|
||
|
||
.record-btn {
|
||
width: 420rpx;
|
||
height: 420rpx;
|
||
border-radius: 210rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #1677ff;
|
||
box-shadow: 0 18rpx 40rpx rgba(22, 119, 255, 0.22);
|
||
}
|
||
.page.big .record-btn {
|
||
border: 1px solid rgba(0, 240, 255, 0.22);
|
||
background: radial-gradient(circle at 30% 30%, rgba(0, 240, 255, 0.22) 0%, rgba(7, 13, 28, 0.82) 60%, rgba(2, 8, 22, 0.92) 100%);
|
||
box-shadow: 0 0 26rpx rgba(0, 240, 255, 0.14), 0 18rpx 40rpx rgba(0, 0, 0, 0.45);
|
||
}
|
||
|
||
.record-btn.recording {
|
||
background: #F56C6C;
|
||
box-shadow: 0 18rpx 40rpx rgba(245, 108, 108, 0.22);
|
||
}
|
||
.page.big .record-btn.recording {
|
||
border-color: rgba(255, 72, 92, 0.35);
|
||
background: radial-gradient(circle at 30% 30%, rgba(255, 72, 92, 0.22) 0%, rgba(7, 13, 28, 0.82) 60%, rgba(2, 8, 22, 0.92) 100%);
|
||
box-shadow: 0 0 26rpx rgba(255, 72, 92, 0.14), 0 18rpx 40rpx rgba(0, 0, 0, 0.45);
|
||
}
|
||
|
||
.record-btn-text {
|
||
color: #ffffff;
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
|
||
</style>
|