miaofuhao 2 ay önce
ebeveyn
işleme
1a227e02cd

+ 1 - 1
.env.development

@@ -32,7 +32,7 @@ VITE_APP_BASE_CHAT_WS = 'ws://192.168.1.15:8060/'
32 32
 # VITE_APP_SIP_CONTACT_URL = "@192.168.8.17;transport=ws"
33 33
 
34 34
 # VITE_APP_PHONE_TYPE = 'PHONE'
35
-VITE_APP_PHONE_TYPE = 'SIP'
35
+VITE_APP_PHONE_TYPE = 'PHONE'
36 36
 VITE_APP_CALL_TYPE = 'VIOCE'
37 37
 VITE_APP_SIP_URL = 'ws://192.168.1.19:5066'
38 38
 VITE_APP_SIP_IP = '192.168.1.19'

BIN
0904dist.zip


BIN
10271dist.zip


+ 128 - 0
src/components/main/ForceResetPwd.vue

@@ -0,0 +1,128 @@
1
+<template>
2
+  <el-dialog
3
+    :title="title"
4
+    v-model="dialogVisible"
5
+    width="400px"
6
+    :close-on-click-modal="false"
7
+    :close-on-press-escape="false"
8
+    :show-close="false"
9
+    :before-close="handleBeforeClose"
10
+  >
11
+    <el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
12
+      <el-form-item label="旧密码" prop="oldPwd">
13
+        <el-input
14
+          v-model="user.oldPwd"
15
+          placeholder="请输入旧密码"
16
+          type="password"
17
+          show-password
18
+        />
19
+      </el-form-item>
20
+      <el-form-item label="新密码" prop="Pwd">
21
+        <el-input
22
+          v-model="user.Pwd"
23
+          placeholder="请输入新密码(必须包含字母大小写和数字,长度8-16位)"
24
+          type="password"
25
+          show-password
26
+        />
27
+      </el-form-item>
28
+      <el-form-item label="确认密码" prop="confirmPassword">
29
+        <el-input
30
+          v-model="user.confirmPassword"
31
+          placeholder="请确认新密码"
32
+          type="password"
33
+          show-password
34
+        />
35
+      </el-form-item>
36
+      <el-form-item>
37
+        <el-button type="primary" @click="submit" style="width: 100%">保存</el-button>
38
+      </el-form-item>
39
+    </el-form>
40
+  </el-dialog>
41
+</template>
42
+
43
+<script setup>
44
+import { ref, reactive, defineEmits } from 'vue'
45
+import { updateUserPwd } from '@/api/system/user'
46
+import useUserStore from '@/store/modules/user'
47
+
48
+const props = defineProps({
49
+  visible: {
50
+    type: Boolean,
51
+    default: false
52
+  }
53
+})
54
+
55
+const emit = defineEmits(['update:visible', 'success'])
56
+
57
+const dialogVisible = computed({
58
+  get() {
59
+    return props.visible
60
+  },
61
+  set(val) {
62
+    // 不允许关闭,除非密码修改成功
63
+  }
64
+})
65
+
66
+const title = '强制修改密码'
67
+const { proxy } = getCurrentInstance()
68
+
69
+const user = reactive({
70
+  oldPwd: undefined,
71
+  Pwd: undefined,
72
+  confirmPassword: undefined
73
+})
74
+
75
+const equalToPassword = (rule, value, callback) => {
76
+  if (user.Pwd !== value) {
77
+    callback(new Error('两次输入的密码不一致'))
78
+  } else {
79
+    callback()
80
+  }
81
+}
82
+
83
+const passwordStrength = (rule, value, callback) => {
84
+  // 检查密码强度:必须包含字母大小写和数字,长度8-16位
85
+  const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,16}$/
86
+  if (!regex.test(value)) {
87
+    callback(new Error('密码必须包含字母大小写和数字,长度8-16位'))
88
+  } else {
89
+    callback()
90
+  }
91
+}
92
+
93
+const rules = ref({
94
+  oldPwd: [{ required: true, message: '旧密码不能为空', trigger: 'blur' }],
95
+  Pwd: [
96
+    { required: true, message: '新密码不能为空', trigger: 'blur' },
97
+    { validator: passwordStrength, trigger: 'blur' }
98
+  ],
99
+  confirmPassword: [
100
+    { required: true, message: '确认密码不能为空', trigger: 'blur' },
101
+    { required: true, validator: equalToPassword, trigger: 'blur' }
102
+  ]
103
+})
104
+
105
+const pwdRef = ref(null)
106
+
107
+/** 提交按钮 */
108
+function submit() {
109
+  pwdRef.value.validate((valid) => {
110
+    if (valid) {
111
+      updateUserPwd(user.oldPwd, user.Pwd).then((response) => {
112
+        proxy.$modal.msgSuccess('修改成功')
113
+        emit('success')
114
+        emit('update:visible', false)
115
+        // 重置表单
116
+        user.oldPwd = undefined
117
+        user.Pwd = undefined
118
+        user.confirmPassword = undefined
119
+      })
120
+    }
121
+  })
122
+}
123
+
124
+/** 阻止关闭 */
125
+function handleBeforeClose(done) {
126
+  // 不允许关闭
127
+}
128
+</script>

