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.

314 lines
7.6 KiB

3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
  1. <script setup>
  2. import {ref, reactive, computed, watch, onMounted} from 'vue'
  3. import {CircleCloseFilled, SuccessFilled} from '@element-plus/icons-vue'
  4. import {ElMessage} from "element-plus";
  5. import API from '@/util/http'
  6. import PasswordSuccess from './PasswordSuccess.vue';
  7. import router from "@/router/index.js";
  8. // 父组件调用
  9. const emit = defineEmits(['confirm'])
  10. const passwdFormRef = ref(null)
  11. const passwd = reactive({
  12. account: '',
  13. oldPassword: '',
  14. newPassword: '',
  15. againPassword: ''
  16. })
  17. const errorMsg = ref('')
  18. //获取用户信息的account
  19. const getAccount= async function() {
  20. try {
  21. const adminData = ref({})
  22. const result = await API({ url: '/admin/userinfo', data: {} })
  23. adminData.value = result
  24. console.log('管理员用户信息', adminData.value)
  25. passwd.account = adminData.value.account
  26. } catch (error) {
  27. console.log('请求失败', error)
  28. }
  29. }
  30. // 实时密码规则校验
  31. const isLengthValid = computed(() =>
  32. /^[A-Za-z0-9~!@#$%^&*()_+\-=[\]{}|;:,.<>?/]{8,16}$/.test(passwd.newPassword)
  33. );
  34. const isComplexValid = computed(() => {
  35. const rules = [/\d/, /[a-z]/, /[A-Z]/, /[^a-zA-Z0-9]/]
  36. return rules.filter((r) => r.test(passwd.newPassword)).length >= 2
  37. })
  38. watch(() => passwd.newPassword, (val) => {
  39. if (val && val === passwd.oldPassword) {
  40. errorMsg.value = '新密码不能与旧密码一致'
  41. } else {
  42. errorMsg.value = ''
  43. }
  44. })
  45. // 提交加载状态
  46. const loading = ref(false)
  47. // 表单校验规则
  48. const rules = reactive({
  49. oldPassword: [{required: true, message: '请输入原密码', trigger: 'blur'}],
  50. newPassword: [
  51. {required: true, message: '新密码不能为空', trigger: 'blur'},
  52. {
  53. validator: (rule, value, callback) => {
  54. if (!/^[a-zA-Z0-9!@#$%^&*()-_+={}[\]|\\:;"'<>,.?/~\`]+$/.test(value)) {
  55. callback(new Error('密码只能包含数字、字母或符号'));
  56. } else if (value === passwd.oldPassword) {
  57. callback(new Error('新密码不能与旧密码一致'))
  58. } else if (value.length < 8 || value.length > 16) {
  59. callback(new Error('长度应在 8 到 16 个字符'))
  60. } else {
  61. const types = [/\d/, /[a-z]/, /[A-Z]/, /[!@#$%^&*()\-_+={}[\]|\\:;"'<>,.?/~`]/];
  62. const matchCount = types.filter((r) => r.test(value)).length
  63. if (matchCount < 2) {
  64. callback(new Error('密码至少包含两种类型(数字、字母或符号)'))
  65. } else {
  66. callback()
  67. }
  68. }
  69. },
  70. trigger: 'blur'
  71. }
  72. ],
  73. againPassword: [
  74. {required: true, message: '请再次输入新密码', trigger: 'blur'},
  75. {
  76. validator: (rule, value, callback) => {
  77. if (value !== passwd.newPassword) {
  78. callback(new Error('两次输入密码不一致'))
  79. } else {
  80. callback()
  81. }
  82. },
  83. trigger: 'blur'
  84. }
  85. ]
  86. })
  87. // 修改密码接口调用方法
  88. const changePassword = async function () {
  89. try {
  90. const params = {
  91. account: passwd.account,
  92. oldPassword: passwd.oldPassword,
  93. newPassword: passwd.newPassword,
  94. againPassword: passwd.againPassword
  95. }
  96. const result = await API({url: '/admin/password', data: params})
  97. console.log('修改密码结果:', result)
  98. if (result.code === 200) {
  99. // 使用命名路由跳转
  100. //await router.push({ name: 'PasswordSuccess' });
  101. ElMessage.success('修改密码成功');
  102. // 或者使用路径跳转(确保大小写完全匹配)
  103. //await router.push('/PasswordSuccess');
  104. emit('confirm')
  105. // 延迟跳转,确保用户看到成功消息
  106. setTimeout(() => {
  107. router.push('/PasswordSuccess');
  108. }, 1000);
  109. }else if (result.code === 0){
  110. ElMessage.error('原密码错误,请重新输入')
  111. passwd.oldPassword = '';
  112. }else if(result.code === 400){
  113. // 显示失败弹窗
  114. console.log('修改密码失败')
  115. ElMessage.error('修改密码失败')
  116. //todo 待完善
  117. }
  118. } catch (error) {
  119. console.error('修改密码失败', error)
  120. ElMessage.error('操作失败')
  121. // 抛出错误让外层捕获,保持finally正常执行
  122. throw error
  123. }
  124. }
  125. // 表单提交与重置
  126. const resetFields = () => {
  127. if (passwdFormRef.value) {
  128. passwdFormRef.value.resetFields()
  129. }
  130. errorMsg.value = ''
  131. }
  132. // 暴露 resetFields 方法给父组件调用
  133. defineExpose({
  134. resetFields
  135. })
  136. // 修改提交方法
  137. const onSubmit = () => {
  138. // 若正在加载中则直接返回,防止重复提交
  139. if (loading.value) return
  140. passwdFormRef.value.validate(async (valid) => {
  141. if (valid) {
  142. loading.value = true // 开始加载
  143. try {
  144. await changePassword() // 调用修改密码接口
  145. } finally {
  146. loading.value = false // 无论成功失败都关闭加载态
  147. }
  148. } else {
  149. console.log('表单校验失败')
  150. }
  151. })
  152. }
  153. onMounted(() => {
  154. getAccount()
  155. })
  156. </script>
  157. <template>
  158. <div class="form-container">
  159. <el-form
  160. ref="passwdFormRef"
  161. :model="passwd"
  162. :rules="rules"
  163. label-width="100px"
  164. class="password-form"
  165. >
  166. <h3 class="form-title">修改密码</h3>
  167. <!-- 原密码 -->
  168. <el-form-item prop="oldPassword" label="原密码">
  169. <el-input
  170. v-model.trim="passwd.oldPassword"
  171. type="password"
  172. placeholder="请输入原密码"
  173. show-password
  174. />
  175. </el-form-item>
  176. <!-- 新密码 -->
  177. <el-form-item prop="newPassword" label="新密码">
  178. <el-input
  179. v-model.trim="passwd.newPassword"
  180. type="password"
  181. placeholder="请输入新密码"
  182. show-password
  183. />
  184. </el-form-item>
  185. <!-- 重复密码 -->
  186. <el-form-item prop="againPassword" label="重复密码">
  187. <el-input
  188. v-model.trim="passwd.againPassword"
  189. type="password"
  190. placeholder="请再次输入新密码"
  191. show-password
  192. />
  193. </el-form-item>
  194. <!-- 密码规则提示 -->
  195. <div class="rule-tips">
  196. <div :class="isLengthValid ? 'tip pass' : 'tip neutral'">
  197. <el-icon>
  198. <component :is="isLengthValid ? SuccessFilled : SuccessFilled"/>
  199. </el-icon>
  200. 密码由8-16位数字字母或符号组成
  201. </div>
  202. <div :class="isComplexValid ? 'tip pass' : 'tip neutral'">
  203. <el-icon>
  204. <component :is="isComplexValid ? SuccessFilled : SuccessFilled"/>
  205. </el-icon>
  206. 至少含2种以上字符
  207. </div>
  208. <div v-if="errorMsg" class="tip fail">
  209. <el-icon>
  210. <CircleCloseFilled/>
  211. </el-icon>
  212. {{ errorMsg }}
  213. </div>
  214. </div>
  215. <!-- 按钮 -->
  216. <div class="button-group">
  217. <el-button
  218. type="primary"
  219. @click="onSubmit"
  220. style="width: 300px"
  221. :loading="loading"
  222. :disabled="!isLengthValid || !isComplexValid"
  223. >
  224. {{ loading ? '修改中...' : '确定' }}
  225. </el-button>
  226. </div>
  227. </el-form>
  228. </div>
  229. </template>
  230. <style scoped>
  231. .form-container {
  232. justify-content: center;
  233. align-items: center;
  234. }
  235. .password-form {
  236. border-radius: 8px;
  237. }
  238. .form-title {
  239. text-align: left;
  240. margin-bottom: 24px;
  241. font-size: 20px;
  242. font-weight: bold;
  243. }
  244. .button-group {
  245. display: flex;
  246. justify-content: center;
  247. margin-top: 24px;
  248. }
  249. .rule-tips {
  250. margin-left: 100px;
  251. margin-bottom: 12px;
  252. }
  253. .tip {
  254. font-size: 14px;
  255. margin: 4px 0;
  256. display: flex;
  257. align-items: center;
  258. }
  259. .pass {
  260. color: #67c23a;
  261. }
  262. .neutral {
  263. color: #999;
  264. }
  265. .fail {
  266. color: #f56c6c;
  267. }
  268. .rule-tips .el-icon {
  269. margin-right: 6px;
  270. }
  271. </style>