Files
APP/pages/100J/audioManager/AudioList.vue
2026-04-01 15:05:18 +08:00

1018 lines
28 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': isVoiceInUse(item), 'btn-default': !isVoiceInUse(item) }">
{{ isVoiceInUse(item) ? '使用中' : '使用' }}
</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,
sync100JBleProtocolFromHelper,
remove100JVoiceBleCache
} 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() {
this.clearVoiceApplyTimers();
if (this.mqttClient) {
this.mqttClient.disconnect();
}
},
methods: {
/** 清除「使用」语音相关的全部定时器,避免返回上一页后仍触发 toast / 二次 navigateBack */
clearVoiceApplyTimers() {
if (this._applyOverallTimer) {
clearTimeout(this._applyOverallTimer);
this._applyOverallTimer = null;
}
if (this.upgradeTimer) {
clearTimeout(this.upgradeTimer);
this.upgradeTimer = null;
}
if (this._applyNavigateTimer) {
clearTimeout(this._applyNavigateTimer);
this._applyNavigateTimer = null;
}
},
scheduleNavigateBackAfterVoice(delayMs = 1500) {
if (this._applyNavigateTimer) {
clearTimeout(this._applyNavigateTimer);
this._applyNavigateTimer = null;
}
this._applyNavigateTimer = setTimeout(() => {
this._applyNavigateTimer = null;
uni.navigateBack();
}, delayMs);
},
// 语音列表仅展示接口数据;上传落库后以服务端 URL 为准,不再写/读本地 path 与 BLE 字节缓存。
getinitData(val, isLoadMore = false) {
const deviceId = this.device.deviceId;
if (!deviceId) return;
try {
uni.removeStorageSync(`100J_local_path_cache_${deviceId}`);
uni.removeStorageSync(`100J_local_audio_${deviceId}`);
} catch (e) {}
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日"),
useStatus: Number(item.useStatus) === 1 ? 1 : 0
}));
this.dataListA = list;
if (this.mescroll) this.mescroll.endBySize(this.dataListA.length, this.total);
}
}).catch(() => {
this.dataListA = [];
this.total = 0;
if (this.mescroll) this.mescroll.endBySize(0, 0);
});
},
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,
id: item.id
}
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 = () => {
const devId = this.device.deviceId;
const vid = (item.id != null && item.id !== '') ? item.id : item.id;
deviceDeleteAudioFile({ id: item.id, deviceId: devId }).then((res) => {
if (res.code == 200) {
if (devId && vid != null && vid !== '') remove100JVoiceBleCache(devId, vid);
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);
}
},
/** 与后端约定:仅 1 为使用中(避免字符串 "0" 在 class 里仍为 truthy */
isVoiceInUse(item) {
return Number(item && item.useStatus) === 1;
},
/** 切换「使用」后同步整表:仅当前项为 1其余为 0避免第一项永远显示使用中 */
syncVoiceListUseStatus(activeItem) {
const pickId = (o) => {
if (!o) return '';
const v = o.id ?? o.fileId ?? o.Id;
return v != null && v !== '' ? String(v) : '';
};
const aid = pickId(activeItem);
if (!aid) return;
this.dataListA.forEach((row, i) => {
const rid = pickId(row);
const use = rid === aid ? 1 : 0;
if (Number(row.useStatus) !== use) {
this.$set(this.dataListA, i, { ...row, useStatus: use });
}
});
},
Apply(item, index) {
this.updateProgress = 0;
this.clearVoiceApplyTimers();
const raw = item.fileUrl || item.url || item.filePath || item.audioUrl || item.ossUrl || '';
const fileUrl = (typeof raw === 'string' && raw)
? (raw.startsWith('/') ? (baseURL + raw) : raw)
: '';
const data = {
id: item.id,
fileUrl,
localPath: '',
onProgress: (p) => {
const n = Math.min(100, Math.max(0, Math.round(Number(p) || 0)));
const cur = Number(this.updateProgress) || 0;
this.updateProgress = Math.max(cur, n);
},
// 不传「蓝牙连接中」类提示:关蓝牙走 4G 时易误导;进度条 + 必要时全局请稍候即可
onWaiting: () => {}
};
const runDeviceUpdate = () => {
// 大文件蓝牙分片耗时可远超 2 分钟,整体超时放宽到 10 分钟(挂到实例上,便于 onUnload / 成功时清除)
const OVERALL_MS = 600000;
if (this._applyOverallTimer) {
clearTimeout(this._applyOverallTimer);
this._applyOverallTimer = null;
}
this._applyOverallTimer = setTimeout(() => {
this._applyOverallTimer = null;
if (this.isUpdating) {
uni.showToast({ title: '操作时间过长已中断,请重试或检查蓝牙连接', icon: 'none', duration: 2500 });
this.isUpdating = false;
this.updateProgress = 0;
}
}, OVERALL_MS);
// 进入列表时的蓝牙快照可能过期;与 HBY100 详情页一致,从 BleHelper 按 MAC 再对齐一次
sync100JBleProtocolFromHelper(this.device).then(() => deviceUpdateVoice(data)).then((RES) => {
if (this._applyOverallTimer) {
clearTimeout(this._applyOverallTimer);
this._applyOverallTimer = null;
}
if (RES.code == 200) {
// 蓝牙上传:进度已由 onProgress 更新,直接完成
if (RES._channel === 'ble') {
if (this.upgradeTimer) {
clearTimeout(this.upgradeTimer);
this.upgradeTimer = null;
}
const title = RES._updateVoiceAfterBleFailed
? '蓝牙已下发,云端同步失败可稍后重试'
: '音频上传成功';
this.syncVoiceListUseStatus(item);
uni.showToast({ title, icon: RES._updateVoiceAfterBleFailed ? 'none' : 'success', duration: 2000 });
this.isUpdating = false;
this.scheduleNavigateBackAfterVoice(1500);
return;
}
// 4GMQTT 进度可能数十秒才上报,用「自上次进度起」滑动超时,避免误报
const MQTT_IDLE_MS = 120000;
const armMqttIdle = () => {
if (this.upgradeTimer) clearTimeout(this.upgradeTimer);
this.upgradeTimer = setTimeout(() => {
if (!this.isUpdating) return;
uni.showToast({
title: '长时间未收到设备进度,若语音已生效可返回查看',
icon: 'none',
duration: 3500
});
this.isUpdating = false;
this.updateProgress = 0;
}, MQTT_IDLE_MS);
};
armMqttIdle();
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 != null && payloadObj.data.progress !== undefined
? payloadObj.data.progress
: payloadObj.progress;
if (progress !== undefined && !isNaN(progress) && progress >= 0 && progress <= 100) {
armMqttIdle();
const cur = Number(this.updateProgress) || 0;
this.updateProgress = Math.max(cur, Math.round(progress));
if (Number(progress) === 100) {
if (this.upgradeTimer) clearTimeout(this.upgradeTimer);
this.upgradeTimer = null;
this.syncVoiceListUseStatus(item);
uni.showToast({ title: '音频上传成功', icon: 'success', duration: 2000 });
this.isUpdating = false;
this.scheduleNavigateBackAfterVoice(1500);
}
}
} catch (e) {
console.error('解析MQTT payload失败', e);
armMqttIdle();
}
});
});
} else {
this.isUpdating = false;
uni.showToast({ title: RES.msg || '操作失败', icon: 'none', duration: 1000 });
}
}).catch((err) => {
if (this._applyOverallTimer) {
clearTimeout(this._applyOverallTimer);
this._applyOverallTimer = null;
}
this.isUpdating = false;
this.updateProgress = 0;
uni.showToast({ title: err.message || '操作失败', icon: 'none', duration: 2500 });
});
};
this.isUpdating = true;
runDeviceUpdate();
},
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>