|
@@ -1,47 +1,104 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div v-loading="loading" class="user-password-form">
|
|
|
|
|
- <h3>Camera Password Settings</h3>
|
|
|
|
|
- <el-form
|
|
|
|
|
- ref="formRef"
|
|
|
|
|
- :model="userManagementFormData"
|
|
|
|
|
- :rules="userManagementFormRules"
|
|
|
|
|
- @keyup.enter="handleConfirm"
|
|
|
|
|
- >
|
|
|
|
|
- <el-form-item prop="oldPassword">
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model.trim="userManagementFormData.oldPassword"
|
|
|
|
|
- placeholder="Old Password"
|
|
|
|
|
- size="large"
|
|
|
|
|
- tabindex="1"
|
|
|
|
|
- type="password"
|
|
|
|
|
- show-password
|
|
|
|
|
- class="el-input"
|
|
|
|
|
- />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item prop="newPassword">
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model.trim="userManagementFormData.newPassword"
|
|
|
|
|
- placeholder="New Password"
|
|
|
|
|
- size="large"
|
|
|
|
|
- tabindex="2"
|
|
|
|
|
- type="password"
|
|
|
|
|
- show-password
|
|
|
|
|
- />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item prop="newPasswordSc">
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model.trim="userManagementFormData.newPasswordSc"
|
|
|
|
|
- placeholder="Confirm New Password"
|
|
|
|
|
- size="large"
|
|
|
|
|
- tabindex="3"
|
|
|
|
|
- type="password"
|
|
|
|
|
- show-password
|
|
|
|
|
- />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-button type="primary" @click.prevent="handleConfirm">
|
|
|
|
|
- Save
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </el-form>
|
|
|
|
|
|
|
+ <div class="password-form-container">
|
|
|
|
|
+ <!-- RTSP Password Verification Switch -->
|
|
|
|
|
+ <div class="settings-section" v-loading="RtspLoading">
|
|
|
|
|
+ <div class="section-header">
|
|
|
|
|
+ <h3>RTSP Password Verification</h3>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="switch-container">
|
|
|
|
|
+ <div class="switch-content">
|
|
|
|
|
+ <div class="switch-info">
|
|
|
|
|
+ <p class="switch-label">Verification Status</p>
|
|
|
|
|
+ <p class="switch-description">
|
|
|
|
|
+ {{ RtspPwdEn === 1 ? 'Enabled - Username and password required to access video stream' : 'Disabled - No authentication required to access video stream' }}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-switch
|
|
|
|
|
+ v-model="RtspPwdEn"
|
|
|
|
|
+ style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
|
|
|
|
|
+ :active-value="1"
|
|
|
|
|
+ :inactive-value="0"
|
|
|
|
|
+ @change="handleRtspPwdEnChange"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- RTSP Access Examples -->
|
|
|
|
|
+ <div class="tips-box">
|
|
|
|
|
+ <div class="tips-title">
|
|
|
|
|
+ <span class="tips-icon">ℹ️</span>
|
|
|
|
|
+ <span>Video Stream Access Address Examples</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="tips-content">
|
|
|
|
|
+ <div class="tip-item">
|
|
|
|
|
+ <p class="tip-label">Password Verification {{ RtspPwdEn === 0 ? '(Disabled)' : '' }}:</p>
|
|
|
|
|
+ <code>rtsp://192.168.0.105:554/video1</code>
|
|
|
|
|
+ <p class="tip-note">Please change to your camera's real IP address and access on the same local area network</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="tip-item">
|
|
|
|
|
+ <p class="tip-label">Password Verification {{ RtspPwdEn === 1 ? '(Enabled)' : '' }}:</p>
|
|
|
|
|
+ <code>rtsp://username:password@192.168.0.105:554/video1</code>
|
|
|
|
|
+ <p class="tip-note">Please change to your camera's real IP address and credentials, access on the same local area network</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Password Change Form -->
|
|
|
|
|
+ <div v-loading="loading" class="settings-section">
|
|
|
|
|
+ <div class="section-header">
|
|
|
|
|
+ <h3>Change Password</h3>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ ref="formRef"
|
|
|
|
|
+ :model="userManagementFormData"
|
|
|
|
|
+ :rules="userManagementFormRules"
|
|
|
|
|
+ @keyup.enter="handleConfirm"
|
|
|
|
|
+ class="password-form"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form-item prop="oldPassword" label="Old Password" label-width="145px">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model.trim="userManagementFormData.oldPassword"
|
|
|
|
|
+ placeholder="Enter your old password"
|
|
|
|
|
+ size="large"
|
|
|
|
|
+ tabindex="1"
|
|
|
|
|
+ type="password"
|
|
|
|
|
+ show-password
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item prop="newPassword" label="New Password" label-width="145px">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model.trim="userManagementFormData.newPassword"
|
|
|
|
|
+ placeholder="Enter your new password"
|
|
|
|
|
+ size="large"
|
|
|
|
|
+ tabindex="2"
|
|
|
|
|
+ type="password"
|
|
|
|
|
+ show-password
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item prop="newPasswordSc" label="Confirm Password" label-width="145px">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model.trim="userManagementFormData.newPasswordSc"
|
|
|
|
|
+ placeholder="Re-enter your new password"
|
|
|
|
|
+ size="large"
|
|
|
|
|
+ tabindex="3"
|
|
|
|
|
+ type="password"
|
|
|
|
|
+ show-password
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="form-actions">
|
|
|
|
|
+ <el-button type="primary" size="large" @click.prevent="handleConfirm">
|
|
|
|
|
+ Save
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button size="large" @click="resetForm">
|
|
|
|
|
+ Reset
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
@@ -50,16 +107,18 @@ import {
|
|
|
type FormInstance,
|
|
type FormInstance,
|
|
|
type FormRules,
|
|
type FormRules,
|
|
|
ElMessage,
|
|
ElMessage,
|
|
|
- ElLoading
|
|
|
|
|
} from 'element-plus'
|
|
} from 'element-plus'
|
|
|
-import { putPasswordApi } from '@/api/userManagement'
|
|
|
|
|
|
|
+import { putPasswordApi, getRtspPasswordApi, putRtspPasswordApi } from '@/api/userManagement'
|
|
|
import { useUserStore } from '@/stores/modules/user'
|
|
import { useUserStore } from '@/stores/modules/user'
|
|
|
-import { ref } from 'vue'
|
|
|
|
|
|
|
+import { ref, reactive } from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
|
|
|
-/** 表单元素的引用 */
|
|
|
|
|
const formRef = ref<FormInstance | null>(null)
|
|
const formRef = ref<FormInstance | null>(null)
|
|
|
-const version = import.meta.env.VITE_APP_TITLE
|
|
|
|
|
|
|
+const RtspPwdEn = ref(1)
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const router = useRouter()
|
|
|
|
|
+
|
|
|
|
|
+const RtspLoading = ref(false)
|
|
|
|
|
|
|
|
const userManagementFormData = reactive({
|
|
const userManagementFormData = reactive({
|
|
|
name: useUserStore().username || 'admin',
|
|
name: useUserStore().username || 'admin',
|
|
@@ -67,17 +126,19 @@ const userManagementFormData = reactive({
|
|
|
newPassword: '',
|
|
newPassword: '',
|
|
|
newPasswordSc: ''
|
|
newPasswordSc: ''
|
|
|
})
|
|
})
|
|
|
-/** 表单校验规则 */
|
|
|
|
|
|
|
+
|
|
|
const userManagementFormRules: FormRules = {
|
|
const userManagementFormRules: FormRules = {
|
|
|
oldPassword: [
|
|
oldPassword: [
|
|
|
{ required: true, message: 'Please enter your old password', trigger: 'blur' },
|
|
{ required: true, message: 'Please enter your old password', trigger: 'blur' },
|
|
|
{ min: 1, max: 16, message: 'Password length must be between 1 and 16 characters', trigger: 'blur' }
|
|
{ min: 1, max: 16, message: 'Password length must be between 1 and 16 characters', trigger: 'blur' }
|
|
|
],
|
|
],
|
|
|
newPassword: [
|
|
newPassword: [
|
|
|
- { required: true, message: 'Please enter your new password.', trigger: 'blur' },
|
|
|
|
|
|
|
+ { required: true, message: 'Please enter your new password', trigger: 'blur' },
|
|
|
{ min: 1, max: 16, message: 'Password length must be between 1 and 16 characters', trigger: 'blur' }
|
|
{ min: 1, max: 16, message: 'Password length must be between 1 and 16 characters', trigger: 'blur' }
|
|
|
],
|
|
],
|
|
|
newPasswordSc: [
|
|
newPasswordSc: [
|
|
|
|
|
+ { required: true, message: 'Please enter your confirm password', trigger: 'blur' },
|
|
|
|
|
+ { min: 1, max: 16, message: 'Password length must be between 1 and 16 characters', trigger: 'blur' },
|
|
|
{
|
|
{
|
|
|
validator: (rule, value, callback) => {
|
|
validator: (rule, value, callback) => {
|
|
|
if (value !== userManagementFormData.newPassword) {
|
|
if (value !== userManagementFormData.newPassword) {
|
|
@@ -91,47 +152,234 @@ const userManagementFormRules: FormRules = {
|
|
|
]
|
|
]
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const router = useRouter()
|
|
|
|
|
-const loading = ref(false)
|
|
|
|
|
|
|
|
|
|
-/** 登录按钮 Loading */
|
|
|
|
|
|
|
+function getRtspPassword() {
|
|
|
|
|
+ getRtspPasswordApi().then(res => {
|
|
|
|
|
+ RtspPwdEn.value = res.data.RtspPwdEn
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ getRtspPassword()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+const handleRtspPwdEnChange = () => {
|
|
|
|
|
+ RtspLoading.value = true
|
|
|
|
|
+ putRtspPasswordApi({ RtspPwdEn: RtspPwdEn.value }).then(res => {
|
|
|
|
|
+ if(res.data == 'ok\n') {
|
|
|
|
|
+ ElMessage.success('Operation successful')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error('Failed to change RTSP password. Please try again.')
|
|
|
|
|
+ }
|
|
|
|
|
+ }).finally(() => {
|
|
|
|
|
+ RtspLoading.value = false
|
|
|
|
|
+ getRtspPassword()
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const handleConfirm = () => {
|
|
const handleConfirm = () => {
|
|
|
- formRef.value?.validate((valid: boolean, fields) => {
|
|
|
|
|
|
|
+ formRef.value?.validate((valid: boolean) => {
|
|
|
if (valid) {
|
|
if (valid) {
|
|
|
- let loading = ElLoading.service()
|
|
|
|
|
|
|
+ loading.value = true
|
|
|
putPasswordApi(userManagementFormData)
|
|
putPasswordApi(userManagementFormData)
|
|
|
- .then((res) => {
|
|
|
|
|
- if (res.data == 'ok\n') {
|
|
|
|
|
- ElMessage.success('Success')
|
|
|
|
|
- useUserStore().logout() // 退出登录
|
|
|
|
|
- router.push('/login')
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error('Failed')
|
|
|
|
|
- }
|
|
|
|
|
- loading.close()
|
|
|
|
|
- })
|
|
|
|
|
- .catch((error) => {
|
|
|
|
|
- loading.close()
|
|
|
|
|
- ElMessage.success('Failed' + error)
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ .then((res) => {
|
|
|
|
|
+ if (res.data == 'ok\n') {
|
|
|
|
|
+ ElMessage.success('Password changed successfully. Please log in again.')
|
|
|
|
|
+ useUserStore().logout()
|
|
|
|
|
+ router.push('/login')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error('Failed to change password. Please try again.')
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((error) => {
|
|
|
|
|
+ ElMessage.error('Change failed: ' + error)
|
|
|
|
|
+ })
|
|
|
|
|
+ .finally(() => {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ })
|
|
|
} else {
|
|
} else {
|
|
|
ElMessage.error('Please check your input')
|
|
ElMessage.error('Please check your input')
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+const resetForm = () => {
|
|
|
|
|
+ formRef.value?.resetFields()
|
|
|
|
|
+}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|
|
|
-.user-password-form{
|
|
|
|
|
- //margin-top: 20px;
|
|
|
|
|
- margin-left: 10px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- //align-items: center;
|
|
|
|
|
- //justify-content: center;
|
|
|
|
|
-}
|
|
|
|
|
-.el-input{
|
|
|
|
|
- width: 300px;
|
|
|
|
|
|
|
+.password-form-container {
|
|
|
|
|
+ max-width: 700px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+ //padding: 30px;
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+
|
|
|
|
|
+ .settings-section {
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 25px;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
|
|
|
|
+
|
|
|
|
|
+ .section-header {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ border-bottom: 2px solid #f0f0f0;
|
|
|
|
|
+ padding-bottom: 15px;
|
|
|
|
|
+
|
|
|
|
|
+ h3 {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #1f2d3d;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .switch-container {
|
|
|
|
|
+ .switch-content {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ padding: 15px 0;
|
|
|
|
|
+
|
|
|
|
|
+ .switch-info {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+
|
|
|
|
|
+ .switch-label {
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #1f2d3d;
|
|
|
|
|
+ margin: 0 0 5px 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .switch-description {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .tips-box {
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+ border-left: 4px solid #409eff;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ margin-top: 20px;
|
|
|
|
|
+
|
|
|
|
|
+ .tips-title {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #1f2d3d;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+
|
|
|
|
|
+ .tips-icon {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .tips-content {
|
|
|
|
|
+ .tip-item {
|
|
|
|
|
+ margin-bottom: 15px;
|
|
|
|
|
+
|
|
|
|
|
+ &:last-child {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .tip-label {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ margin: 0 0 8px 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ code {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ border: 1px solid #e4e7eb;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ padding: 10px 12px;
|
|
|
|
|
+ font-family: 'Monaco', 'Courier New', monospace;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #d63384;
|
|
|
|
|
+ word-break: break-all;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .tip-note {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .password-form {
|
|
|
|
|
+ :deep(.el-form-item) {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+
|
|
|
|
|
+ &:last-of-type {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-form-item__label) {
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .form-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-top: 30px;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+
|
|
|
|
|
+ .el-button {
|
|
|
|
|
+ min-width: 120px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
-</style>
|
|
|
|
|
|
|
|
|
|
|
|
+/* Responsive Design */
|
|
|
|
|
+@media (max-width: 768px) {
|
|
|
|
|
+ .password-form-container {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ max-width: 100%;
|
|
|
|
|
+
|
|
|
|
|
+ .form-header {
|
|
|
|
|
+ margin-bottom: 30px;
|
|
|
|
|
+
|
|
|
|
|
+ .form-title {
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .settings-section {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .switch-container .switch-content {
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: 15px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .form-actions {
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+
|
|
|
|
|
+ .el-button {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|