|
|
<template>
|
|
|
<div class="control-view">
|
|
|
<div class="main-content">
|
|
|
<!-- 顶部状态栏 -->
|
|
|
<div class="header-status">
|
|
|
<div class="time-info">
|
|
|
<span>{{ currentTime }} {{ week }}</span>
|
|
|
</div>
|
|
|
<div class="page-title">
|
|
|
<h1>系统设置</h1>
|
|
|
</div>
|
|
|
<div class="system-status"></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 主体区域 -->
|
|
|
<div class="control-content">
|
|
|
<!-- 系统时间卡片 -->
|
|
|
<div class="main_item">
|
|
|
<div class="main_title">系统时间</div>
|
|
|
<div class="main_content">
|
|
|
<!-- 当前设备时间 -->
|
|
|
<div class="line">
|
|
|
<div class="item">
|
|
|
<div class="key">当前设备时间</div>
|
|
|
<div class="value">{{ deviceDatetime || '—' }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 设置新时间 -->
|
|
|
<div class="line">
|
|
|
<div class="item">
|
|
|
<div class="key">设置时间</div>
|
|
|
<el-date-picker v-model="targetDatetime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择目标时间" style="flex: 1;" />
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="buttonBox">
|
|
|
<div class="button refresh" @click="getSystemTime">
|
|
|
<el-icon>
|
|
|
<Refresh />
|
|
|
</el-icon>
|
|
|
刷新时间
|
|
|
</div>
|
|
|
<div class="button save" @click="setSystemTime">
|
|
|
<el-icon>
|
|
|
<Check />
|
|
|
</el-icon>
|
|
|
设置时间
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 上次设置结果 -->
|
|
|
<div v-if="lastSetResult" class="result-box">
|
|
|
<div class="result-item">
|
|
|
<span class="result-key">修改前:</span>
|
|
|
<span class="result-val">{{ lastSetResult.old_time }}</span>
|
|
|
</div>
|
|
|
<div class="result-item">
|
|
|
<span class="result-key">修改后:</span>
|
|
|
<span class="result-val">{{ lastSetResult.new_time }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 电表型号卡片 -->
|
|
|
<div class="main_item">
|
|
|
<div class="main_title">电表型号</div>
|
|
|
<div class="main_content">
|
|
|
<div class="line">
|
|
|
<div class="item full-width">
|
|
|
<div class="key">当前型号</div>
|
|
|
<div class="value">{{ currentMeterFirm || '—' }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="line">
|
|
|
<div class="item full-width">
|
|
|
<div class="key">选择型号</div>
|
|
|
<el-select v-model="selectedMeterFirm" placeholder="请选择电表型号" style="flex: 1;">
|
|
|
<el-option label="ADL400" value="ADL400" />
|
|
|
<el-option label="ADL200" value="ADL200" />
|
|
|
</el-select>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="buttonBox">
|
|
|
<div class="button save" @click="setMeterFirm">
|
|
|
<el-icon>
|
|
|
<Check />
|
|
|
</el-icon>
|
|
|
确认修改
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 修改密码卡片 -->
|
|
|
<div class="main_item">
|
|
|
<div class="main_title">修改操作密码</div>
|
|
|
<div class="main_content">
|
|
|
<div class="line">
|
|
|
<div class="item full-width">
|
|
|
<div class="key">启用密码保护</div>
|
|
|
<el-switch v-model="passwordEnabled" @change="handlePasswordEnableChange" />
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="line">
|
|
|
<div class="item full-width">
|
|
|
<div class="key">原密码</div>
|
|
|
<!-- <el-input v-model="pwdForm.oldPassword" type="password" placeholder="请输入原密码" show-password style="flex:1" /> -->
|
|
|
<BasePasswordInput v-model="pwdForm.oldPassword" layout-name="number" placeholder="请输入原密码" clearable />
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="line">
|
|
|
<div class="item full-width">
|
|
|
<div class="key">新密码</div>
|
|
|
<!-- <el-input v-model="pwdForm.newPassword" type="password" placeholder="至少 6 位" show-password style="flex:1" /> -->
|
|
|
<BasePasswordInput v-model="pwdForm.newPassword" layout-name="number" placeholder="请输入新密码" clearable />
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="line">
|
|
|
<div class="item full-width">
|
|
|
<div class="key">确认新密码</div>
|
|
|
<!-- <el-input v-model="pwdForm.confirmPassword" type="password" placeholder="再次输入新密码" show-password style="flex:1" /> -->
|
|
|
<BasePasswordInput v-model="pwdForm.confirmPassword" layout-name="number" placeholder="再次输入新密码" clearable />
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="buttonBox">
|
|
|
<div class="button save" @click="changePassword">
|
|
|
<el-icon>
|
|
|
<Check />
|
|
|
</el-icon>
|
|
|
确认修改
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 底部信息 -->
|
|
|
<div class="bottom-info">
|
|
|
<div style="color:#fff">
|
|
|
设备号:{{ commit_uid }}
|
|
|
</div>
|
|
|
<div class="bottom_item"></div>
|
|
|
<div class="bottom_item">
|
|
|
<el-button class="back-button" @click="goBack">
|
|
|
<el-icon>
|
|
|
<HomeFilled />
|
|
|
</el-icon>
|
|
|
返回主界面
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
import RPC from '@/utils/rpc'
|
|
|
import BasePasswordInput from '@/components/BasePasswordInput.vue'
|
|
|
|
|
|
const router = useRouter()
|
|
|
const commit_uid = sessionStorage.getItem('central_control_comm_uid')
|
|
|
|
|
|
// ─── 时间相关 ─────────────────────────────────────
|
|
|
const currentTime = ref('')
|
|
|
const week = ref('')
|
|
|
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
|
|
|
|
|
|
const deviceDatetime = ref('') // 设备当前时间(来自后端)
|
|
|
const targetDatetime = ref('') // 用户选择的目标时间
|
|
|
const lastSetResult = ref(null) // 上次设置结果
|
|
|
|
|
|
const updateTime = () => {
|
|
|
const now = new Date()
|
|
|
const year = now.getFullYear()
|
|
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
|
|
const day = String(now.getDate()).padStart(2, '0')
|
|
|
const hours = String(now.getHours()).padStart(2, '0')
|
|
|
const minutes = String(now.getMinutes()).padStart(2, '0')
|
|
|
const seconds = String(now.getSeconds()).padStart(2, '0')
|
|
|
week.value = weekDays[now.getDay()]
|
|
|
currentTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
|
}
|
|
|
|
|
|
// 获取设备系统时间
|
|
|
const getSystemTime = async () => {
|
|
|
try {
|
|
|
const res = await RPC.post('/m9z/system/getTime', {})
|
|
|
deviceDatetime.value = res?.datetime || ''
|
|
|
} catch (e) {
|
|
|
// 仅在 401 时 rpc.js 会触发密码弹框,其他错误已有通用提示
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 设置系统时间
|
|
|
const setSystemTime = async () => {
|
|
|
if (!targetDatetime.value) {
|
|
|
ElMessage.warning('请先选择目标时间')
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
const res = await RPC.post('/m9z/system/setTime', { datetime: targetDatetime.value })
|
|
|
if (res?.success) {
|
|
|
lastSetResult.value = res
|
|
|
deviceDatetime.value = res.new_time
|
|
|
targetDatetime.value = ''
|
|
|
ElMessage.success('系统时间设置成功')
|
|
|
}
|
|
|
} catch (e) {
|
|
|
// 401 由 rpc.js 统一处理
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ─── 电表型号 ─────────────────────────────────────
|
|
|
const currentMeterFirm = ref('')
|
|
|
const selectedMeterFirm = ref('')
|
|
|
|
|
|
const getMeterFirm = async () => {
|
|
|
try {
|
|
|
const res = await RPC.post('/m9z/meter/getMeterFirm', {})
|
|
|
currentMeterFirm.value = res?.meter_firm || ''
|
|
|
selectedMeterFirm.value = currentMeterFirm.value
|
|
|
} catch (e) {
|
|
|
// 错误由 rpc.js 处理
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const setMeterFirm = async () => {
|
|
|
if (!selectedMeterFirm.value) {
|
|
|
ElMessage.warning('请选择电表型号')
|
|
|
return
|
|
|
}
|
|
|
if (selectedMeterFirm.value === currentMeterFirm.value) {
|
|
|
ElMessage.info('电表型号未变更')
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
await ElMessageBox.confirm(
|
|
|
`确认要将电表型号修改为 ${selectedMeterFirm.value} 吗?`,
|
|
|
'提示',
|
|
|
{
|
|
|
confirmButtonText: '确定',
|
|
|
cancelButtonText: '取消',
|
|
|
type: 'warning',
|
|
|
}
|
|
|
)
|
|
|
const res = await RPC.post('/m9z/meter/setMeterFirm', {
|
|
|
meter_firm: selectedMeterFirm.value,
|
|
|
})
|
|
|
if (res?.success) {
|
|
|
currentMeterFirm.value = res.meter_firm
|
|
|
ElMessage.success(`电表型号已修改为 ${res.meter_firm}`)
|
|
|
}
|
|
|
} catch (e) {
|
|
|
if (e === 'cancel' || e === 'close') return
|
|
|
// 其他错误由 rpc.js 处理
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ─── 修改密码 ─────────────────────────────────────
|
|
|
const pwdForm = ref({ oldPassword: '', newPassword: '', confirmPassword: '' })
|
|
|
|
|
|
const changePassword = async () => {
|
|
|
if (!pwdForm.value.oldPassword || !pwdForm.value.newPassword) {
|
|
|
ElMessage.warning('请输入原密码和新密码')
|
|
|
return
|
|
|
}
|
|
|
if (pwdForm.value.newPassword.length < 6) {
|
|
|
ElMessage.warning('新密码至少 6 位')
|
|
|
return
|
|
|
}
|
|
|
if (pwdForm.value.newPassword !== pwdForm.value.confirmPassword) {
|
|
|
ElMessage.warning('两次输入的新密码不一致')
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
await RPC.post('/m9z/password/change', {
|
|
|
old_password: pwdForm.value.oldPassword,
|
|
|
new_password: pwdForm.value.newPassword,
|
|
|
})
|
|
|
ElMessage.success('密码修改成功,请重新登录')
|
|
|
// 清除 session,下次操作时重新验证
|
|
|
localStorage.removeItem('m9z_session_id')
|
|
|
pwdForm.value = { oldPassword: '', newPassword: '', confirmPassword: '' }
|
|
|
} catch (e) {
|
|
|
// 401 或其他错误由 rpc.js 统一处理
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ─── 密码保护开关 ─────────────────────────────────
|
|
|
const passwordEnabled = ref(false)
|
|
|
|
|
|
const getPasswordStatus = async () => {
|
|
|
try {
|
|
|
const res = await RPC.post('/m9z/password/status', {})
|
|
|
passwordEnabled.value = res?.enabled || false
|
|
|
} catch (e) {
|
|
|
// 错误由 rpc.js 处理
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const handlePasswordEnableChange = async (val) => {
|
|
|
const actionText = val ? '开启' : '关闭'
|
|
|
try {
|
|
|
await ElMessageBox.confirm(
|
|
|
`确认要${actionText}密码保护功能吗?`,
|
|
|
'提示',
|
|
|
{
|
|
|
confirmButtonText: '确定',
|
|
|
cancelButtonText: '取消',
|
|
|
type: 'warning',
|
|
|
}
|
|
|
)
|
|
|
|
|
|
const method = val ? '/m9z/password/enable' : '/m9z/password/disable'
|
|
|
try {
|
|
|
await RPC.post(method, {})
|
|
|
ElMessage.success(`${actionText}密码保护成功`)
|
|
|
} catch (e) {
|
|
|
// 如果接口失败,回滚状态
|
|
|
passwordEnabled.value = !val
|
|
|
}
|
|
|
} catch (e) {
|
|
|
// 用户取消,回滚状态
|
|
|
passwordEnabled.value = !val
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const goBack = () => {
|
|
|
router.push('/')
|
|
|
}
|
|
|
|
|
|
let timeInterval = null
|
|
|
|
|
|
// 处理 session 恢复后的重新加载
|
|
|
const handleSessionRestored = () => {
|
|
|
console.log('Session 已恢复,重新加载数据')
|
|
|
getSystemTime()
|
|
|
getPasswordStatus()
|
|
|
getMeterFirm()
|
|
|
}
|
|
|
|
|
|
onMounted(() => {
|
|
|
updateTime()
|
|
|
timeInterval = setInterval(updateTime, 1000)
|
|
|
getSystemTime()
|
|
|
getPasswordStatus()
|
|
|
getMeterFirm()
|
|
|
|
|
|
// 监听 session 恢复事件
|
|
|
window.addEventListener('m9z:session-restored', handleSessionRestored)
|
|
|
})
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
if (timeInterval) clearInterval(timeInterval)
|
|
|
|
|
|
// 移除事件监听
|
|
|
window.removeEventListener('m9z:session-restored', handleSessionRestored)
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
.control-view {
|
|
|
width: 100vw;
|
|
|
height: 100vh;
|
|
|
box-sizing: border-box;
|
|
|
position: relative;
|
|
|
overflow: auto;
|
|
|
background: center / cover url('@/assets/images/背景小@2x.png') no-repeat;
|
|
|
background-color: #0A3933;
|
|
|
}
|
|
|
|
|
|
.main-content {
|
|
|
position: relative;
|
|
|
z-index: 2;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
padding: 10px 20px;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
.header-status {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 20px;
|
|
|
color: rgba(255, 255, 255, 0.9);
|
|
|
}
|
|
|
|
|
|
.time-info {
|
|
|
font-size: 18px;
|
|
|
font-weight: 300;
|
|
|
width: 270px;
|
|
|
}
|
|
|
|
|
|
.page-title h1 {
|
|
|
font-size: 30px;
|
|
|
font-weight: bold;
|
|
|
margin: 0;
|
|
|
color: #ffffff;
|
|
|
text-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
|
|
|
}
|
|
|
|
|
|
.system-status {
|
|
|
min-width: 200px;
|
|
|
font-size: 18px;
|
|
|
font-weight: 300;
|
|
|
width: 270px;
|
|
|
}
|
|
|
|
|
|
.control-content {
|
|
|
display: flex;
|
|
|
width: 100%;
|
|
|
gap: 20px;
|
|
|
overflow: hidden;
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
.main_item {
|
|
|
border: .5px solid #0e9228;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
min-height: 400px;
|
|
|
width: 420px;
|
|
|
|
|
|
.main_title {
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
height: 50px;
|
|
|
font-size: 16px;
|
|
|
background-color: rgba(#188549, .3);
|
|
|
color: #fff;
|
|
|
border-bottom: .5px solid #0e9228;
|
|
|
}
|
|
|
|
|
|
.main_content {
|
|
|
padding: 24px 20px;
|
|
|
font-size: 14px;
|
|
|
background-color: rgba(#000000, .3);
|
|
|
flex: 1;
|
|
|
|
|
|
.line {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
.item {
|
|
|
flex: 1;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 12px;
|
|
|
|
|
|
&.full-width {
|
|
|
width: 100%;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.key {
|
|
|
color: #fff;
|
|
|
flex-shrink: 0;
|
|
|
width: 90px;
|
|
|
}
|
|
|
|
|
|
.value {
|
|
|
flex: 1;
|
|
|
text-align: center;
|
|
|
background-color: rgba(#188549, .3);
|
|
|
color: #fff;
|
|
|
padding: 5px 10px;
|
|
|
min-height: 32px;
|
|
|
box-sizing: border-box;
|
|
|
border-radius: 5px;
|
|
|
}
|
|
|
|
|
|
.buttonBox {
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
gap: 16px;
|
|
|
margin-top: 24px;
|
|
|
}
|
|
|
|
|
|
.button {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 6px;
|
|
|
padding: 8px 20px;
|
|
|
border-radius: 8px;
|
|
|
cursor: pointer;
|
|
|
font-size: 14px;
|
|
|
color: #fff;
|
|
|
user-select: none;
|
|
|
|
|
|
&.save {
|
|
|
background-color: #188549;
|
|
|
|
|
|
&:hover {
|
|
|
background-color: #1fa85d;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
&.refresh {
|
|
|
background-color: #1565a8;
|
|
|
|
|
|
&:hover {
|
|
|
background-color: #1a7fd4;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.result-box {
|
|
|
margin-top: 20px;
|
|
|
padding: 12px 16px;
|
|
|
background-color: rgba(#188549, .15);
|
|
|
border: 1px solid rgba(#0e9228, .5);
|
|
|
border-radius: 8px;
|
|
|
|
|
|
.result-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
margin-bottom: 6px;
|
|
|
color: rgba(255, 255, 255, 0.85);
|
|
|
font-size: 13px;
|
|
|
|
|
|
&:last-child {
|
|
|
margin-bottom: 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.result-key {
|
|
|
color: rgba(255, 255, 255, 0.5);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.bottom-info {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-top: auto;
|
|
|
padding: 10px 30px;
|
|
|
box-sizing: border-box;
|
|
|
color: rgba(255, 255, 255, 0.7);
|
|
|
font-size: 16px;
|
|
|
background-color: rgba(0, 0, 0, 0.3);
|
|
|
position: fixed;
|
|
|
bottom: 0;
|
|
|
left: 0;
|
|
|
width: 100vw;
|
|
|
z-index: 999
|
|
|
}
|
|
|
|
|
|
.back-button {
|
|
|
background: transparent;
|
|
|
border: 1px solid rgba(255, 255, 255, 0.4);
|
|
|
color: rgba(255, 255, 255, 0.8);
|
|
|
|
|
|
&:hover {
|
|
|
background: rgba(255, 255, 255, 0.1);
|
|
|
border-color: rgba(255, 255, 255, 0.6);
|
|
|
color: #fff;
|
|
|
}
|
|
|
}
|
|
|
</style>
|