浏览代码

登录页面增加

Zhangbw 2 月之前
父节点
当前提交
cae5738e85
共有 5 个文件被更改,包括 241 次插入30 次删除
  1. 18 1
      src/App.vue
  2. 60 11
      src/CustomerService.vue
  3. 149 0
      src/Login.vue
  4. 6 7
      target/classes/META-INF/mps/autoMapper
  5. 8 11
      target/classes/META-INF/mps/mappers

+ 18 - 1
src/App.vue

@@ -1,7 +1,24 @@
 <template>
-  <CustomerService />
+  <Login v-if="!isLoggedIn" @login-success="handleLoginSuccess" />
+  <CustomerService v-else />
 </template>
 
 <script setup>
+import { ref, onMounted } from 'vue'
+import Login from './Login.vue'
 import CustomerService from './CustomerService.vue'
+
+const isLoggedIn = ref(false)
+
+onMounted(() => {
+  // 检查是否已登录
+  const token = localStorage.getItem('talk_token')
+  if (token) {
+    isLoggedIn.value = true
+  }
+})
+
+const handleLoginSuccess = () => {
+  isLoggedIn.value = true
+}
 </script>

+ 60 - 11
src/CustomerService.vue

@@ -6,6 +6,7 @@
           ≡ {{ sidebarVisible ? '收起配置' : '展开配置' }}
         </div>
         <h1 class="title">智能客服</h1>
+        <div class="logout-btn" @click="handleLogout">退出登录</div>
       </div>
 
       <div class="content-wrapper">
@@ -190,6 +191,16 @@ import { ArrowUp, ArrowDown, Microphone, PhoneFilled, ChatDotRound, Mute } from
 import { useVoiceRecognition } from './composables/useVoiceRecognition.js'
 import { ElMessage } from 'element-plus'
 
+// 获取请求头(包含token)
+const getHeaders = () => {
+  const token = localStorage.getItem('talk_token')
+  return {
+    'Content-Type': 'application/json',
+    'Authorization': token ? `Bearer ${token}` : '',
+    'clientid': 'talk-web'
+  }
+}
+
 const showChat = ref(false)
 const isTextInput = ref(false)
 const isMicMuted = ref(false)
