You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
central-control/src/views/SystemSettings/index.vue

478 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>
<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 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
onMounted(() => {
updateTime()
timeInterval = setInterval(updateTime, 1000)
getSystemTime()
getPasswordStatus()
})
onUnmounted(() => {
if (timeInterval) clearInterval(timeInterval)
})
</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>