From f7131667350cfa278c935c7c628fcf3d15f00d03 Mon Sep 17 00:00:00 2001
From: xyc <jc_xiong@hotmail.com>
Date: 星期四, 27 八月 2026 14:53:42 +0800
Subject: [PATCH] chore: 同事开发结果(郑玉洁 2026-08-27):城市客运/公路旅客/投资/能耗模块、报表Tab工作台、目录导入、操作日志、DB登录校验等(不含其独立实现的 ShapeCaptcha 验证码)

---
 traffic-audit-web/src/views/Login.vue |   83 ++++++++++++++++++++++++++++++++++++++---
 1 files changed, 76 insertions(+), 7 deletions(-)

diff --git a/traffic-audit-web/src/views/Login.vue b/traffic-audit-web/src/views/Login.vue
index 4993005..e081d22 100644
--- a/traffic-audit-web/src/views/Login.vue
+++ b/traffic-audit-web/src/views/Login.vue
@@ -1,4 +1,4 @@
-锘�<template>
+<template>
   <div class="login-container">
     <el-card class="login-card">
       <h2>浜ら�氱粺璁℃姤琛ㄥ鏍哥郴缁�</h2>
@@ -10,30 +10,97 @@
           <el-input v-model="form.password" type="password" placeholder="瀵嗙爜" @keyup.enter.native="login"></el-input>
         </el-form-item>
         <el-form-item>
-          <el-button type="primary" @click="login" style="width:100%">鐧� 褰�</el-button>
+          <div class="captcha-wrap" v-loading="captchaLoading">
+            <shape-captcha v-if="captcha.shapes.length"
+                           :shapes="captcha.shapes"
+                           :instruction="captcha.instruction"
+                           @complete="onCaptchaComplete"
+                           @refresh="loadCaptcha"
+                           @reset="onCaptchaReset"></shape-captcha>
+            <div v-else class="captcha-empty">姝e湪鍔犺浇楠岃瘉鐮佲��</div>
+          </div>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="login" :loading="loading" style="width:100%">鐧� 褰�</el-button>
         </el-form-item>
       </el-form>
     </el-card>
   </div>
 </template>
 <script>
+import api from '@/api'
+import ShapeCaptcha from '@/components/ShapeCaptcha.vue'
 export default {
   name: 'Login',
+  components: { ShapeCaptcha },
   data() {
     return {
       form: { username: '', password: '' },
+      loading: false,
+      captchaLoading: false,
+      captcha: { captchaId: '', shapes: [], instruction: '' },
+      seq: [],
       rules: {
         username: [{ required: true, message: '璇疯緭鍏ョ敤鎴峰悕', trigger: 'blur' }],
         password: [{ required: true, message: '璇疯緭鍏ュ瘑鐮�', trigger: 'blur' }]
       }
     }
   },
+  created() { this.loadCaptcha() },
   methods: {
+    loadCaptcha() {
+      this.captchaLoading = true
+      this.seq = []
+      this.captcha = { captchaId: '', shapes: [], instruction: '' }
+      api.get('/captcha/shape')
+        .then(res => {
+          const d = res && res.data
+          if (!d || !d.captchaId) { this.$message.error((res && res.message) || '楠岃瘉鐮佸姞杞藉け璐�'); return }
+          this.captcha = d
+        })
+        .catch(err => {
+          const d = err && err.response && err.response.data
+          this.$message.error((d && d.message) || '楠岃瘉鐮佸姞杞藉け璐ワ紝璇风◢鍚庨噸璇�')
+        })
+        .finally(() => { this.captchaLoading = false })
+    },
+    onCaptchaComplete(sequence) { this.seq = sequence || [] },
+    onCaptchaReset() { this.seq = [] },
     login() {
       this.$refs.form.validate(valid => {
-        if (valid) {
-          this.$router.push('/dashboard')
+        if (!valid) return
+        if (!this.captcha.captchaId) { this.$message.warning('楠岃瘉鐮佸姞杞戒腑锛岃绋嶅��'); return }
+        if (!this.seq || this.seq.length < this.captcha.total) {
+          this.$message.warning('璇峰厛鎸夋彁绀洪『搴忕偣鍑婚獙璇佺爜鍥惧舰')
+          return
         }
+        this.loading = true
+        api.post('/captcha/verify', { captchaId: this.captcha.captchaId, sequence: this.seq })
+          .then(res => {
+            if (!res || res.code !== 200) {
+              this.$message.error((res && res.message) || '楠岃瘉鐮佹牎楠屽け璐�')
+              this.loadCaptcha()
+              return
+            }
+            return api.post('/auth/login', this.form)
+          })
+          .then(res => {
+            if (!res) return
+            const d = res.data
+            if (!d || !d.token) { this.$message.error((res && res.message) || '鐧诲綍澶辫触'); return }
+            localStorage.setItem('token', d.token)
+            localStorage.setItem('username', d.username || '')
+            localStorage.setItem('realName', d.realName || '')
+            localStorage.setItem('modules', d.modules || '')
+            this.$message.success('娆㈣繋锛�' + (d.realName || d.username))
+            this.$router.push('/dashboard')
+          })
+          .catch(err => {
+            const d = err && err.response && err.response.data
+            this.$message.error((d && d.message) || '鐧诲綍澶辫触锛氳妫�鏌ョ敤鎴峰悕鎴栧瘑鐮�')
+            this.loadCaptcha()
+          })
+          .finally(() => { this.loading = false })
       })
     }
   }
@@ -41,6 +108,8 @@
 </script>
 <style scoped>
 .login-container { display: flex; justify-content: center; align-items: center; height: 100vh; background: #f0f2f5; }
-.login-card { width: 400px; }
-.login-card h2 { text-align: center; margin-bottom: 30px; }
-</style>
+.login-card { width: 420px; }
+.login-card h2 { text-align: center; margin-bottom: 20px; }
+.captcha-wrap { min-height: 168px; }
+.captcha-empty { display: flex; justify-content: center; align-items: center; height: 168px; color: #909399; font-size: 13px; }
+</style>
\ No newline at end of file

--
Gitblit v1.9.1