+ 18 - 3
src/components/main/Navbar/cpns/TelephoneLogin/index.vue

@@ -50,6 +50,17 @@
50 50
           <el-input v-model="formLogin.accountNumber" disabled autocomplete="off" />
51 51
         </el-form-item>
52 52
         <el-form-item
53
+          label="坐席组"
54
+          :label-width="formLabelWidth"
55
+          prop="groupNumber"
56
+          required
57
+        >
58
+          <el-radio-group v-model="formLogin.groupNumber">
59
+            <el-radio label="zxz">呼入组</el-radio>
60
+            <el-radio label="whz">呼出组</el-radio>
61
+          </el-radio-group>
62
+        </el-form-item>
63
+        <el-form-item
53 64
           label="分机号"
54 65
           :label-width="formLabelWidth"
55 66
           prop="extensionNumber"
@@ -82,7 +93,7 @@ import useUserStore from '@/store/modules/user'
82 93
 import { jssipInit,localMediaStream } from '@/utils/jsSip'
83 94
 import Cookies from 'js-cookie'
84 95
 
85
-import { ref } from 'vue'
96
+import { ref, computed, onMounted } from 'vue'
86 97
 const { proxy } = getCurrentInstance();
87 98
 
88 99
 const scoketState = ref('签出')
@@ -101,6 +112,7 @@ const formLogin = reactive({
101 112
 })
