1
0
forked from dyf/APP
Files
APP/pages/100J/audioManager/AudioList.vue
2026-03-19 12:37:29 +08:00

955 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="maincontent contentBg">
<mescroll-uni class="device-list" @init="mescrollInit" @down="downCallback" @up="upCallback" :up="upOption"
:down="downOption" :fixed="false" :style="{ height: mescrollHeight + 'px' }">
<uni-swipe-action ref="swipeAction">
<view v-for="(item, index) in dataListA" class="li" :key="index" :ref="'swipeItem_' + index">
<view class="itemIcon center"
:class="{ 'displayNone': !Status.isEdit, 'check': isCheck(item, index) }"
v-on:click.stop="checkToggle(item, index)">
<image class="img" :class="{ 'displayNone': !isCheck(item, index) }"
src="/static/images/common/gou.png" mode="aspectFit"></image>
</view>
<view class="itemMain " :class="{ 'Edit': Status.isEdit }">
<uni-swipe-action-item :right-options="Options" @click="handleSwipeClick($event, item, index)"
class="device-card ">
<view @click.stop="handleFile(item, index)">
<view class="item">
<view class="itemLeft ">
<view class="title">{{ item.fileNameExt }}</view>
<view class="smallTitle">
<!-- 展示前端生成的时间 -->
<text>{{ item.createTime || Common.DateFormat(new Date(),
"yyyy年MM月dd日") }}</text>
<text v-show="item.duration">{{ item.duration }}</text>
<image :class="{ 'displayNone': !item.fileUrl }" class="img"
src="/static/images/100/volume.png" mode="aspectFit"
@click.stop="play(item, index)"></image>
</view>
</view>
<view class="itemRight ">
<view class="btn" @click.stop="Apply(item, index)"
:class="{ 'active': item.useStatus, 'btn-default': !item.useStatus }">
{{ item.useStatus == 1 ? '使用中' : '使用' }}
</view>
</view>
<view class="clear"></view>
</view>
</view>
</uni-swipe-action-item>
</view>
</view>
</uni-swipe-action>
</mescroll-uni>
<view class="footer">
<view class="addContent" :class="{ 'displayNone': Status.isEdit }">
<view class="addItem" @click="gotoRecord('Record')">
<view class="imgContent center">
<image class="img" src="/static/images/100/record.png" mode="aspectFit"></image>
</view>
<view class="txt">录制语音</view>
</view>
<view class="addItem" @click="gotoRecord('File')">
<view class="imgContent center">
<image class="img" src="/static/images/100/upload.png" mode="aspectFit"></image>
</view>
<view class="txt">上传语音</view>
</view>
<view class="addItem" @click="gotoRecord('Txt')">
<view class="imgContent center">
<image class="img" src="/static/images/100/txtToAudio.png" mode="aspectFit"></image>
</view>
<view class="txt">文字转语音</view>
</view>
</view>
<view class="editContent" :class="{ 'displayNone': !Status.isEdit }">
<view class="btn-del" @click.stop="delCheckList()">删除</view>
</view>
</view>
<MessagePopup :visible="Status.Pop.showPop" :type="Status.Pop.popType" :bgColor="Status.Pop.bgColor"
:borderColor="Status.Pop.borderColor" :textColor="Status.Pop.textColor"
:buttonBgColor="Status.Pop.buttonBgColor" :buttonTextColor="Status.Pop.buttonTextColor"
:iconUrl="Status.Pop.iconUrl" :message="Status.Pop.message" :buttonText="Status.Pop.buttonText"
@buttonClick="HidePop" :visiblePrompt="Status.Pop.visiblePrompt" :promptTitle="Status.Pop.promptTitle"
v-model="Status.Pop.modelValue" @closePop="closePop" :buttonCancelText="Status.Pop.buttonCancelText"
:showCancel="Status.Pop.showCancel" @cancelPop="closePop" :showSlot="Status.Pop.showSlot">
<view class="popup-prompt">
<input type="text" class="popup-prompt-input" placeholder="请输入名称" v-model="cEdit.fileNameExt" />
</view>
</MessagePopup>
<!-- <view class="displayNone">
<audio :src="cPlay.src" :id="cPlay.Id" :name="cPlay.name" author="">
</view> -->
<global-loading ref="loading"></global-loading>
<!-- 圆形进度条 + 全屏遮罩层升级中显示 -->
<view v-if="isUpdating" class="mask-layer">
<view class="circle-progress-box">
<progress :percent="updateProgress" activeColor="#bbe600" backgroundColor="#686767" :border-radius='22' show-info stroke-width="15" class="custom-progress" />
</view>
</view>
</view>
</template>
<script>
var eventChannel = null;
var these = null;
var ble = null;
var innerAudioContext = null;
var timeout = null;
import MescrollUni from '@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.vue'
import BleTool from '@/utils/BleHelper.js'
import MqttClient from '@/utils/mqtt.js';
import {
deviceVoliceList,
videRenameAudioFile,
deviceDeleteAudioFile,
deviceUpdateVoice,
updateBleStatus
} from '@/api/100J/HBY100-J.js'
import { baseURL } from '@/utils/request.js'
import {
showLoading,
hideLoading,
updateLoading
} from '@/utils/loading.js'
import Common from '@/utils/Common.js'
export default {
components: {
MescrollUni
},
data() {
return {
mescroll: null,
Status: {
isEdit: false,
playState: '',
playPath: '',
mqttClient: null,
Pop: {
showPop: false, //是否显示弹窗
popType: 'custom',
bgColor: '#383934bd',
borderColor: '#BBE600',
textColor: '#ffffffde',
buttonBgColor: '#BBE600',
buttonTextColor: '#232323DE',
iconUrl: '',
message: '您确定要这样做吗?',
buttonText: '确定',
clickEvt: '',
visiblePrompt: false,
promptTitle: '设备名称',
modelValue: '',
visibleClose: false,
okCallback: null,
buttonCancelText: '',
showCancel: false,
showSlot: false,
},
},
Options: [{
text: '重命名',
style: {
backgroundColor: '#E09319',
borderRadius: '16px',
width: '240rpx', // 初始宽度
},
},
{
text: '删除',
style: {
backgroundColor: 'rgb(240, 60, 60)',
borderRadius: '16px',
width: '240rpx', // 初始宽度
},
},
],
blue: {
},
device: {},
dataListA: [],
checkList: [],
downOption: {
auto: true
},
upOption: {
auto: false,
noMoreSize: 0,
offset: 50,
isLock: false,
empty: {
tip: '暂无数据',
hideScroll: false
}
},
page: 1, // 当前页码
size: 10, // 每页条数
total: 0, // 总数据量
loadedCount: 0,
loading: false,
finished: false,
mescrollHeight: 0,
cEdit: {
Id: "", //编号
fileNameExt: "", //名称
createTime: "", //创建时间
fileUrl: "", //本地地址
fileUrl: "", //网络地址
},
cPlay: {
Id: "", //编号
fileNameExt: "", //名称
createTime: "", //创建时间
fileUrl: "", //本地地址
fileUrl: "", //网络地址
},
updateProgress: 0, // 升级进度0-100
isUpdating: false, // 是否正在升级(控制进度条显示/隐藏)
mqttSubscribeSuccess: false // MQTT订阅是否成功
}
},
onLoad() {
these = this;
this.getSystemInfoSyncH();
eventChannel = this.getOpenerEventChannel();
eventChannel.on('deviceData', (rec) => {
console.log(rec, 'ressss');
this.blue = rec.ble;
this.device = rec.data;
// 同步蓝牙状态,确保语音上传走蓝牙优先
if (rec.ble && (rec.ble.isConnected || rec.ble.bleDeviceId)) {
updateBleStatus(!!rec.ble.isConnected, rec.ble.bleDeviceId || '', rec.data?.deviceId || '');
}
this.getinitData(rec.data.deviceId, true)
});
},
onBackPress() {
console.log("页面返回")
},
onUnload() {
// 页面卸载时断开MQTT连接
if (this.mqttClient) {
this.mqttClient.disconnect();
}
},
methods: {
//语音管理列表(合并云端 + 本地无网络保存的语音)
getinitData(val, isLoadMore = false) {
const deviceId = this.device.deviceId;
if (!deviceId) return;
const mergeLocal = (serverList) => {
const key = `100J_local_audio_${deviceId}`;
const cacheKey = `100J_local_path_cache_${deviceId}`;
const localList = uni.getStorageSync(key) || [];
const pathCache = uni.getStorageSync(cacheKey) || {};
const localMapped = localList.map(item => ({
...item,
fileNameExt: item.name || '本地语音',
createTime: item._createTime || item.createTime || Common.DateFormat(new Date(), "yyyy年MM月dd日"),
fileUrl: item.fileUrl || item.localPath,
useStatus: 0,
_isLocal: true
}));
const enriched = (serverList || []).map(item => {
const urlKey = item.fileUrl || item.url || item.filePath || item.audioUrl || item.ossUrl;
const localPath = pathCache[urlKey] || pathCache[item.id];
return localPath ? { ...item, localPath } : item;
});
return [...localMapped, ...enriched];
};
deviceVoliceList({ deviceId }).then((res) => {
if (res.code == 200) {
this.total = res.total;
const list = (res.data || []).map(item => ({
...item,
createTime: item.createTime || Common.DateFormat(new Date(), "yyyy年MM月dd日")
}));
this.dataListA = mergeLocal(list);
if (this.mescroll) this.mescroll.endBySize(this.dataListA.length, this.total + (this.dataListA.length - list.length));
}
}).catch(() => {
// 无网络时仅显示本地保存的语音
this.dataListA = mergeLocal([]);
this.total = this.dataListA.length;
if (this.mescroll) this.mescroll.endBySize(this.dataListA.length, this.total);
});
},
createAudioPlayer(localPath) {
if (innerAudioContext) {
innerAudioContext.close();
console.log("1111111")
}
innerAudioContext = plus.audio.createPlayer({
src: localPath,
autoplay: false,
backgroundControl: false,
});
innerAudioContext.addEventListener('canplay', () => {
console.log("准备就绪,可以播放了");
this.Status.playState = 'ready';
this.cEdit.time = innerAudioContext.getDuration();
console.log("音频长度:", this.cEdit.time);
});
//开始播放
innerAudioContext.addEventListener("play", (res) => {
this.Status.playState = 'play';
console.log("播放中", res);
});
//暂停播放
innerAudioContext.addEventListener("pause", (res) => {
this.Status.playState = 'pause';
console.log("暂停了", res);
});
//播放结束
innerAudioContext.addEventListener("ended", (res) => {
this.Status.playState = 'ready';
console.log("播放结束", res);
});
innerAudioContext.addEventListener("seeked", (res) => {
console.log("seek完成了", res);
})
innerAudioContext.addEventListener("error", (ex) => {
console.log("播放错误", ex);
});
},
gotoRecord(type) {
let id = this.device.deviceId;
uni.navigateTo({
url: '/pages/100J/audioManager/Recording?pageType=' + type + '&id=' + id,
events: {
RecordOver: function(res) {
these.downCallback();
}
},
fail(ex) {
this.showMsg("资源不存在");
}
});
},
play(item, index) {
if (item.type === 'Txt') {
this.showMsg("文字转语音暂不支持播放")
return;
}
if (!item.fileUrl) {
this.showMsg("音频加载错误无法播放");
return;
}
if (this.Status.playPath === item.fileUrl) {
console.log("当前正在播放");
if (this.Status.playState === 'play') {
console.log("暂停播放:", item)
innerAudioContext.pause();
this.Status.playState = 'pause'
return;
} else if (this.Status.playState === 'pause') {
console.log("恢复播放:", item)
innerAudioContext.play();
this.Status.playState = 'play'
return;
}
} else {
console.log("准备播放:", item)
this.Status.playPath = item.fileUrl;
this.createAudioPlayer(item.fileUrl);
innerAudioContext.play();
}
},
handleSwipeClick(e, item, index) {
console.log(e.content.text, item)
switch (e.index) {
case 0:
this.ReName(item, index);
break
case 1:
this.delCheckList(item, index);
break
};
if (this.$refs.swipeAction) {
this.$refs.swipeAction.closeAll();
}
},
ReName(item, index) {
this.cEdit = Object.assign(this.cEdit, item);
let task = () => {
this.closePop();
let data = {
fileName: this.cEdit.fileNameExt,
deviceId: this.device.deviceId,
fileId: item.fileId
}
videRenameAudioFile(data).then((res) => {
console.log('res');
if (res.code == 200) {
uni.showToast({
title: res.msg,
icon: 'none',
duration: 1000
});
this.getinitData()
this.cEdit.fileNameExt = ''
// 关闭所有滑动项
this.$refs.swipeAction.closeAll();
}
})
}
this.showPop({
showPop: true, //是否显示弹窗
popType: 'custom',
bgColor: '#383934bd',
borderColor: '#BBE60096',
textColor: '#ffffffde',
buttonBgColor: '#BBE600',
buttonTextColor: '#232323DE',
iconUrl: '',
message: '',
buttonText: '确定',
clickEvt: '',
visiblePrompt: false,
promptTitle: '',
modelValue: '',
visibleClose: true,
okCallback: task,
showSlot: true,
buttonCancelText: '取消',
showCancel: true
});
},
// 删除
delCheckList(item, index) {
if (!this.checkList.length && !item) {
console.log("无文件删除");
return;
}
let task = () => {
if (item._isLocal) {
// 本地项:从本地存储移除
const key = `100J_local_audio_${this.device.deviceId}`;
let list = uni.getStorageSync(key) || [];
list = list.filter(l => l.id !== item.id && l.Id !== item.Id);
uni.setStorageSync(key, list);
uni.showToast({ title: '已删除', icon: 'none', duration: 1000 });
this.getinitData();
this.$refs.swipeAction.closeAll();
return;
}
deviceDeleteAudioFile({ fileId: item.fileId, deviceId: this.device.deviceId }).then((res) => {
if (res.code == 200) {
uni.showToast({ title: res.msg, icon: 'none', duration: 1000 });
this.getinitData();
this.$refs.swipeAction.closeAll();
}
});
}
this.showPop({
showPop: true, //是否显示弹窗
popType: 'custom',
bgColor: '#383934bd',
borderColor: '#e034344d',
textColor: '#ffffffde',
buttonBgColor: '#E03434',
buttonTextColor: '#232323DE',
iconUrl: '',
message: '删除后无法恢复,你确定要删除吗?',
buttonText: '确定',
clickEvt: '',
visiblePrompt: false,
promptTitle: '',
modelValue: '',
visibleClose: true,
okCallback: task,
showSlot: false,
buttonCancelText: '取消',
showCancel: true
})
},
isCheck(item, index) {
let f = this.checkList.find((v, vi) => {
return v === item.Id;
});
if (f) {
return true;
}
return false;
},
checkToggle(item, index) {
let f = null;
let i = null;
this.checkList.find((v, vi) => {
if (v === item.Id) {
f = v;
i = vi;
return true;
}
return false;
});
if (i !== null) {
this.checkList.splice(i, 1);
} else {
this.checkList.push(item.Id);
}
},
Apply(item, index) {
this.updateProgress = 0;
this.isUpdating = true;
// 本地项优先用 localPath云端项用 fileUrl兼容多种字段名相对路径补全 baseURL
let fileUrl = '';
let localPath = (item.localPath && typeof item.localPath === 'string') ? item.localPath : '';
if (!item._isLocal) {
const raw = item.fileUrl || item.url || item.filePath || item.audioUrl || item.ossUrl || '';
fileUrl = (typeof raw === 'string' && raw) ? (raw.startsWith('/') ? (baseURL + raw) : raw) : '';
} else {
// 本地项localPath 优先,无则用 fileUrlmergeLocal 中可能只有 fileUrl 存路径)
if (!localPath && item.fileUrl) localPath = item.fileUrl;
}
const data = {
id: item.id,
fileUrl,
localPath,
onProgress: (p) => { this.updateProgress = p; },
onWaiting: () => { uni.showToast({ title: '等待蓝牙连接中...', icon: 'none', duration: 2000 }); }
};
// 整体超时 60 秒仅影响蓝牙上传4G HTTP 很快返回)
const overallTimer = setTimeout(() => {
if (this.isUpdating) {
uni.showToast({ title: '操作超时', icon: 'none', duration: 2000 });
this.isUpdating = false;
this.updateProgress = 0;
}
}, 60000);
deviceUpdateVoice(data).then((RES) => {
clearTimeout(overallTimer);
if (RES.code == 200) {
// 蓝牙上传:进度已由 onProgress 更新,直接完成
if (RES._channel === 'ble') {
uni.showToast({ title: '音频上传成功', icon: 'success', duration: 2000 });
this.isUpdating = false;
setTimeout(() => { uni.navigateBack(); }, 1500);
return;
}
// 4G订阅 MQTT 获取设备端进度6 秒超时
this.upgradeTimer = setTimeout(() => {
if (this.isUpdating) {
uni.showToast({ title: '音频进度同步超时', icon: 'none', duration: 2000 });
this.isUpdating = false;
this.updateProgress = 0;
}
}, 6000);
this.mqttClient = this.mqttClient || new MqttClient();
this.mqttClient.connect(() => {
const statusTopic = `status/894078/HBY100/${this.device.deviceImei}`;
this.mqttClient.subscribe(statusTopic, (payload) => {
try {
const payloadObj = typeof payload === 'string' ? JSON.parse(payload) : payload;
const progress = payloadObj.data?.progress;
if (progress !== undefined && !isNaN(progress) && progress >= 0 && progress <= 100) {
this.updateProgress = progress;
if (progress === 100) {
clearTimeout(this.upgradeTimer);
uni.showToast({ title: '音频上传成功', icon: 'success', duration: 2000 });
this.isUpdating = false;
setTimeout(() => { uni.navigateBack(); }, 1500);
}
}
} catch (e) {
clearTimeout(this.upgradeTimer);
console.error('解析MQTT payload失败', e);
}
});
});
} else {
this.isUpdating = false;
uni.showToast({ title: RES.msg || '操作失败', icon: 'none', duration: 1000 });
}
}).catch((err) => {
clearTimeout(overallTimer);
this.isUpdating = false;
uni.showToast({ title: err.message || '操作失败', icon: 'none', duration: 2000 });
});
},
closePop: function() {
this.Status.Pop.showPop = false;
if (this.Status.Pop.cancelCallback) {
this.Status.Pop.cancelCallback();
}
},
HidePop: function() {
this.Status.Pop.showPop = false;
if (this.Status.Pop.okCallback) {
this.Status.Pop.okCallback();
}
},
showPop: function(option) {
hideLoading(this);
let def = {
showPop: true, //是否显示弹窗
popType: 'custom',
bgColor: '#383934bd',
borderColor: '#BBE600',
textColor: '#ffffffde',
buttonBgColor: '#BBE600',
buttonTextColor: '#232323DE',
iconUrl: '',
message: '',
buttonText: '确定',
clickEvt: '',
visiblePrompt: false,
promptTitle: '',
modelValue: '',
visibleClose: false,
okCallback: null,
showSlot: false,
buttonCancelText: '',
showCancel: false,
}
let keys = Object.keys(def);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (key in option) {
continue;
}
this.Status.Pop[key] = def[key];
}
if (option) {
keys = Object.keys(option);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
this.Status.Pop[key] = option[key];
}
}
if (!option.borderColor) {
option.borderColor = '#BBE600';
option.buttonBgColor = '#BBE600';
}
these.Status.Pop.showPop = true;
},
showMsg(msg, isSucc) {
let icoUrl = '/static/images/6155/DeviceDetail/uploadErr.png';
let borderColor = "#e034344d";
let buttonBgColor = "#E03434";
if (isSucc) {
icoUrl = '/static/images/common/success.png';
borderColor = "#BBE600";
buttonBgColor = "#BBE600";
}
this.showPop({
message: msg,
iconUrl: icoUrl,
borderColor: borderColor,
buttonBgColor: buttonBgColor,
buttonText: '确定',
okCallback: null
});
},
mescrollInit(mescroll) {
this.mescroll = mescroll;
},
// 下拉刷新
downCallback() {
// 重置分页参数
this.page = 1;
this.getinitData(false);
},
// 上拉加载
upCallback() {
this.page += 1;
this.getinitData(true);
},
getSystemInfoSyncH() {
let sysInfo = uni.getSystemInfoSync();
// 底部 footer 高度约 220rpx + 120rpx = 340rpx转换为 px1rpx = sysInfo.pixelRatio / 750 * 屏幕宽度?不,直接用 rpx 转 px 公式)
const footerHeight = 500 * (sysInfo.screenWidth / 750); // rpx 转 px
console.log("footerHeight=", footerHeight);
this.mescrollHeight = sysInfo.screenHeight - footerHeight;
console.log("mescrollHeight=", this.mescrollHeight);
},
handleFile(item, index) {}
}
}
</script>
<style lang="less">
.custom-progress{
border-radius: 10rpx;
}
.custom-progress .uni-progress-bar{
border-radius: 10rpx !important;
}
.custom-progress .uni-progress-info{
color: #BBE600;
font-size: 40rpx;
font-weight: 600;
}
.popup-prompt {
width: 100%;
margin-top: 40rpx;
}
.popup-prompt-input {
width: 100%;
height: 80rpx;
line-height: 80rpx;
border: 1rpx solid #BBE60096;
border-radius: 10rpx;
padding: 0 20rpx;
margin-bottom: 30rpx;
font-size: 26rpx;
box-sizing: border-box;
text-align: left;
}
.li {
border-radius: 16rpx;
background: #1a1a1a;
margin-top: 24rpx;
width: 100%;
box-sizing: border-box;
padding-left: 15rpx;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: space-between;
align-items: center;
}
.li .item {
width: 100%;
height: 200rpx;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: space-between;
align-items: center;
}
.li .itemMain {
width: 100%;
}
.li .itemMain.Edit {
width: calc(100% - 35rpx);
margin-left: 15rpx;
}
.item .itemLeft {
width: calc(100% - 120rpx);
}
.item .itemRight {
width: 120rpx;
}
.li .btn {
width: 100rpx;
height: 50rpx;
border-radius: 30rpx;
text-align: center;
line-height: 50rpx;
font-family: PingFang SC;
font-size: 24rpx;
font-weight: 400;
}
.li .btn.btn-default {
background-color: #AED600;
color: #000000;
}
.li .btn.active {
background: #3a3a3a;
color: #ffffff99;
}
.item .itemLeft .title {
color: rgba(255, 255, 255, 0.87);
font-family: PingFang SC;
font-size: 32rpx;
font-weight: 400;
letter-spacing: 0.14rpx;
text-align: left;
}
.item .itemLeft .smallTitle {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
align-items: center;
justify-content: space-between;
width: calc(100% - 50rpx);
margin-top: 5rpx;
color: rgba(255, 255, 255, 0.6);
font-family: PingFang SC;
font-size: 24rpx;
font-weight: 400;
letter-spacing: 0.14px;
}
.item .itemLeft .img {
width: 30rpx;
height: 30rpx;
}
.itemIcon {
width: 40rpx;
height: 40rpx;
box-sizing: border-box;
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 8rpx;
color: #00000000;
}
.itemIcon .img {
width: 28rpx;
height: 20rpx;
}
.itemIcon.check {
background-color: #BBE600;
color: #000000;
}
.footer {
width: 100%;
min-height: 220rpx;
height: auto;
position: fixed;
z-index: 99;
bottom: 0rpx;
left: 0rpx;
box-sizing: border-box;
border-radius: 16rpx 16rpx 0px 0px;
/* background: #000000; */
padding-bottom: 120rpx;
}
.footer .addContent {
height: 100%;
margin-top: 20rpx;
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: space-evenly;
align-items: center;
}
.footer .addContent .addItem {
width: 140rpx;
height: 180rpx;
display: flex;
flex-wrap: wrap;
align-content: space-between;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.addItem .imgContent {
width: 120rpx;
height: 120rpx;
border-radius: 8px;
background: rgba(26, 26, 26, 1);
}
.addItem .imgContent .img {
width: 60rpx;
height: 52rpx;
}
.addItem .txt {
color: rgba(255, 255, 255, 0.87);
font-family: PingFang SC;
font-size: 26rpx;
font-weight: 400;
line-height: 40rpx;
letter-spacing: 0.14px;
text-align: center;
}
.footer .btn-del {
width: 100%;
height: 100rpx;
line-height: 100rpx;
border-radius: 180rpx;
background: rgba(224, 52, 52, 1);
color: rgba(255, 255, 255, 0.87);
font-family: PingFang SC;
font-size: 32rpx;
font-weight: 400;
letter-spacing: 12rpx;
text-align: center;
}
.editContent {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-content: center;
align-items: flex-start;
width: 100%;
height: 100%;
justify-content: flex-end;
}
/* 全屏遮罩层:半透明黑色,覆盖整个页面,禁止底层滚动/点击 */
.mask-layer {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
/* 半透明黑,可调整透明度 */
z-index: 9999;
/* 遮罩层层级拉满,确保在最上层 */
display: flex;
justify-content: center;
align-items: center;
/* 让内部圆形进度条垂直+水平居中 */
}
/* 圆形进度条容器 */
.circle-progress-box {
position: relative;
width: 70%;
height: 100rpx;
}
</style>