@@ -285,9 +296,7 @@ watch(currentTranscription, async (newVal, oldVal) => {
         const selectedAgentData = agents.value.find(a => a.id === selectedAgent.value)
         const response = await fetch('http://localhost:8080/talk/message', {
           method: 'POST',
-          headers: {
-            'Content-Type': 'application/json'
-          },
+          headers: getHeaders(),
           body: JSON.stringify({
             message: newContent,
             agentId: selectedAgent.value,
@@ -341,9 +350,7 @@ watch(showChat, async (newVal) => {
       try {
         await fetch('http://localhost:8080/talk/agent/' + selectedAgent.value, {
           method: 'PUT',
-          headers: {
-            'Content-Type': 'application/json'
-          },
+          headers: getHeaders(),
           body: JSON.stringify({
             id: selectedAgent.value,
             status: '0'
@@ -472,7 +479,9 @@ const selectedAgent = ref(null)
 // 获取发言人字典列表
 const fetchTtsVcnList = async () => {
   try {
-    const response = await fetch('http://localhost:8080/talk/dict/ttsVcn')
+    const response = await fetch('http://localhost:8080/talk/dict/ttsVcn', {
+      headers: getHeaders()
+    })
     const data = await response.json()
     ttsVcnList.value = data || []
   } catch (error) {
@@ -483,7 +492,9 @@ const fetchTtsVcnList = async () => {
 // 获取客服列表
 const fetchAgents = async () => {
   try {
-    const response = await fetch('http://localhost:8080/talk/agent/list?status=0')
+    const response = await fetch('http://localhost:8080/talk/agent/list?status=0', {
+      headers: getHeaders()
+    })
     const result = await response.json()
     if (result.code === 200 && result.rows) {
       agents.value = result.rows.map(agent => ({
@@ -509,6 +520,25 @@ const fetchAgents = async () => {
   }
 }
 
+// 退出登录
+const handleLogout = async () => {
+  try {
+    // 调用后端退出登录接口
+    await fetch('http://localhost:8080/talk/auth/logout', {
+      method: 'POST',
+      headers: getHeaders()
+    })
+  } catch (error) {
+    console.error('退出登录失败:', error)
+  } finally {
+    // 清除本地存储
+    localStorage.removeItem('talk_token')
+    localStorage.removeItem('talk_user')
+    // 刷新页面返回登录页
+    window.location.reload()
+  }
+}
+
 // 组件挂载时获取客服列表和发言人字典
 onMounted(() => {
   fetchAgents()
@@ -537,9 +567,7 @@ const startChat = async () => {
     if (selectedAgentData) {
       const response = await fetch('http://localhost:8080/talk/agent/' + selectedAgent.value, {
         method: 'PUT',
-        headers: {
-          'Content-Type': 'application/json'
-        },
+        headers: getHeaders(),
         body: JSON.stringify({
           id: selectedAgent.value,
           ttsSpeed: config.value.speed,
@@ -719,6 +747,27 @@ const handleWheel = (e) => {
   margin: 0;
 }
 
+.logout-btn {
+  position: absolute;
+  right: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+  padding: 6px 16px;
+  background: white;
+  color: #ef4444;
+  border: 1px solid #ef4444;
+  border-radius: 20px;
+  font-size: 14px;
+  cursor: pointer;
+  user-select: none;
+  transition: all 0.2s;
+}
+
+.logout-btn:hover {
+  background: #ef4444;
+  color: white;
+}
+
 .content-wrapper {
   position: relative;
   background: white;

+ 149 - 0
src/Login.vue

@@ -0,0 +1,149 @@
+<template>
+  <div class="login-container">
+    <div class="login-box">
+      <h2>智能客服系统登录</h2>
+      <form @submit.prevent="handleLogin">
+        <div class="form-group">
+          <input
+            v-model="loginForm.username"
+            type="text"
+            placeholder="用户名"
+            required
+          />
+        </div>
+        <div class="form-group">
+          <input
+            v-model="loginForm.password"
+            type="password"
+            placeholder="密码"
+            required
+          />
+        </div>
+        <div v-if="errorMessage" class="error-message">
+          {{ errorMessage }}
+        </div>
+        <button type="submit" :disabled="loading">
+          {{ loading ? '登录中...' : '登录' }}
+        </button>
+      </form>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+
+const emit = defineEmits(['login-success'])
+
+const loginForm = ref({
+  username: '',
+  password: ''
+})
+
+const loading = ref(false)
+const errorMessage = ref('')
+
+const handleLogin = async () => {
+  loading.value = true
+  errorMessage.value = ''
+
+  try {
+    const response = await fetch('http://localhost:8080/talk/auth/login', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json'
+      },
+      body: JSON.stringify(loginForm.value)
+    })
+
+    const result = await response.json()
+
+    if (result.code === 200) {
+      // 保存token到localStorage
+      localStorage.setItem('talk_token', result.data.token)
+      localStorage.setItem('talk_user', JSON.stringify(result.data.user))
+
+      // 通知父组件登录成功
+      emit('login-success', result.data)
+    } else {
+      errorMessage.value = result.msg || '登录失败'
+    }
+  } catch (error) {
+    console.error('登录错误:', error)
+    errorMessage.value = '网络错误,请稍后重试'
+  } finally {
+    loading.value = false
+  }
+}
+</script>
+
+<style scoped>
+.login-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  min-height: 100vh;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+.login-box {
+  background: white;
+  padding: 40px;
+  border-radius: 10px;
+  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
+  width: 100%;
+  max-width: 400px;
+}
+
+h2 {
+  text-align: center;
+  margin-bottom: 30px;
+  color: #333;
+}
+
+.form-group {
+  margin-bottom: 20px;
+}
+
+input {
+  width: 100%;
+  padding: 12px;
+  border: 1px solid #ddd;
+  border-radius: 5px;
+  font-size: 14px;
+  box-sizing: border-box;
+}
+
+input:focus {
+  outline: none;
+  border-color: #667eea;
+}
+
+button {
+  width: 100%;
+  padding: 12px;
+  background: #667eea;
+  color: white;
+  border: none;
+  border-radius: 5px;
+  font-size: 16px;
+  cursor: pointer;
+  transition: background 0.3s;
+}
+
+button:hover:not(:disabled) {
+  background: #5568d3;
+}
+
+button:disabled {
+  background: #ccc;
+  cursor: not-allowed;
+}
+
+.error-message {
+  color: #f56c6c;
+  font-size: 14px;
+  margin-bottom: 15px;
+  text-align: center;
+}
+</style>

+ 6 - 7
target/classes/META-INF/mps/autoMapper

@@ -1,7 +1,6 @@
-org.dromara.workflow.domain.vo.FlowCategoryVo
-org.dromara.workflow.domain.vo.TestLeaveVo
-org.dromara.web.domain.vo.TenantListVo
-org.dromara.workflow.domain.bo.TestLeaveBo
-org.dromara.workflow.domain.vo.FlowSpelVo
-org.dromara.workflow.domain.bo.FlowSpelBo
-org.dromara.workflow.domain.bo.FlowCategoryBo
+org.dromara.talk.domain.bo.TalkSessionBo
+org.dromara.talk.domain.vo.TalkAgentVo
+org.dromara.talk.domain.vo.TalkSessionVo
+org.dromara.talk.domain.vo.TalkUserVo
+org.dromara.talk.domain.bo.TalkAgentBo
+org.dromara.talk.domain.bo.TalkUserBo

+ 8 - 11
target/classes/META-INF/mps/mappers

@@ -1,11 +1,8 @@
-org.dromara.web.domain.vo.TenantListVoToSysTenantVoMapper
-org.dromara.workflow.domain.FlowSpelToFlowSpelVoMapper
-org.dromara.workflow.domain.vo.FlowCategoryVoToFlowCategoryMapper
-org.dromara.workflow.domain.bo.FlowCategoryBoToFlowCategoryMapper
-org.dromara.workflow.domain.vo.FlowSpelVoToFlowSpelMapper
-org.dromara.workflow.domain.bo.FlowSpelBoToFlowSpelMapper
-org.dromara.workflow.domain.bo.TestLeaveBoToTestLeaveMapper
-org.dromara.system.domain.vo.SysTenantVoToTenantListVoMapper
-org.dromara.workflow.domain.vo.TestLeaveVoToTestLeaveMapper
-org.dromara.workflow.domain.TestLeaveToTestLeaveVoMapper
-org.dromara.workflow.domain.FlowCategoryToFlowCategoryVoMapper
+org.dromara.talk.domain.TalkUserToTalkUserVoMapper
+org.dromara.talk.domain.vo.TalkUserVoToTalkUserMapper__3
+org.dromara.talk.domain.TalkUserToTalkUserVoMapper__4
+org.dromara.talk.domain.bo.TalkUserBoToTalkUserMapper__4
+org.dromara.talk.domain.TalkUserToTalkUserVoMapper__3
+org.dromara.talk.domain.vo.TalkUserVoToTalkUserMapper
+org.dromara.talk.domain.bo.TalkUserBoToTalkUserMapper
+org.dromara.talk.domain.vo.TalkUserVoToTalkUserMapper__4