Files
device-voice-h5/pages/index/index.vue
sexygoat 83c32279eb
All checks were successful
构建并部署前端到生产环境 / build-and-deploy (push) Successful in 54s
fix: 修改套餐位置
2026-05-28 14:21:24 +08:00

801 lines
21 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="container">
<UserInfoCard :currentCardNo="currentCardNo" :deviceInfo="deviceInfo" :onlineStatus="onlineStatus"
:isDevice="userInfo.isDevice" :networkStatus="deviceInfo.network_status" />
<DeviceStatusCard v-if="userInfo.isDevice" :deviceInfo="deviceInfo" :isRealName="isRealName"
:isDevice="userInfo.isDevice" @authentication="enterDetail('authentication')" />
<!-- <VoiceCard v-if="!userInfo.isDevice" :voiceStats="voiceStats" :dateRangeText="dateRangeText"
@openDatePicker="openDateRangePicker" /> -->
<TrafficCard :identifier="currentCardNo" />
<!-- <WhitelistCard v-if="!userInfo.isDevice" :whitelistData="whitelistData" @refresh="refreshWhitelist"
@add="showAddWhitelistDialog" @showSms="showSmsCodeDialog" /> -->
<WifiCard v-if="userInfo.isDevice" :deviceInfo="deviceInfo" @modify="modifyWifi" @copy="copy" />
<FunctionCard :realNameStatus="realNameStatus" :alreadyBindPhone="alreadyBindPhone"
:isDevice="userInfo.isDevice" :walletBalance="deviceInfo.walletBalance" @enter="enterDetail" @sync="onSync">
<!-- 修改WIFI弹窗 -->
<up-popup :show="showModifyWifi" mode="center" @close="showModifyWifi=false">
<view class="wifi-popup">
<view class="title mb-md">修改WIFI</view>
<view class="flex-col-g20">
<view class="flex-col-g8">
<label class="caption">名称:</label>
<up-input placeholder="WIFI名称" class="wifi-input" v-model="wifi_info.ssid" />
</view>
<view class="flex-col-g8">
<label class="caption">密码:</label>
<up-input placeholder="WIFI密码" class="wifi-input" v-model="wifi_info.pwd" />
</view>
</view>
<view class="btn-group mt-20">
<button class="btn-apple btn-secondary" @tap="showModifyWifi=false">取消</button>
<button class="btn-apple btn-primary" @tap="confirmModify">确定</button>
</view>
</view>
</up-popup>
<!-- 重启设备 -->
<up-modal title="您确定要重启设备吗?" :show="restartShow" showCancelButton @confirm="YesRestart"
@cancel="restartShow=false" />
<!-- 恢复出厂设置 -->
<up-modal title="您确定要恢复出厂设置吗?" :show="recoverShow" showCancelButton @confirm="YesRecover"
@cancel="recoverShow=false" />
<!-- 退出登录 -->
<up-modal title="您确定要退出登录吗?" :show="logoutShow" showCancelButton @confirm="YesLogout"
@cancel="logoutShow=false" />
<!-- 添加白名单弹窗 -->
<up-popup :show="showAddWhitelist" mode="center" @close="showAddWhitelist=false">
<view class="wifi-popup">
<view class="title mb-md">添加白名单</view>
<view class="flex-col-g20">
<view class="flex-col-g8">
<label class="caption">姓名:</label>
<up-input placeholder="请输入姓名" border="surround" v-model="whitelistForm.name" />
</view>
<view class="flex-col-g8">
<label class="caption">手机号:</label>
<up-input placeholder="请输入手机号" border="surround" v-model="whitelistForm.phone" />
</view>
</view>
<view class="btn-group mt-20">
<button class="btn-apple btn-secondary" @tap="showAddWhitelist=false">取消</button>
<button class="btn-apple btn-primary" @tap="confirmAddWhitelist">确定</button>
</view>
</view>
</up-popup>
<!-- 上传短信码弹窗 -->
<up-popup :show="showSmsCode" mode="center" @close="showSmsCode=false">
<view class="wifi-popup">
<view class="title mb-md">上传短信验证码</view>
<view class="flex-col-g20">
<view class="flex-col-g8">
<label class="caption">手机号: {{smsCodePhone}}</label>
</view>
<view class="flex-col-g8">
<label class="caption">验证码:</label>
<up-input placeholder="请输入短信验证码" border="surround" v-model="smsCode" />
</view>
</view>
<view class="btn-group mt-20">
<button class="btn-apple btn-secondary" @tap="showSmsCode=false">取消</button>
<button class="btn-apple btn-primary" @tap="confirmUploadSmsCode">确定</button>
</view>
</view>
</up-popup>
</FunctionCard>
<view class="bottom-spacer"></view>
<!-- 日期选择器弹窗 -->
<up-datetime-picker v-if="!userInfo.isDevice" :show="showStartDatePicker" v-model="startDateTimestamp"
mode="date" @confirm="onStartDateConfirm" @cancel="showStartDatePicker=false"
@close="showStartDatePicker=false"></up-datetime-picker>
<up-datetime-picker v-if="!userInfo.isDevice" :show="showEndDatePicker" v-model="endDateTimestamp" mode="date"
@confirm="onEndDateConfirm" @cancel="showEndDatePicker=false"
@close="showEndDatePicker=false"></up-datetime-picker>
<FloatingButton @tap="connectKF" />
</view>
</template>
<script setup>
import {
ref,
reactive,
onMounted,
computed
} from 'vue';
import { onShow } from '@dcloudio/uni-app';
import UserInfoCard from '@/components/UserInfoCard.vue';
import DeviceStatusCard from '@/components/DeviceStatusCard.vue';
import VoiceCard from '@/components/VoiceCard.vue';
import TrafficCard from '@/components/TrafficCard.vue';
import WhitelistCard from '@/components/WhitelistCard.vue';
import WifiCard from '@/components/WifiCard.vue';
import FunctionCard from '@/components/FunctionCard.vue';
import FloatingButton from '@/components/FloatingButton.vue';
import {
assetApi,
deviceApi
} from '@/api/index.js';
import {
useUserStore
} from '@/store/index.js';
const userStore = useUserStore();
let showModifyWifi = ref(false);
let restartShow = ref(false);
let recoverShow = ref(false);
let logoutShow = ref(false);
let loading = ref(false);
let userInfo = reactive({
isDevice: true,
avatar: ''
});
let deviceInfo = reactive({
battery: 0,
connect: 0,
network_status: 1,
status: 1,
packageName: '-',
expireDate: '-',
walletBalance: 0,
iccid: '-',
currentIccid: '-',
category: '-',
phone: '-',
flowSize: 0,
totalBytesCnt: 0,
ssidName: '-',
ssidPwd: '-',
signal_quality: '暂无数据',
signal_bad_reason: '',
onlineStatus: '0',
connCnt: 0,
run_time: 0,
last_online_time: '-',
lan_ip: '-',
kf_url: '-',
mchList: [],
imei: '-',
max_clients: 1,
asset_type: 'device',
bound_phone: ''
});
let isRealName = ref(false);
let alreadyBindPhone = ref(false);
let boundPhone = ref('');
let realNameStatus = ref('');
let wifi_info = reactive({
ssid: '',
pwd: ''
});
let currentCardNo = ref('');
let accountName = ref('');
let voiceCardStatus = reactive({
cardStatus: '',
online: '离线'
});
let voiceStats = reactive({
totalVoice: 0,
usedVoice: 0,
remainingVoice: 0
});
let showStartDatePicker = ref(false);
let showEndDatePicker = ref(false);
let startDate = ref('');
let endDate = ref('');
let startDateTimestamp = ref(Date.now());
let endDateTimestamp = ref(Date.now());
let dateRangeText = ref('');
let whitelistData = reactive([]);
let showAddWhitelist = ref(false);
let whitelistForm = reactive({
name: '',
phone: ''
});
let showSmsCode = ref(false);
let smsCodePhone = ref('');
let smsCode = ref('');
let indexEntryChecking = ref(false);
const formatDate = (dateStr) => {
if (!dateStr) return '-';
return dateStr.split('T')[0] || '-';
};
const resolveCurrentCard = (cards = []) => {
if (cards.length === 1) return cards[0];
if (cards.length > 1) return cards.find(card => card.is_current) || null;
return null;
};
const loadAssetInfo = async () => {
const identifier = userStore.state.identifier;
if (!identifier) return;
loading.value = true;
try {
const data = await assetApi.getInfo(identifier);
// 基本信息
userInfo.isDevice = data.asset_type === 'device';
currentCardNo.value = data.identifier || '-';
deviceInfo.network_status = data.network_status;
deviceInfo.status = data.status;
deviceInfo.imei = data.imei || '-';
deviceInfo.asset_type = data.asset_type || 'device';
deviceInfo.bound_phone = data.bound_phone || '';
deviceInfo.packageName = data.current_package || '-';
deviceInfo.expireDate = formatDate(data.current_package_expires_at);
deviceInfo.iccid = data.iccid || '-';
deviceInfo.walletBalance = data.wallet_balance ?? 0;
const currentCard = resolveCurrentCard(data.cards || []);
isRealName.value = !!currentCard?.real_name_at || currentCard?.real_name_status === 1 || data.real_name_status === 1;
realNameStatus.value = isRealName.value ? '已实名' : '未实名';
boundPhone.value = data.bound_phone || '';
alreadyBindPhone.value = !!data.bound_phone;
// 流量信息已由 TrafficCard 组件独立获取,这里不再处理
// 当前卡信息
if (data.cards && data.cards.length > 0) {
deviceInfo.currentIccid = currentCard?.iccid || '-';
// 卡列表
deviceInfo.mchList = data.cards.map(card => ({
iccidMark: card.iccid || '-',
category: card.slot_position,
is_current: card.is_current
}));
} else {
deviceInfo.currentIccid = '-';
deviceInfo.mchList = [];
}
// 运营商信息
const carrierTypeMap = {
CMCC: '中国移动',
CUCC: '中国联通',
CTCC: '中国电信',
CBN: '中国广电'
};
deviceInfo.carrier_name = (currentCard && carrierTypeMap[currentCard.carrier_type]) || '-';
// 设备实时信息
if (data.device_realtime) {
const rt = data.device_realtime;
deviceInfo.onlineStatus = rt.online_status === 1 ? '1' : '0';
deviceInfo.battery = rt.battery_level ?? 0;
deviceInfo.ssidName = rt.ssid || '-';
deviceInfo.ssidPwd = rt.wifi_password || '-';
deviceInfo.lan_ip = rt.lan_ip || '-';
deviceInfo.connCnt = rt.client_number ?? 0;
deviceInfo.max_clients = rt.max_clients ?? 1;
deviceInfo.run_time = rt.run_time || 0;
deviceInfo.signal_quality = rt.signal_quality || '暂无数据';
deviceInfo.signal_bad_reason = rt.signal_bad_reason || '';
} else {
// 没有实时信息时设置默认值
deviceInfo.onlineStatus = '0';
deviceInfo.battery = 0;
deviceInfo.ssidName = '-';
deviceInfo.ssidPwd = '-';
deviceInfo.lan_ip = '-';
deviceInfo.connCnt = 0;
deviceInfo.max_clients = 1;
deviceInfo.run_time = 0;
deviceInfo.signal_quality = '暂无数据';
deviceInfo.signal_bad_reason = '';
}
// 到期时间由 TrafficCard 组件获取套餐信息时一并返回
return data;
} catch (e) {
console.error('加载资产信息失败', e);
return null;
} finally {
loading.value = false;
}
};
const handleIndexEntry = async () => {
const token = uni.getStorageSync('token');
if (!token) {
uni.reLaunch({ url: '/pages/login/login' });
return;
}
if (indexEntryChecking.value) return;
indexEntryChecking.value = true;
try {
const data = await loadAssetInfo();
if (data && !data.bound_phone) {
uni.redirectTo({ url: '/pages/bind/bind?fromLogin=true' });
}
} finally {
indexEntryChecking.value = false;
}
};
// 处理套餐加载完成事件(从 TrafficCard 组件传递过来)
const handlePackageLoaded = (activePackage) => {
if (activePackage && activePackage.expires_at) {
// 格式化到期时间
const date = new Date(activePackage.expires_at);
deviceInfo.expireDate =
`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
} else {
deviceInfo.expireDate = '-';
}
};
const modifyWifi = () => {
wifi_info.ssid = deviceInfo.ssidName;
wifi_info.pwd = deviceInfo.ssidPwd;
showModifyWifi.value = true;
};
const confirmModify = async () => {
try {
await deviceApi.setWifi(
userStore.state.identifier,
wifi_info.ssid,
wifi_info.pwd,
true
);
deviceInfo.ssidName = wifi_info.ssid;
deviceInfo.ssidPwd = wifi_info.pwd;
uni.showToast({
title: '修改成功',
icon: 'success'
});
} catch (e) {
console.error('修改WiFi失败', e);
}
showModifyWifi.value = false;
};
const connectKF = () => {
const kfUrl =
'https://work.weixin.qq.com/kfid/kfc0fd79f27686fb65e?enc_scene=ENCfR9AVui6UfvvxySGVuvaG&scene_param=commonlink';
// #ifdef H5
window.location.href = kfUrl;
// #endif
// #ifdef MP-WEIXIN
// 微信小程序跳转到企业微信客服
wx.openCustomerServiceChat({
extInfo: {
url: kfUrl
},
corpId: 'ww4e3b7f9f7c8a9b0c',
success: () => {
console.log('打开客服会话成功');
},
fail: (err) => {
console.error('打开客服会话失败', err);
// 失败时使用网页跳转
uni.navigateTo({
url: `/pages/webview/webview?url=${encodeURIComponent(kfUrl)}`
});
}
});
// #endif
// #ifndef H5 || MP-WEIXIN
// 其他平台,复制链接
uni.showModal({
title: '联系客服',
content: '是否复制客服链接?',
confirmText: '复制',
success: (res) => {
if (res.confirm) {
uni.setClipboardData({
data: kfUrl,
success: () => {
uni.showToast({
title: '客服链接已复制',
icon: 'success'
});
}
});
}
}
});
// #endif
};
const enterBack = async () => {
if (!deviceInfo.lan_ip || deviceInfo.lan_ip === '-') {
uni.showModal({
title: '提示',
content: '请将设备开机后, 等待2分钟左右进行连接WiFi',
showCancel: false,
buttonText: '我知道了'
});
return;
}
await new Promise((resolve) => {
uni.showModal({
title: '提示',
content: '请先连接当前设备WiFi否则无法访问后台管理',
cancelText: '取消',
confirmText: '我知道了',
success: (res) => res.confirm && resolve()
});
});
// 跳转到设备后台管理地址
const url = deviceInfo.lan_ip.startsWith('http') ? deviceInfo.lan_ip : `http://${deviceInfo.lan_ip}`;
// #ifdef H5
window.location.href = url;
// #endif
// #ifndef H5
uni.showModal({
title: '后台地址',
content: url,
confirmText: '复制',
success: (res) => {
if (res.confirm) {
uni.setClipboardData({
data: url,
success: () => {
uni.showToast({
title: '地址已复制',
icon: 'success'
});
}
});
}
}
});
// #endif
};
const YesRestart = async () => {
try {
await deviceApi.reboot(userStore.state.identifier);
uni.showToast({
title: '重启指令已发送',
icon: 'success'
});
} catch (e) {
console.error('重启设备失败', e);
}
restartShow.value = false;
};
const copy = (content) => {
uni.setClipboardData({
data: content,
success: () => uni.showToast({
title: '复制成功',
icon: 'none'
})
});
};
const YesRecover = async () => {
try {
await deviceApi.factoryReset(userStore.state.identifier);
uni.showToast({
title: '恢复出厂设置指令已发送',
icon: 'success'
});
} catch (e) {
console.error('恢复出厂设置失败', e);
}
recoverShow.value = false;
};
const YesLogout = () => {
uni.clearStorageSync();
uni.reLaunch({
url: '/pages/login/login'
});
logoutShow.value = false;
};
const onlineStatus = computed(() => {
if (!userInfo.isDevice) return voiceCardStatus.online || '离线';
return deviceInfo.onlineStatus === '1' ? '在线' : '离线';
});
const initCurrentMonth = () => {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
startDate.value = `${year}-${month}-01`;
const lastDay = new Date(year, parseInt(month), 0).getDate();
endDate.value = `${year}-${month}-${String(lastDay).padStart(2, '0')}`;
updateDateRangeText();
};
const updateDateRangeText = () => {
if (startDate.value && endDate.value) {
const [sYear, sMonth, sDay] = startDate.value.split('-');
const [eYear, eMonth, eDay] = endDate.value.split('-');
dateRangeText.value = `${sYear}-${sMonth}-${sDay}${eYear}-${eMonth}-${eDay}`;
}
};
const openDateRangePicker = () => {
if (startDate.value) startDateTimestamp.value = new Date(startDate.value).getTime();
showStartDatePicker.value = true;
};
const onStartDateConfirm = (e) => {
const date = new Date(e.value);
startDate.value =
`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
showStartDatePicker.value = false;
setTimeout(() => {
if (endDate.value) endDateTimestamp.value = new Date(endDate.value).getTime();
else endDateTimestamp.value = e.value;
showEndDatePicker.value = true;
}, 300);
};
const onEndDateConfirm = (e) => {
const date = new Date(e.value);
endDate.value =
`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
showEndDatePicker.value = false;
if (new Date(startDate.value) > new Date(endDate.value)) {
uni.showToast({
title: '开始日期不能大于结束日期',
icon: 'none'
});
return;
}
updateDateRangeText();
};
const refreshWhitelist = () => uni.showToast({
title: '刷新成功',
icon: 'success'
});
const showAddWhitelistDialog = () => {
whitelistForm.name = '';
whitelistForm.phone = '';
showAddWhitelist.value = true;
};
const confirmAddWhitelist = () => {
if (!whitelistForm.name || !whitelistForm.phone) {
uni.showToast({
title: '请输入姓名和手机号',
icon: 'none'
});
return;
}
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(whitelistForm.phone)) {
uni.showToast({
title: '请输入正确的手机号',
icon: 'none'
});
return;
}
whitelistData.push({
name: whitelistForm.name,
phone: whitelistForm.phone,
state: '1',
createTime: new Date().toISOString().split('T')[0]
});
uni.showToast({
title: '添加成功',
icon: 'none'
});
showAddWhitelist.value = false;
};
const showSmsCodeDialog = (phone, state) => {
if (state !== '5' && state !== '3') {
uni.showToast({
title: '当前状态无需上传短信验证码',
icon: 'none'
});
return;
}
smsCodePhone.value = phone;
smsCode.value = '';
showSmsCode.value = true;
};
const confirmUploadSmsCode = () => {
if (!smsCode.value) {
uni.showToast({
title: '请输入短信验证码',
icon: 'none'
});
return;
}
uni.showToast({
title: '操作成功',
icon: 'success'
});
showSmsCode.value = false;
};
const onSync = async () => {
const identifier = userStore.state.identifier;
if (!identifier) {
uni.showToast({
title: '请先登录',
icon: 'none'
});
return;
}
uni.showModal({
title: '确认同步',
content: '确定要同步运营商数据吗?',
success: (res) => {
if (!res.confirm) return;
uni.showLoading({
title: '同步中...',
mask: true
});
assetApi.refresh(identifier).then((data) => {
uni.hideLoading();
if (data.accepted) {
const refreshTypeText = data.refresh_type === 'device' ? '设备' : '卡';
uni.showToast({
title: `${refreshTypeText}数据同步成功`,
icon: 'success',
duration: 2000
});
setTimeout(() => {
loadAssetInfo();
}, 2000);
} else {
uni.showToast({
title: '同步失败,请稍后重试',
icon: 'none'
});
}
}).catch((e) => {
uni.hideLoading();
console.error('同步数据失败', e);
if (e.msg && e.msg.includes('冷却')) {
uni.showToast({
title: e.msg,
icon: 'none',
duration: 2000
});
} else {
uni.showToast({
title: '同步失败',
icon: 'none'
});
}
});
}
});
};
const enterDetail = (name) => {
switch (name) {
case 'package-order':
uni.navigateTo({
url: '/pages/package-order/package-order'
});
break;
case 'order-list':
uni.navigateTo({
url: '/pages/order-list/order-list'
});
break;
case 'back':
enterBack();
break;
case 'switch':
uni.navigateTo({
url: '/pages/switch/switch'
});
break;
case 'asset-package':
uni.navigateTo({
url: '/pages/asset-package-history/asset-package-history'
});
break;
case 'bind':
uni.navigateTo({
url: '/pages/bind/bind'
});
break;
case 'change-phone':
if (!boundPhone.value) {
uni.showToast({
title: '请先去绑定手机号',
icon: 'none',
duration: 2000
});
return;
}
uni.navigateTo({
url: `/pages/change-phone/change-phone?oldPhone=${boundPhone.value}`
});
break;
case 'device-exchange':
uni.navigateTo({
url: '/pages/device-exchange/device-exchange'
});
break;
case 'wallet':
uni.navigateTo({
url: '/pages/my-wallet/my-wallet'
});
break;
case 'authentication':
uni.navigateTo({
url: '/pages/auth/auth'
});
break;
case 'recover':
restartShow.value = true;
break;
case 'restart':
restartShow.value = true;
break;
case 'out':
logoutShow.value = true;
break;
}
};
onMounted(() => {
initCurrentMonth();
});
onShow(() => {
handleIndexEntry();
});
</script>
<style scoped lang="scss">
.wifi-popup {
width: 600rpx;
padding: 30rpx;
.title {
font-size: 32rpx;
font-weight: 600;
color: var(--text-primary);
}
.wifi-input {
border: 2rpx solid var(--border-light);
border-radius: 8rpx;
padding: 8rpx 16rpx;
}
}
.bottom-spacer {
height: 120rpx;
}
</style>