102 113
 const rules = reactive({
103 114
   accountNumber: [{ required: true, message: '请输入工号', trigger: 'blur' }],
115
+  groupNumber: [{ required: true, message: '请选择坐席组', trigger: 'change' }],
104 116
   extensionNumber: [
105 117
     { required: true, message: '请输入分机号', trigger: 'blur' },
106 118
     { min: 4, max: 4, message: '分机号长度为4位', trigger: 'blur' }
@@ -165,6 +177,8 @@ function signIn() {
165 177
   }
166 178
   
167 179
 }
180
+
181
+
168 182
 function sipRegistered(data) {
169 183
   console.log(data)
170 184
 }
@@ -177,12 +191,12 @@ function loginIn() {
177 191
     Type: 'Login',
178 192
     AgentId: useUserStore().name,
179 193
     AgentExten: Cookies.get('extensionNumber'),
180
-    AgentGroup: 'zxz'
194
+    AgentGroup: Cookies.get('groupNumber') || 'zxz'
181 195
   }
182 196
   
183 197
   useSocketStore().setAccountNumber(useUserStore().name)
184 198
   useSocketStore().setExtensionNumber(Cookies.get('extensionNumber'))
185
-  useSocketStore().setGroupNumber('zxz')
199
+  useSocketStore().setGroupNumber(Cookies.get('groupNumber') || 'zxz')
186 200
   Send(scoketDatas.value, (data) => {
187 201
     if (data.Type === 'Login' && data.Result === true) {
188 202
       loginFlag.value = true
@@ -205,6 +219,7 @@ const signInConfirm = async (formEl) => {
205 219
       }
206 220
 
207 221
       Cookies.set('extensionNumber', formLogin.extensionNumber, { expires: 30 })
222
+      Cookies.set('groupNumber', formLogin.groupNumber, { expires: 30 })
208 223
       useSocketStore().setAccountNumber(formLogin.accountNumber)
209 224
       useSocketStore().setExtensionNumber(formLogin.extensionNumber)
210 225
       useSocketStore().setGroupNumber(formLogin.groupNumber)

+ 8 - 5
src/views/login/login.vue

@@ -117,12 +117,15 @@ function handleLogin() {
117 117
   proxy.$refs.loginRef.validate((valid) => {
118 118
     if (valid) {
119 119
       loading.value = true
120
+      // 存储加密前的密码用于密码强度检查
121
+        Cookies.set('plainPassword', loginForm.value.password, { expires: 30 })
120 122
       // 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
121 123
       if (loginForm.value.rememberMe) {
122 124
         Cookies.set('username', loginForm.value.username, { expires: 30 })
123
-        Cookies.set('password', encrypt(loginForm.value.password), {
124
-          expires: 30
125
-        })
125
+        
126
+        // 密码加密后存储
127
+        Cookies.set('password', encrypt(loginForm.value.password), { expires: 30 })
128
+        
126 129
         Cookies.set('rememberMe', loginForm.value.rememberMe, { expires: 30 })
127 130
       } else {
128 131
         // 否则移除
@@ -161,11 +164,11 @@ function getCode() {
161 164
 function getCookie() {
162 165
   const username = Cookies.get('username')
163 166
   const password = Cookies.get('password')
167
+  const plainPassword = Cookies.get('plainPassword')
164 168
   const rememberMe = Cookies.get('rememberMe')
165 169
   loginForm.value = {
166 170
     username: username === undefined ? loginForm.value.username : username,
167
-    password:
168
-      password === undefined ? loginForm.value.password : decrypt(password),
171
+    password: password === undefined ? loginForm.value.password : decrypt(password),
169 172
     rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
170 173
   }
171 174
 }

+ 57 - 14
src/views/main/main.vue

@@ -31,25 +31,31 @@
31 31
     <drawer-phone ref="drawerPhoneRef" id="drawerPhoneOutID"></drawer-phone>
32 32
     <!-- 来电弹屏 -->
33 33
     <page-incoming></page-incoming>
34
-	<!-- 右下角消息提示 -->
35
-	<msg-modal></msg-modal>
34
+    <!-- 右下角消息提示 -->
35
+    <msg-modal></msg-modal>
36
+    <!-- 强制修改密码弹框 -->
37
+    <force-reset-pwd v-model:visible="showForceResetPwd" @success="onPasswordResetSuccess" />
36 38
   </div>
37 39
 </template>
38 40
 
39 41
 <script setup>
40 42
 import { useWindowSize } from '@vueuse/core'
43
+import { onMounted, ref, computed, watchEffect } from 'vue'
44
+
41 45
 import Sidebar from '@/components/main/Sidebar/index.vue'
42
-import {
43
-  AppMain,
44
-  Navbar,
45
-  TagsView,
46
-  Settings,
47
-  DrawerPhone,
48
-  PageIncoming,
49
-  MsgModal
46
+import { 
47
+  AppMain, 
48
+  Navbar, 
49
+  TagsView, 
50
+  Settings, 
51
+  DrawerPhone, 
52
+  PageIncoming, 
53
+  MsgModal 
50 54
 } from '@/components/main'
55
+import ForceResetPwd from '@/components/main/ForceResetPwd.vue'
51 56
 import useAppStore from '@/store/modules/app'
52 57
 import useSettingsStore from '@/store/modules/settings'
58
+import Cookies from 'js-cookie'
53 59
 
54 60
 const settingsStore = useSettingsStore()
55 61
 const theme = computed(() => settingsStore.theme)
@@ -69,6 +75,10 @@ const classObj = computed(() => ({
69 75
 const { width, height } = useWindowSize()
70 76
 const WIDTH = 992 // refer to Bootstrap's responsive design
71 77
 
78
+// 强制修改密码相关
79
+const showForceResetPwd = ref(false)
80
+const passwordChecked = ref(false)
81
+
72 82
 watchEffect(() => {
73 83
   if (device.value === 'mobile' && sidebar.value.opened) {
74 84
     useAppStore().closeSideBar({ withoutAnimation: false })
@@ -80,19 +90,52 @@ watchEffect(() => {
80 90
     useAppStore().toggleDevice('desktop')
81 91
   }
82 92
 })
83
-// useSelectStore().getPageListAction()
93
+
94
+// 检查密码强度
95
+function checkPasswordStrength() {
96
+  // 只有在用户登录后且未检查过密码强度时才检查
97
+  if (passwordChecked.value) {
98
+    return
99
+  }
100
+  
101
+  const plainPassword = Cookies.get('plainPassword')
102
+  console.log('plainPassword', plainPassword)
103
+  if (plainPassword) {
104
+    // 检查密码强度:必须包含字母大小写和数字,长度8-16位
105
+    const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,16}$/
106
+    if (!regex.test(plainPassword)) {
107
+      // 密码强度不足,显示强制修改密码弹框
108
+      showForceResetPwd.value = true
109
+    }
110
+  }
111
+  // 标记为已检查
112
+  passwordChecked.value = true
113
+}
114
+
115
+// 密码修改成功后的回调
116
+function onPasswordResetSuccess() {
117
+  // 密码修改成功后,可以清除标记,下次登录时重新检查
118
+  passwordChecked.value = false
119
+}
120
+
121
+onMounted(() => {
122
+  // 页面加载后检查密码强度
123
+  setTimeout(() => {
124
+    checkPasswordStrength()
125
+  }, 1000)
126
+})
127
+
84 128
 const drawerPhoneOutID = ref(null)
85 129
 const drawerPhoneRef = ref(null)
86 130
 const drawerPhoneFlag = ref(false)
87
-onMounted(() => {
88
-  // document.addEventListener('click', handleClickOutsideDrawer);
89
-})
131
+
90 132
 function handleClickOutsideDrawer(event) {
91 133
   drawerPhoneOutID.value = document.getElementById('drawerPhoneOutID')
92 134
   if (drawerPhoneOutID.value && !drawerPhoneOutID.value.contains(event.target)) {
93 135
     drawerPhoneRef.value.hidePhone()
94 136
   }
95 137
 }
138
+
96 139
 function handleClickOutside() {
97 140
   useAppStore().closeSideBar({ withoutAnimation: false })
98 141
 }