优化项目,进行响应式处理
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/ChatDots.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue</title>
|
||||
<title>AI 智能面试官 | 职位技能对话与复盘平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
1
public/ChatDots.svg
Normal file
1
public/ChatDots.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1760077724598" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6812" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M864 192H160A64.073143 64.073143 0 0 0 96 256V891.245714a63.890286 63.890286 0 0 0 105.179429 49.005715l127.049142-106.788572 535.771429-1.536a64.073143 64.073143 0 0 0 64-64V256a64.036571 64.036571 0 0 0-64-64z" fill="#FF861C" p-id="6813"></path><path d="M293.302857 551.899429a48.018286 48.018286 0 1 0 53.394286-79.835429 48.018286 48.018286 0 0 0-53.394286 79.835429zM485.302857 551.899429a48.018286 48.018286 0 1 0 53.394286-79.835429 48.018286 48.018286 0 0 0-53.394286 79.835429zM677.302857 551.899429a48.018286 48.018286 0 1 0 53.394286-79.835429 48.018286 48.018286 0 0 0-53.394286 79.835429z" fill="#FFD8B4" p-id="6814"></path></svg>
|
||||
|
After Width: | Height: | Size: 976 B |
1
public/chat-dot-square.svg
Normal file
1
public/chat-dot-square.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1760077651596" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5557" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M274.005333 736h526.037334a64.768 64.768 0 0 0 64-64V256a64.810667 64.810667 0 0 0-64-64H224A64.810667 64.810667 0 0 0 160 256v571.008l113.962667-91.008z m22.016 64l-148.053333 118.016c-10.666667 8.021333-21.802667 9.173333-33.450667 3.498667-11.690667-5.674667-17.877333-15.189333-18.517333-28.501334V256c0.64-36.010667 13.141333-66.176 37.504-90.496 24.32-24.32 54.528-36.821333 90.496-37.504h576c36.010667 0.64 66.176 13.141333 90.496 37.504 24.32 24.32 36.821333 54.528 37.504 90.496v416c-0.64 36.010667-13.141333 66.176-37.504 90.496-24.32 24.32-54.485333 36.821333-90.496 37.504H295.978667zM512 498.986667a49.493333 49.493333 0 0 1-50.986667-50.986667A49.493333 49.493333 0 0 1 512 397.013333a49.493333 49.493333 0 0 1 50.986667 50.986667A49.493333 49.493333 0 0 1 512 498.986667z m192 0a49.493333 49.493333 0 0 1-50.986667-50.986667A49.493333 49.493333 0 0 1 704 397.013333a49.493333 49.493333 0 0 1 50.986667 50.986667 49.493333 49.493333 0 0 1-50.986667 50.986667z m-384 0A49.493333 49.493333 0 0 1 268.970667 448 49.493333 49.493333 0 0 1 320 397.013333 49.493333 49.493333 0 0 1 370.986667 448 49.493333 49.493333 0 0 1 320 498.986667z" fill="#08080A" fill-opacity=".96" p-id="5558"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
135
src/ard.vue
135
src/ard.vue
@@ -1,135 +0,0 @@
|
||||
<template>
|
||||
<div class="dashboard-container">
|
||||
<!-- 欢迎横幅 -->
|
||||
<el-card shadow="never" class="welcome-banner">
|
||||
<div class="welcome-content">
|
||||
<div class="welcome-text">
|
||||
<h2>欢迎回来!</h2>
|
||||
<p>准备好开始您的下一次模拟面试了吗?在这里管理您的题库,不断提升面试技巧。</p>
|
||||
</div>
|
||||
<img src="/src/assets/dashboard-hero.svg" alt="仪表盘插图" class="welcome-illustration" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 功能导航 -->
|
||||
<div class="feature-grid">
|
||||
<router-link to="/interview" class="feature-card-link">
|
||||
<el-card shadow="hover" class="feature-card">
|
||||
<div class="card-content">
|
||||
<el-icon class="card-icon" style="background-color: #ecf5ff; color: #409eff;"><ChatLineRound /></el-icon>
|
||||
<div class="text-content">
|
||||
<h3>开始模拟面试</h3>
|
||||
<p>上传简历,与AI进行实战演练</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</router-link>
|
||||
|
||||
<router-link to="/question-bank" class="feature-card-link">
|
||||
<el-card shadow="hover" class="feature-card">
|
||||
<div class="card-content">
|
||||
<el-icon class="card-icon" style="background-color: #f0f9eb; color: #67c23a;"><MessageBox /></el-icon>
|
||||
<div class="text-content">
|
||||
<h3>题库管理</h3>
|
||||
<p>新增、编辑和导入您的面试题库</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</router-link>
|
||||
|
||||
<router-link to="/history" class="feature-card-link">
|
||||
<el-card shadow="hover" class="feature-card">
|
||||
<div class="card-content">
|
||||
<el-icon class="card-icon" style="background-color: #fdf6ec; color: #e6a23c;"><Finished /></el-icon>
|
||||
<div class="text-content">
|
||||
<h3>面试历史</h3>
|
||||
<p>查看过往的面试记录与AI复盘报告</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 导入Element Plus图标
|
||||
import { ChatLineRound, MessageBox, Finished } from '@element-plus/icons-vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 仪表盘容器 */
|
||||
.dashboard-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 欢迎横幅 */
|
||||
.welcome-banner {
|
||||
border: none;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.welcome-text h2 {
|
||||
font-size: 1.8em;
|
||||
margin-top: 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.welcome-text p {
|
||||
color: #606266;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.welcome-illustration {
|
||||
width: 200px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* 功能网格布局 */
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.feature-card-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.feature-card .card-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 32px;
|
||||
padding: 15px;
|
||||
border-radius: 50%;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.text-content h3 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #303133;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.text-content p {
|
||||
margin: 0;
|
||||
color: #909399;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,25 +1,18 @@
|
||||
<template>
|
||||
<!-- 整体后台布局容器 -->
|
||||
<el-container class="layout-container">
|
||||
<!-- 侧边栏 -->
|
||||
<el-aside width="220px" class="sidebar">
|
||||
<!-- Logo区域 -->
|
||||
|
||||
<el-aside v-if="!isMobile" width="220px" class="sidebar">
|
||||
<div class="logo-container">
|
||||
<el-icon class="logo-icon"><ChatDotSquare /></el-icon>
|
||||
<span class="logo-title">AI面试官</span>
|
||||
</div>
|
||||
<!--
|
||||
el-scrollbar: Element Plus的滚动条组件。
|
||||
通过CSS设置其高度为100%,它会自动在内容溢出时显示滚动条,
|
||||
否则滚动条不显示,解决了您提出的问题。
|
||||
-->
|
||||
<el-scrollbar style="height: calc(100% - 60px);">
|
||||
<el-scrollbar style="height: calc(100% - 60px);">
|
||||
<el-menu
|
||||
:default-active="$route.path"
|
||||
background-color="#0a192f"
|
||||
text-color="#8892b0"
|
||||
active-text-color="#64ffda"
|
||||
:router="true"
|
||||
:default-active="route.path"
|
||||
background-color="#0a192f"
|
||||
text-color="#8892b0"
|
||||
active-text-color="#64ffda"
|
||||
:router="true"
|
||||
>
|
||||
<el-menu-item index="/">
|
||||
<el-icon><House /></el-icon>
|
||||
@@ -49,9 +42,60 @@
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
|
||||
<!-- 右侧主内容区 -->
|
||||
<el-drawer
|
||||
v-if="isMobile"
|
||||
v-model="drawerVisible"
|
||||
direction="ltr"
|
||||
:with-header="false"
|
||||
size="220px"
|
||||
custom-class="mobile-sidebar-drawer"
|
||||
>
|
||||
<div class="logo-container">
|
||||
<el-icon class="logo-icon"><ChatDotSquare /></el-icon>
|
||||
<span class="logo-title">AI面试官</span>
|
||||
</div>
|
||||
<el-scrollbar style="height: calc(100% - 60px);">
|
||||
<el-menu
|
||||
:default-active="route.path"
|
||||
background-color="#0a192f"
|
||||
text-color="#8892b0"
|
||||
active-text-color="#64ffda"
|
||||
:router="true"
|
||||
@select="handleMobileMenuSelect"
|
||||
>
|
||||
<el-menu-item index="/">
|
||||
<el-icon><House /></el-icon>
|
||||
<span>仪表盘</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/home">
|
||||
<el-icon><ChatLineRound /></el-icon>
|
||||
<span>模拟面试</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/question-bank">
|
||||
<el-icon><MessageBox /></el-icon>
|
||||
<span>题库管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/question-category">
|
||||
<el-icon><MessageBox /></el-icon>
|
||||
<span>题库分类</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/history">
|
||||
<el-icon><Finished /></el-icon>
|
||||
<span>会话历史</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/answer-record">
|
||||
<el-icon><List /></el-icon>
|
||||
<span>答题记录</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</el-drawer>
|
||||
|
||||
<el-container>
|
||||
<el-header class="header">
|
||||
<el-icon v-if="isMobile" class="menu-toggle-icon" @click="drawerVisible = true" size="24">
|
||||
<Fold />
|
||||
</el-icon>
|
||||
<div class="header-title">欢迎使用AI模拟面试平台</div>
|
||||
</el-header>
|
||||
<el-main class="main-content">
|
||||
@@ -66,7 +110,44 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {House, ChatLineRound, MessageBox, ChatDotSquare, Finished, List} from '@element-plus/icons-vue';
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import {
|
||||
House, ChatLineRound, MessageBox, ChatDotSquare, Finished, List, Fold
|
||||
} from '@element-plus/icons-vue';
|
||||
|
||||
// 引入 useRoute 实例,用于 el-menu 的 default-active 绑定
|
||||
const route = useRoute();
|
||||
|
||||
// 侧边栏抽屉状态(仅移动端使用)
|
||||
const drawerVisible = ref(false);
|
||||
|
||||
// 响应式判断是否为移动端
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
const MOBILE_WIDTH = 768; // 手机和平板的分界线
|
||||
|
||||
const isMobile = computed(() => windowWidth.value <= MOBILE_WIDTH);
|
||||
|
||||
const handleResize = () => {
|
||||
windowWidth.value = window.innerWidth;
|
||||
// 如果窗口宽度变大,关闭抽屉(从移动端切换回桌面端时)
|
||||
if (!isMobile.value) {
|
||||
drawerVisible.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
// 移动端菜单选择后关闭抽屉
|
||||
const handleMobileMenuSelect = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -74,9 +155,9 @@ import {House, ChatLineRound, MessageBox, ChatDotSquare, Finished, List} from '@
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* 更新后的侧边栏样式 */
|
||||
/* 侧边栏样式 (仅桌面端可见,移动端使用 drawer 样式覆盖) */
|
||||
.sidebar {
|
||||
background-color: #0a192f; /* 更深、更现代的科技蓝 */
|
||||
background-color: #0a192f;
|
||||
transition: width 0.28s;
|
||||
}
|
||||
|
||||
@@ -93,7 +174,7 @@ import {House, ChatLineRound, MessageBox, ChatDotSquare, Finished, List} from '@
|
||||
|
||||
.logo-icon {
|
||||
margin-right: 12px;
|
||||
color: #64ffda; /* 高亮颜色 */
|
||||
color: #64ffda;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
@@ -102,14 +183,27 @@ import {House, ChatLineRound, MessageBox, ChatDotSquare, Finished, List} from '@
|
||||
|
||||
/* 菜单项激活时的样式 */
|
||||
.el-menu-item.is-active {
|
||||
background-color: #112240 !important; /* 深色背景 */
|
||||
border-left: 3px solid #64ffda; /* 左侧高亮条 */
|
||||
background-color: #112240 !important;
|
||||
border-left: 3px solid #64ffda;
|
||||
}
|
||||
|
||||
.el-menu-item:hover {
|
||||
background-color: #112240; /* 悬浮背景色 */
|
||||
background-color: #112240;
|
||||
}
|
||||
|
||||
/* ---------------- 移动端抽屉样式优化 ---------------- */
|
||||
/* 使用 :deep() 穿透作用域,修改 el-drawer 内部样式 */
|
||||
:deep(.mobile-sidebar-drawer) .el-drawer__body {
|
||||
padding: 0;
|
||||
background-color: #0a192f; /* 确保抽屉背景色与侧边栏一致 */
|
||||
}
|
||||
:deep(.mobile-sidebar-drawer) .el-drawer__wrapper {
|
||||
/* 增加一个半透明的蒙层 */
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
/* ---------------- 移动端抽屉样式优化 ---------------- */
|
||||
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -118,6 +212,13 @@ import {House, ChatLineRound, MessageBox, ChatDotSquare, Finished, List} from '@
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* 移动端菜单切换按钮样式 */
|
||||
.menu-toggle-icon {
|
||||
margin-right: 15px;
|
||||
cursor: pointer;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.1em;
|
||||
color: #303133;
|
||||
@@ -128,6 +229,7 @@ import {House, ChatLineRound, MessageBox, ChatDotSquare, Finished, List} from '@
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
/* 过渡动画保持不变 */
|
||||
.fade-transform-enter-active,
|
||||
.fade-transform-leave-active {
|
||||
transition: all 0.5s;
|
||||
|
||||
135
src/rd.vue
135
src/rd.vue
@@ -1,135 +0,0 @@
|
||||
<template>
|
||||
<div class="dashboard-container">
|
||||
<!-- 欢迎横幅 -->
|
||||
<el-card shadow="never" class="welcome-banner">
|
||||
<div class="welcome-content">
|
||||
<div class="welcome-text">
|
||||
<h2>欢迎回来!</h2>
|
||||
<p>准备好开始您的下一次模拟面试了吗?在这里管理您的题库,不断提升面试技巧。</p>
|
||||
</div>
|
||||
<img src="/src/assets/dashboard-hero.svg" alt="仪表盘插图" class="welcome-illustration" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 功能导航 -->
|
||||
<div class="feature-grid">
|
||||
<router-link to="/interview" class="feature-card-link">
|
||||
<el-card shadow="hover" class="feature-card">
|
||||
<div class="card-content">
|
||||
<el-icon class="card-icon" style="background-color: #ecf5ff; color: #409eff;"><ChatLineRound /></el-icon>
|
||||
<div class="text-content">
|
||||
<h3>开始模拟面试</h3>
|
||||
<p>上传简历,与AI进行实战演练</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</router-link>
|
||||
|
||||
<router-link to="/question-bank" class="feature-card-link">
|
||||
<el-card shadow="hover" class="feature-card">
|
||||
<div class="card-content">
|
||||
<el-icon class="card-icon" style="background-color: #f0f9eb; color: #67c23a;"><MessageBox /></el-icon>
|
||||
<div class="text-content">
|
||||
<h3>题库管理</h3>
|
||||
<p>新增、编辑和导入您的面试题库</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</router-link>
|
||||
|
||||
<router-link to="/history" class="feature-card-link">
|
||||
<el-card shadow="hover" class="feature-card">
|
||||
<div class="card-content">
|
||||
<el-icon class="card-icon" style="background-color: #fdf6ec; color: #e6a23c;"><Finished /></el-icon>
|
||||
<div class="text-content">
|
||||
<h3>面试历史</h3>
|
||||
<p>查看过往的面试记录与AI复盘报告</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 导入Element Plus图标
|
||||
import { ChatLineRound, MessageBox, Finished } from '@element-plus/icons-vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 仪表盘容器 */
|
||||
.dashboard-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 欢迎横幅 */
|
||||
.welcome-banner {
|
||||
border: none;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.welcome-text h2 {
|
||||
font-size: 1.8em;
|
||||
margin-top: 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.welcome-text p {
|
||||
color: #606266;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.welcome-illustration {
|
||||
width: 200px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* 功能网格布局 */
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.feature-card-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.feature-card .card-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 32px;
|
||||
padding: 15px;
|
||||
border-radius: 50%;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.text-content h3 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #303133;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.text-content p {
|
||||
margin: 0;
|
||||
color: #909399;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,102 +1,398 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { pageList } from '@/api/question-progress'
|
||||
<template>
|
||||
<div class="answer-record-container">
|
||||
<el-card class="query-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>答题记录查询</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :inline="true" class="search-form">
|
||||
<el-form-item>
|
||||
<el-input placeholder="请输入需要查询的题目" v-model="searchContent" style="width: 240px" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="fetchData" icon="Search" :loading="isLoading">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card class="data-card" shadow="hover">
|
||||
<div v-if="!isMobile" class="desktop-table-view">
|
||||
<el-table :data="tableData" style="width: 100%" border :max-height="tableMaxHeight" v-loading="isLoading" empty-text="暂无答题记录">
|
||||
<el-table-column align="center" prop="questionContent" label="问题" width="200" show-overflow-tooltip />
|
||||
<el-table-column align="center" prop="userAnswer" label="用户回答" width="200" show-overflow-tooltip />
|
||||
<el-table-column align="center" prop="score" label="评分" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getScoreTagType(scope.row.score)" effect="dark">{{ scope.row.score }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === 'ACTIVE' ? 'info' : 'success'">
|
||||
{{ scope.row.status === 'ACTIVE' ? '进行中' : '已完成' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" prop="createdTime" label="创建时间" width="180" />
|
||||
<el-table-column align="center" label="AI分析" width="120" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" size="small" @click="showDetails(scope.row)">
|
||||
查看详情
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div v-else class="mobile-card-view" v-loading="isLoading">
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item
|
||||
v-for="(row, index) in tableData"
|
||||
:key="index"
|
||||
:name="index"
|
||||
class="answer-card-item"
|
||||
>
|
||||
<template #title>
|
||||
<div class="card-title-mobile">
|
||||
<el-tag :type="getScoreTagType(row.score)" effect="dark" size="small">{{ row.score }}分</el-tag>
|
||||
<span class="q-content-text">{{ row.questionContent }}</span>
|
||||
<span class="time-text">{{ formatTime(row.createdTime) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="card-detail-content">
|
||||
<h4>用户回答:</h4>
|
||||
<p class="answer-text">{{ row.userAnswer }}</p>
|
||||
|
||||
<el-divider />
|
||||
<h4>AI 分析:</h4>
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="AI 回答">
|
||||
<p class="ai-analysis-text">{{ row.aiAnswer }}</p>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="AI 反馈">
|
||||
<p class="ai-analysis-text">{{ row.feedback }}</p>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="AI 建议">
|
||||
<p class="ai-analysis-text">{{ row.suggestions }}</p>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
<p v-if="!tableData.length && !isLoading" class="empty-data-tip">暂无答题记录</p>
|
||||
</div>
|
||||
|
||||
<el-pagination class="pagination-container"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:background="true"
|
||||
:layout="paginationLayout"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="`AI 分析详情 - 评分: ${currentRow.score}`" width="600px" custom-class="detail-dialog">
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="问题与回答">
|
||||
<div class="detail-section">
|
||||
<h4 class="detail-title">问题:</h4>
|
||||
<p>{{ currentRow.questionContent }}</p>
|
||||
</div>
|
||||
<el-divider />
|
||||
<div class="detail-section">
|
||||
<h4 class="detail-title">用户回答:</h4>
|
||||
<p>{{ currentRow.userAnswer }}</p>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="AI 回答">
|
||||
<p class="analysis-content">{{ currentRow.aiAnswer }}</p>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="AI 反馈">
|
||||
<p class="analysis-content">{{ currentRow.feedback }}</p>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="AI 建议">
|
||||
<p class="analysis-content">{{ currentRow.suggestions }}</p>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, computed, onBeforeUnmount } from "vue";
|
||||
import { pageList } from '@/api/question-progress' // 确保路径正确
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
|
||||
// --- 响应式状态 ---
|
||||
const tableData = ref([])
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const size = ref('default')
|
||||
const background = ref(false)
|
||||
const disabled = ref(false)
|
||||
const handleSizeChange = (val) => {
|
||||
console.log(`${val} items per page`)
|
||||
pageSize.value = val
|
||||
fetchData()
|
||||
}
|
||||
const total = ref(0)
|
||||
const handleCurrentChange = (val) => {
|
||||
console.log(`current page: ${val}`)
|
||||
currentPage.value = val
|
||||
fetchData()
|
||||
|
||||
}
|
||||
const searchContent = ref('')
|
||||
const isLoading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const currentRow = ref({})
|
||||
const activeNames = ref([0])
|
||||
|
||||
// --- 响应式断点控制 ---
|
||||
const windowWidth = ref(window.innerWidth)
|
||||
const MOBILE_WIDTH = 768
|
||||
|
||||
const isMobile = computed(() => windowWidth.value <= MOBILE_WIDTH)
|
||||
const tableMaxHeight = computed(() => {
|
||||
return isMobile.value ? 'auto' : 'calc(100vh - 280px)'
|
||||
})
|
||||
|
||||
const paginationLayout = computed(() => {
|
||||
return isMobile.value ? 'total, prev, pager, next' : 'total, sizes, prev, pager, next, jumper'
|
||||
})
|
||||
|
||||
// 监听窗口大小变化
|
||||
const handleResize = () => {
|
||||
windowWidth.value = window.innerWidth
|
||||
}
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
|
||||
// --- 数据处理和方法 ---
|
||||
|
||||
const fetchData = () => {
|
||||
isLoading.value = true
|
||||
pageList({
|
||||
current: currentPage.value,
|
||||
size: pageSize.value,
|
||||
questionName: searchContent.value
|
||||
}).then(({ data }) => {
|
||||
console.log(data)
|
||||
tableData.value = data.records
|
||||
currentPage.value = data.current
|
||||
total.value = data.total
|
||||
|
||||
}).catch(error => {
|
||||
console.error("Fetch data failed:", error)
|
||||
// 可以在这里添加一个 ElMessage 错误提示
|
||||
}).finally(() => {
|
||||
isLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
currentPage.value = val
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 显示详情弹窗 (PC/平板)
|
||||
const showDetails = (row) => {
|
||||
currentRow.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 根据分数判断 Tag 类型
|
||||
const getScoreTagType = (score) => {
|
||||
if (score >= 80) return 'success'
|
||||
if (score >= 60) return 'warning'
|
||||
return 'danger'
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (time) => {
|
||||
if (!time) return ''
|
||||
const date = new Date(time)
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
fetchData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card>
|
||||
<!-- 上半部分-->
|
||||
<div>
|
||||
<el-card style="width: 100%;margin-bottom: 15px;" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>答题记录</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :inline="true">
|
||||
<el-form-item style="margin: 0;">
|
||||
<el-input placeholder="请输入需要查询的题目" v-model="searchContent" style="width: 240px"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-bottom: 0;margin-left: 15px;">
|
||||
<el-button type="primary" @click="fetchData">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
<!-- 数据部分-->
|
||||
<style scoped>
|
||||
/* 基础容器 */
|
||||
.answer-record-container {
|
||||
padding: 20px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: calc(100vh - 80px);
|
||||
}
|
||||
|
||||
<el-card style="width: 100%;" shadow="hover">
|
||||
<el-table :data="tableData" style="width: 100%" border height="calc(100vh - 260px)">
|
||||
<el-table-column align="center" prop="questionContent" label="问题" />
|
||||
<el-table-column align="center" prop="userAnswer" label="用户回答" />
|
||||
<el-table-column align="center" prop="aiAnswer" label="AI回答" />
|
||||
<el-table-column align="center" prop="feedback" label="AI反馈" />
|
||||
<el-table-column align="center" prop="suggestions" label="AI建议" />
|
||||
<el-table-column align="center" prop="score" label="AI评分" />
|
||||
<el-table-column #default="scope" align="center">
|
||||
<el-tag :type="scope.row.status === 'ACTIVE' ? 'primary' : 'success'">
|
||||
{{ scope.row.status }}
|
||||
</el-tag>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" prop="createdTime" label="创建时间" />
|
||||
</el-table>
|
||||
/* 卡片样式 */
|
||||
.el-card {
|
||||
border-radius: 12px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
<el-pagination class="pagination-container" v-model:current-page="currentPage" v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]" :size="size" :disabled="disabled" :background="background"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange" />
|
||||
</el-card>
|
||||
</el-card>
|
||||
</template>
|
||||
.query-card {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.data-card {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
<style scoped lang="css">
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 1.2em;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------- */
|
||||
/* PC/平板样式 (默认) */
|
||||
/* ----------------------------------------------------- */
|
||||
|
||||
/* 表格内容长文本样式优化 */
|
||||
/* 使用 :deep() 穿透作用域,调整 Element Plus 内部样式 */
|
||||
:deep(.el-table .el-table__cell) {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.detail-dialog .detail-title {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.detail-dialog p {
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.analysis-content {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.empty-data-tip {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------- */
|
||||
/* 响应式优化:手机 (width <= 768px) */
|
||||
/* ----------------------------------------------------- */
|
||||
@media (max-width: 768px) {
|
||||
.answer-record-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 查询表单优化 */
|
||||
.search-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.search-form .el-form-item {
|
||||
margin-right: 0 !important;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.search-form .el-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
.search-form .el-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 移动端卡片列表样式 */
|
||||
.answer-card-item {
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 移动端卡片标题 */
|
||||
.card-title-mobile {
|
||||
display: grid;
|
||||
grid-template-columns: 50px 1fr 80px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 5px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.q-content-text {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.time-text {
|
||||
color: #909399;
|
||||
font-size: 0.8em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 卡片详细内容 */
|
||||
.card-detail-content {
|
||||
padding: 0 10px 10px 10px;
|
||||
}
|
||||
|
||||
.card-detail-content h4 {
|
||||
margin: 10px 0 5px 0;
|
||||
color: #409EFF;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.answer-text, .ai-analysis-text {
|
||||
color: #606266;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* 分页优化 */
|
||||
.pagination-container {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 覆盖 el-collapse 默认边框,使其看起来像卡片 */
|
||||
:deep(.el-collapse) {
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
:deep(.el-collapse-item__header) {
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
padding: 10px;
|
||||
}
|
||||
:deep(.el-collapse-item__wrap) {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -13,7 +13,7 @@
|
||||
<template #content>
|
||||
<div class="header-content">
|
||||
<span class="title">{{ reportData.sessionDetails.candidateName }} 的面试复盘报告</span>
|
||||
<el-tag type="info" size="large">{{
|
||||
<el-tag type="info" size="large" class="report-time-tag">{{
|
||||
new Date(reportData.sessionDetails.createdTime).toLocaleString()
|
||||
}}
|
||||
</el-tag>
|
||||
@@ -23,34 +23,39 @@
|
||||
|
||||
<el-card class="box-card report-summary" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon>
|
||||
<div class="card-header primary-color">
|
||||
<el-icon size="20">
|
||||
<DataAnalysis/>
|
||||
</el-icon>
|
||||
<span>AI 最终评估报告</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="finalReport" class="summary-content">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions :column="isMobile ? 1 : 2" border>
|
||||
<el-descriptions-item label="目标岗位">{{ reportData.sessionDetails.jobRequirements || '未提供' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="面试模式">{{ reportData.sessionDetails.model === 'ai' ? 'AI智能面试' : '本地题库面试' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="综合得分">
|
||||
<el-tag size="medium">{{ finalReport.overallScore }} 分</el-tag>
|
||||
<el-tag size="large" type="success" effect="dark" class="score-tag">{{ finalReport.overallScore }} 分</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="录用建议">
|
||||
<el-tag :type="getRecommendationType(finalReport.hiringRecommendation)" size="medium" effect="dark">
|
||||
<el-tag :type="getRecommendationType(finalReport.hiringRecommendation)" size="large" effect="dark">
|
||||
{{ finalReport.hiringRecommendation }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-divider/>
|
||||
<h4>综合评语</h4>
|
||||
|
||||
<h4 class="section-title">综合评语</h4>
|
||||
<p class="feedback-paragraph">{{ finalReport.overallFeedback }}</p>
|
||||
<h4>技术能力评估</h4>
|
||||
|
||||
<h4 class="section-title">技术能力评估</h4>
|
||||
<ul class="assessment-list">
|
||||
<li v-for="(value, key) in finalReport.technicalAssessment" :key="key">
|
||||
<strong>{{ key }}:</strong> {{ value }}
|
||||
</li>
|
||||
</ul>
|
||||
<h4>改进建议</h4>
|
||||
|
||||
<h4 class="section-title">改进建议</h4>
|
||||
<ol class="suggestions-list">
|
||||
<li v-for="suggestion in finalReport.suggestions" :key="suggestion">{{ suggestion }}</li>
|
||||
</ol>
|
||||
@@ -62,23 +67,27 @@
|
||||
|
||||
<el-card class="box-card question-details" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon>
|
||||
<div class="card-header success-color">
|
||||
<el-icon size="20">
|
||||
<ChatDotRound/>
|
||||
</el-icon>
|
||||
<span>问答详情与逐题评估</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-timeline>
|
||||
<el-timeline class="custom-timeline">
|
||||
<el-timeline-item v-for="(item, index) in reportData.questionDetails" :key="item.questionId"
|
||||
:timestamp="`第 ${index + 1} 题`" placement="top">
|
||||
<el-card class="question-card" shadow="never">
|
||||
<h4>{{ item.questionContent }}</h4>
|
||||
<p class="user-answer"><strong>您的回答:</strong> {{ item.userAnswer }}</p>
|
||||
<el-divider/>
|
||||
:timestamp="`第 ${index + 1} 题 - ${formatTime(item.createdTime)}`" placement="top">
|
||||
<el-card class="question-card" shadow="always">
|
||||
<h4 class="question-content">{{ item.questionContent }}</h4>
|
||||
|
||||
<el-divider content-position="left">用户回答</el-divider>
|
||||
<p class="user-answer-text">{{ item.userAnswer }}</p>
|
||||
|
||||
<el-divider content-position="left">AI 评估</el-divider>
|
||||
<div class="feedback-section">
|
||||
<p><strong>AI 评语:</strong> {{ item.aiFeedback }}</p>
|
||||
<p><strong>AI 建议:</strong> {{ item.suggestions }}</p>
|
||||
|
||||
<div class="score-section">
|
||||
<strong>本题得分:</strong>
|
||||
<el-rate v-model="item.score" :max="5" disabled show-score text-color="#ff9900"
|
||||
@@ -94,7 +103,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onMounted, computed} from 'vue';
|
||||
import {ref, onMounted, computed, onBeforeUnmount} from 'vue';
|
||||
import {useRouter} from 'vue-router';
|
||||
import {getInterviewReportDetail} from '@/api/interview.js';
|
||||
import {DataAnalysis, ChatDotRound} from '@element-plus/icons-vue';
|
||||
@@ -107,14 +116,50 @@ const router = useRouter();
|
||||
const reportData = ref(null);
|
||||
const isLoading = ref(false);
|
||||
|
||||
// --- 响应式断点控制 ---
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
const MOBILE_WIDTH = 768;
|
||||
const isMobile = computed(() => windowWidth.value <= MOBILE_WIDTH);
|
||||
|
||||
const handleResize = () => {
|
||||
windowWidth.value = window.innerWidth;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize);
|
||||
fetchReport();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
// --- 计算属性 ---
|
||||
const finalReport = computed(() => {
|
||||
if (reportData.value && reportData.value.sessionDetails.finalReport) {
|
||||
try {
|
||||
// 检查 finalReport 是否是字符串,如果是则解析
|
||||
return typeof reportData.value.sessionDetails.finalReport === 'string'
|
||||
const reportJson = typeof reportData.value.sessionDetails.finalReport === 'string'
|
||||
? JSON.parse(reportData.value.sessionDetails.finalReport)
|
||||
: reportData.value.sessionDetails.finalReport;
|
||||
|
||||
// 确保 technicalAssessment 和 suggestions 字段存在且为数组/对象
|
||||
if (reportJson.technicalAssessment && typeof reportJson.technicalAssessment === 'string') {
|
||||
try {
|
||||
reportJson.technicalAssessment = JSON.parse(reportJson.technicalAssessment);
|
||||
} catch(e) {
|
||||
console.warn("Technical assessment parsing failed, keeping as string.");
|
||||
}
|
||||
}
|
||||
if (reportJson.suggestions && typeof reportJson.suggestions === 'string') {
|
||||
try {
|
||||
reportJson.suggestions = JSON.parse(reportJson.suggestions);
|
||||
} catch(e) {
|
||||
console.warn("Suggestions parsing failed, keeping as string.");
|
||||
}
|
||||
}
|
||||
return reportJson;
|
||||
|
||||
} catch (e) {
|
||||
console.error('解析最终报告JSON失败:', e);
|
||||
return null;
|
||||
@@ -142,36 +187,43 @@ const fetchReport = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// --- 事件处理 ---
|
||||
// --- 事件处理/工具函数 ---
|
||||
const goBack = () => router.push('/history');
|
||||
|
||||
const getRecommendationType = (rec) => {
|
||||
if (rec === '强烈推荐' || rec === '推荐') return 'success';
|
||||
if (rec === '待考虑') return 'warning';
|
||||
if (rec === '不推荐') return 'danger';
|
||||
if (rec && rec.includes('强烈推荐') || rec.includes('推荐')) return 'success';
|
||||
if (rec && rec.includes('待考虑')) return 'warning';
|
||||
if (rec && rec.includes('不推荐')) return 'danger';
|
||||
return 'info';
|
||||
};
|
||||
|
||||
// --- 生命周期钩子 ---
|
||||
onMounted(() => {
|
||||
fetchReport();
|
||||
});
|
||||
const formatTime = (time) => {
|
||||
if (!time) return '';
|
||||
return new Date(time).toLocaleTimeString('zh-CN', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ----------------------------------------------------- */
|
||||
/* PC/平板样式 (默认) */
|
||||
/* ----------------------------------------------------- */
|
||||
.report-container {
|
||||
padding: 20px;
|
||||
padding: 30px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.report-header {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 25px;
|
||||
background-color: #ffffff;
|
||||
padding: 15px 20px;
|
||||
padding: 20px 30px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #ebeef5;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@@ -182,92 +234,282 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.header-content .title {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
font-size: 1.8em;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.report-time-tag {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.box-card {
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #ebeef5;
|
||||
margin-bottom: 25px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.box-card:hover {
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
font-size: 1.3em;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #303133;
|
||||
padding: 15px 20px;
|
||||
margin: -20px; /* 负边距抵消卡片内边距 */
|
||||
border-top-left-radius: 12px;
|
||||
border-top-right-radius: 12px;
|
||||
}
|
||||
|
||||
.card-header.primary-color {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
.card-header.success-color {
|
||||
background-color: #f0f9eb;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.card-header .el-icon {
|
||||
margin-right: 10px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.summary-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.el-descriptions {
|
||||
margin-bottom: 20px;
|
||||
/* Descriptions 样式优化 */
|
||||
:deep(.el-descriptions__label.is-bordered-label) {
|
||||
background-color: #f5f7fa;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
}
|
||||
.score-tag {
|
||||
font-weight: bold;
|
||||
animation: pulse-score 2s infinite;
|
||||
}
|
||||
@keyframes pulse-score {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.report-summary h4 {
|
||||
margin: 25px 0 10px 0;
|
||||
font-size: 1.1em;
|
||||
.section-title {
|
||||
margin: 30px 0 10px 0;
|
||||
font-size: 1.2em;
|
||||
color: #303133;
|
||||
border-left: 4px solid #409eff;
|
||||
padding-left: 10px;
|
||||
border-left: 5px solid #67c23a;
|
||||
padding-left: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.feedback-paragraph {
|
||||
text-indent: 2em;
|
||||
color: #606266;
|
||||
color: #5a646e;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 20px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.assessment-list, .suggestions-list {
|
||||
padding-left: 20px;
|
||||
list-style: disc;
|
||||
color: #606266;
|
||||
padding-left: 25px;
|
||||
list-style-type: '👉 '; /* 趣味列表符号 */
|
||||
list-style-position: inside;
|
||||
color: #5a646e;
|
||||
}
|
||||
.suggestions-list {
|
||||
list-style-type: '💡 ';
|
||||
}
|
||||
|
||||
.assessment-list li, .suggestions-list li {
|
||||
line-height: 1.8;
|
||||
line-height: 2;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.assessment-list strong, .suggestions-list strong {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
|
||||
/* 问答详情 Timeline */
|
||||
.custom-timeline {
|
||||
padding-left: 10px;
|
||||
}
|
||||
.question-card {
|
||||
margin-top: 10px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 10px;
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
.question-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.user-answer {
|
||||
color: #606266;
|
||||
.question-content {
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.user-answer-text {
|
||||
color: #5a646e;
|
||||
font-style: italic;
|
||||
background-color: #f5f7fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
background-color: #fcfcfc;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
line-height: 1.6;
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.feedback-section {
|
||||
background-color: #fafbfd;
|
||||
background-color: #ffffff;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-top: 15px;
|
||||
border: 1px dashed #dcdfe6;
|
||||
}
|
||||
|
||||
.feedback-section p {
|
||||
line-height: 1.6;
|
||||
color: #5a646e;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.feedback-section strong {
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.score-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
margin-top: 15px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.el-rate {
|
||||
margin-left: 10px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------- */
|
||||
/* 响应式优化:手机/小屏 (width <= 768px) */
|
||||
/* ----------------------------------------------------- */
|
||||
@media (max-width: 768px) {
|
||||
.report-container {
|
||||
padding: 10px; /* 减少内边距 */
|
||||
}
|
||||
|
||||
/* 头部优化 */
|
||||
.report-header {
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
flex-direction: column; /* 垂直堆叠标题和时间 */
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.header-content .title {
|
||||
font-size: 1.3em;
|
||||
margin-bottom: 5px;
|
||||
|
||||
/* 解决 iPhone 14 Pro 标题换行问题 */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 90vw; /* 限制最大宽度,防止溢出 */
|
||||
}
|
||||
|
||||
.report-time-tag {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
/* 卡片头部 */
|
||||
.box-card {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 1.1em;
|
||||
padding: 10px 15px;
|
||||
margin: -15px; /* 适配卡片内边距 */
|
||||
}
|
||||
|
||||
/* 摘要内容 */
|
||||
.summary-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 强制 Descriptions 单列 */
|
||||
:deep(.el-descriptions-item__container) {
|
||||
display: block !important;
|
||||
}
|
||||
:deep(.el-descriptions-item__label.is-bordered-label) {
|
||||
width: 100% !important;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
text-align: left;
|
||||
}
|
||||
:deep(.el-descriptions-item__content) {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 0;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* 评语和列表 */
|
||||
.section-title {
|
||||
font-size: 1.1em;
|
||||
margin: 20px 0 8px 0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.feedback-paragraph {
|
||||
font-size: 0.9em;
|
||||
line-height: 1.6;
|
||||
text-indent: 1.5em;
|
||||
}
|
||||
|
||||
.assessment-list, .suggestions-list {
|
||||
padding-left: 15px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* 问答详情 */
|
||||
.custom-timeline {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
:deep(.el-timeline-item__timestamp) {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.question-content {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.user-answer-text {
|
||||
padding: 10px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.feedback-section {
|
||||
padding: 10px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.score-section {
|
||||
flex-direction: column; /* 垂直堆叠得分标签和星星 */
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.el-rate {
|
||||
margin-left: 0;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -7,10 +7,13 @@
|
||||
<span class="status-text">{{ statusText }}</span>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<el-button v-if="mode !== 'chat' && interviewStatus !== 'COMPLETED'" @click="interviewEnd" size="small">
|
||||
<el-button v-if="mode !== 'chat' && interviewStatus !== 'COMPLETED'" @click="interviewEnd" size="small"
|
||||
class="header-button end-button" type="danger" plain>
|
||||
结束面试
|
||||
</el-button>
|
||||
<el-button @click="$emit('close')" size="small">关闭</el-button>
|
||||
<el-button @click="$router.push('/home')" size="small" class="header-button close-button" type="info" plain>
|
||||
关闭
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,24 +24,38 @@
|
||||
class="message-row"
|
||||
:class="'message-' + message.sender.toLowerCase()"
|
||||
>
|
||||
<el-avatar
|
||||
class="avatar"
|
||||
:style="{ backgroundColor: message.sender === 'AI' ? '#409EFF' : '#67C23A' }"
|
||||
>
|
||||
{{ message.sender === 'AI' ? 'AI' : '我' }}
|
||||
</el-avatar>
|
||||
<template v-if="message.sender === 'AI'">
|
||||
<el-avatar
|
||||
class="avatar"
|
||||
style="background-color: #409EFF; margin-right: 12px;"
|
||||
>AI</el-avatar>
|
||||
|
||||
<div class="message-content">
|
||||
<div class="message-bubble" v-html="formatMessage(message.content)"></div>
|
||||
<div class="message-time">{{ message.createdTime }}</div>
|
||||
</div>
|
||||
<div class="message-content">
|
||||
<div class="message-bubble markdown-body" v-html="formatMessage(message.content)"></div>
|
||||
<div class="message-time">{{ formatTime(message.createdTime) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="message-content">
|
||||
<div class="message-bubble markdown-body" v-html="formatMessage(message.content)"></div>
|
||||
<div class="message-time">{{ formatTime(message.createdTime) }}</div>
|
||||
</div>
|
||||
|
||||
<el-avatar
|
||||
class="avatar"
|
||||
style="background-color: #67C23A; margin-left: 12px;"
|
||||
>我</el-avatar>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="isAiThinking" class="message-row message-ai">
|
||||
<el-avatar class="avatar" style="background-color: #409EFF;">AI</el-avatar>
|
||||
<div v-if="isAiThinking" class="message-row message-ai typing-indicator-row">
|
||||
<el-avatar class="avatar" style="background-color: #409EFF; margin-right: 12px;">AI</el-avatar>
|
||||
<div class="message-content">
|
||||
<div class="message-bubble thinking">
|
||||
<span></span><span></span><span></span>
|
||||
<div class="typing-dot"></div>
|
||||
<div class="typing-dot"></div>
|
||||
<div class="typing-dot"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,9 +66,11 @@
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
v-model="userAnswer"
|
||||
placeholder="在此输入您的回答..."
|
||||
placeholder="在此输入您的回答 (Enter 键发送)..."
|
||||
:disabled="isLoading || interviewStatus === 'COMPLETED'"
|
||||
resize="none"
|
||||
maxlength="1000"
|
||||
show-word-limit
|
||||
class="answer-input"
|
||||
@keypress.enter.prevent="sendMessage"
|
||||
></el-input>
|
||||
@@ -65,6 +84,7 @@
|
||||
:disabled="interviewStatus === 'COMPLETED' || !userAnswer.trim()"
|
||||
class="send-button"
|
||||
>
|
||||
<el-icon><i class="el-icon-s-promotion"></i></el-icon>
|
||||
发送回答
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -78,8 +98,8 @@
|
||||
sub-title="感谢您的参与!您可以查看面试报告或开始新的面试。"
|
||||
>
|
||||
<template #extra>
|
||||
<el-button type="primary" @click="$router.push('/')">返回</el-button>
|
||||
<el-button @click="$router.push('/history')">查看报告</el-button>
|
||||
<el-button type="primary" @click="$router.push('/')">返回主页</el-button>
|
||||
<el-button @click="$router.push('/history')" type="info" plain>查看报告</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
</div>
|
||||
@@ -91,11 +111,12 @@
|
||||
import {ref, onMounted, nextTick, computed} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
// **保持原有接口导入,不对接口逻辑进行修改**
|
||||
import {getMessageListBySessionId} from "@/api/interview-message.js";
|
||||
import {endInterview, getNextQuestion, submitAnswer} from "@/api/interview.js";
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import hljs from 'highlight.js' // 导入 highlight.js
|
||||
import 'highlight.js/styles/github.css' // 或者您喜欢的任何主题
|
||||
import hljs from 'highlight.js'
|
||||
import 'highlight.js/styles/github.css' // 导入您选择的 highlight.js 主题
|
||||
|
||||
const md = new MarkdownIt({
|
||||
html: true,
|
||||
@@ -105,9 +126,10 @@ const md = new MarkdownIt({
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return '<pre class="hljs"><code>' +
|
||||
hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
|
||||
hljs.highlight(str, {language: lang, ignoreIllegals: true}).value +
|
||||
'</code></pre>';
|
||||
} catch (__) {}
|
||||
} catch (__) {
|
||||
}
|
||||
}
|
||||
|
||||
return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';
|
||||
@@ -132,6 +154,7 @@ const questionProgressId = ref(0)
|
||||
|
||||
// 从路由 query 中获取模式参数
|
||||
const mode = route.query.mode || 'chat'
|
||||
const sessionId = route.query.sessionId
|
||||
|
||||
// 计算属性
|
||||
const pageTitle = computed(() => {
|
||||
@@ -142,59 +165,71 @@ const pageTitle = computed(() => {
|
||||
})
|
||||
|
||||
const statusText = computed(() => {
|
||||
return interviewStatus.value === 'ACTIVE' ? '进行中' : '已结束'
|
||||
// 根据实际的 interviewStatus 更新
|
||||
if (interviewStatus.value === 'COMPLETED') return '已结束'
|
||||
if (interviewStatus.value === 'ACTIVE') return '进行中'
|
||||
return '加载中'
|
||||
})
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
// 从路由 query 中获取 sessionId
|
||||
const sessionId = route.query.sessionId
|
||||
|
||||
if (sessionId) {
|
||||
// 这里是根据 sessionId 从后端加载历史消息的逻辑
|
||||
getHistoryList(sessionId)
|
||||
} else {
|
||||
// 如果没有 sessionId,则初始化为新会话
|
||||
ElMessage.error('会话ID缺失,请返回首页。')
|
||||
router.push('/interview')
|
||||
}
|
||||
scrollToBottom()
|
||||
})
|
||||
|
||||
const getHistoryList = async (sessionId) => {
|
||||
const res = await getMessageListBySessionId(sessionId)
|
||||
if (res.code === 0) {
|
||||
messages.value = res.data
|
||||
if (messages.value && messages.value.length > 0) {
|
||||
const filterList = messages.value.filter(item => item.sender === 'AI');
|
||||
if (filterList && filterList.length > 0) {
|
||||
questionProgressId.value = filterList[filterList.length - 1].questionProgressId
|
||||
try {
|
||||
const res = await getMessageListBySessionId(sessionId)
|
||||
if (res.code === 0) {
|
||||
// **保持与原始逻辑一致**
|
||||
messages.value = res.data || []
|
||||
// 假设后端返回的数据结构中直接包含 status 字段
|
||||
if (res.data && res.data.status) {
|
||||
interviewStatus.value = res.data.status
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('获取历史消息失败')
|
||||
}
|
||||
|
||||
if (messages.value && messages.value.length > 0) {
|
||||
const filterList = messages.value.filter(item => item.sender === 'AI');
|
||||
if (filterList && filterList.length > 0) {
|
||||
// 获取最新的 AI 消息对应的 questionProgressId
|
||||
questionProgressId.value = filterList[filterList.length - 1].questionProgressId
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('获取历史消息失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('获取历史消息失败,网络错误')
|
||||
} finally {
|
||||
scrollToBottom()
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
const sendMessage = async () => {
|
||||
if (!userAnswer.value.trim() || isLoading.value) return
|
||||
if (!userAnswer.value.trim() || isLoading.value || interviewStatus.value === 'COMPLETED') return
|
||||
|
||||
const currentAnswer = userAnswer.value
|
||||
|
||||
// 1. 立即显示用户消息
|
||||
const userMessage = {
|
||||
sender: 'USER',
|
||||
content: userAnswer.value,
|
||||
timestamp: new Date()
|
||||
content: currentAnswer,
|
||||
createdTime: new Date().toISOString() // 使用当前时间作为占位符
|
||||
}
|
||||
|
||||
messages.value.push(userMessage)
|
||||
const currentAnswer = userAnswer.value
|
||||
userAnswer.value = ''
|
||||
isAiThinking.value = true
|
||||
scrollToBottom()
|
||||
|
||||
try {
|
||||
const sessionId = route.query.sessionId
|
||||
// 2. 提交答案 (不修改接口调用逻辑)
|
||||
const answerRes = await submitAnswer(
|
||||
{
|
||||
sessionId,
|
||||
@@ -202,55 +237,69 @@ const sendMessage = async () => {
|
||||
answer: currentAnswer,
|
||||
}
|
||||
)
|
||||
|
||||
// 3. 获取下一题或结束信息 (不修改接口调用逻辑)
|
||||
if (answerRes.code === 0) {
|
||||
const nextQuestionRes = await getNextQuestion(sessionId, questionProgressId.value)
|
||||
if (nextQuestionRes.code === 0) {
|
||||
if (!nextQuestionRes.data) {
|
||||
interviewEnd()
|
||||
if (!nextQuestionRes.data || nextQuestionRes.data.isComplete) {
|
||||
// 明确判断是否结束
|
||||
await interviewEnd(true) // 传递一个参数表示是正常流程结束
|
||||
interviewStatus.value = 'COMPLETED'
|
||||
} else {
|
||||
// 接收新问题
|
||||
questionProgressId.value = nextQuestionRes.data.questionProgressId
|
||||
messages.value.push(nextQuestionRes.data)
|
||||
}
|
||||
questionProgressId.value = nextQuestionRes.data.questionProgressId
|
||||
messages.value.push(nextQuestionRes.data)
|
||||
} else {
|
||||
ElMessage.error('获取下一题失败: ' + nextQuestionRes.message)
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('提交回答失败: ' + answerRes.message)
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
ElMessage.error('发送失败,请稍后重试。')
|
||||
console.error(error)
|
||||
ElMessage.error('发送失败,请检查网络或联系管理员。')
|
||||
} finally {
|
||||
isAiThinking.value = false
|
||||
scrollToBottom()
|
||||
}
|
||||
}
|
||||
|
||||
// 结束面试
|
||||
const interviewEnd = () => {
|
||||
const sessionId = route.query.sessionId
|
||||
endInterview(sessionId).then((res) => {
|
||||
// 结束面试 (不修改接口调用逻辑)
|
||||
const interviewEnd = async (isAutoCompleted = false) => {
|
||||
if (interviewStatus.value === 'COMPLETED') return;
|
||||
|
||||
try {
|
||||
const res = await endInterview(sessionId)
|
||||
if (res.code === 0) {
|
||||
router.push('/history')
|
||||
interviewStatus.value = 'COMPLETED'
|
||||
if (!isAutoCompleted) {
|
||||
ElMessage.success('面试已成功结束,即将为您生成报告。')
|
||||
}
|
||||
scrollToBottom()
|
||||
} else {
|
||||
ElMessage.error('结束面试失败: ' + res.message)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 实际项目中,这里会调用后端 API 结束会话
|
||||
// await fetch(`/api/end-session?sessionId=${sessionId}`, { method: 'POST' });
|
||||
|
||||
interviewStatus.value = 'COMPLETED'
|
||||
scrollToBottom()
|
||||
} catch (e) {
|
||||
ElMessage.error('结束面试失败,网络错误。')
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化消息内容,将换行符替换为<br>
|
||||
// 格式化消息内容,使用 MarkdownIt 渲染
|
||||
const formatMessage = (content) => {
|
||||
// return content ? content.replace(/\n/g, '<br />') : ''
|
||||
// 如果内容不为空,则返回处理后的字符串
|
||||
return content ? md.render(content) : ''
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timestamp) => {
|
||||
return new Date(timestamp).toLocaleTimeString('zh-CN', {
|
||||
if (!timestamp) return ''
|
||||
// 确保 timestamp 是一个 Date 对象或可以被 Date 构造函数解析
|
||||
const date = new Date(timestamp);
|
||||
if (isNaN(date.getTime())) return ''; // 如果时间无效,返回空字符串
|
||||
|
||||
return date.toLocaleTimeString('zh-CN', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
@@ -267,25 +316,43 @@ const scrollToBottom = () => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 容器和头部样式保持不变 */
|
||||
/* 默认样式:PC/平板 (大于 768px) */
|
||||
:root {
|
||||
--primary-color: #409EFF; /* Element Plus 主题蓝 */
|
||||
--ai-bubble-bg: #EAF4FF; /* 柔和的浅蓝 */
|
||||
--user-bubble-bg: #B3E19D; /* 柔和的浅绿 */
|
||||
--chat-bg: #F0F2F5; /* 整体背景灰 */
|
||||
--text-color: #303133;
|
||||
}
|
||||
|
||||
.chat-window-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
height: 90vh; /* 限制最大高度 */
|
||||
width: 100%;
|
||||
max-width: 960px; /* PC端最佳宽度 */
|
||||
margin: 5vh auto; /* 居中并增加顶部间距 */
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
/* 现代感的轻微阴影 */
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 头部样式优化 */
|
||||
.chat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 24px;
|
||||
background: linear-gradient(135deg, #409EFF 0%, #64b5ff 100%);
|
||||
color: white;
|
||||
/* 扁平化,用浅色背景和底部阴影代替强渐变 */
|
||||
background: #FFFFFF;
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
color: var(--text-color);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
@@ -296,117 +363,216 @@ const scrollToBottom = () => {
|
||||
|
||||
.header-left h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
max-width: 300px; /* 限制标题长度 */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 状态指示器美化 */
|
||||
.status-indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: #67C23A;
|
||||
display: inline-block;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.status-indicator.ACTIVE {
|
||||
background-color: #67C23A; /* 绿色 */
|
||||
}
|
||||
|
||||
.status-indicator.COMPLETED {
|
||||
background: #909399;
|
||||
background-color: #F56C6C; /* 红色 */
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 消息区域 */
|
||||
/* 按钮组样式 */
|
||||
.header-right {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.end-button {
|
||||
border-color: #F56C6C !important;
|
||||
color: #F56C6C !important;
|
||||
}
|
||||
|
||||
/* 消息区域优化 */
|
||||
.messages-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
background: #f9fafb;
|
||||
padding: 20px 24px;
|
||||
background: var(--chat-bg);
|
||||
/* 滚动条美化(Webkit Only)*/
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.message-row {
|
||||
display: flex;
|
||||
margin-bottom: 24px;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.message-ai {
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 20px;
|
||||
max-width: 80%; /* PC端消息宽度 */
|
||||
align-items: flex-start; /* 消息气泡顶部对齐 */
|
||||
}
|
||||
|
||||
.message-user {
|
||||
justify-content: flex-end;
|
||||
margin-left: auto;
|
||||
flex-direction: row-reverse; /* 用户头像在右边 */
|
||||
}
|
||||
|
||||
.avatar {
|
||||
flex-shrink: 0;
|
||||
margin-right: 12px;
|
||||
font-weight: bold;
|
||||
/* 通用气泡样式 */
|
||||
.message-bubble {
|
||||
padding: 12px 16px;
|
||||
border-radius: 18px; /* 柔和的圆角 */
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
max-width: 100%;
|
||||
word-break: break-word;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); /* 轻微阴影 */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-user .avatar {
|
||||
order: 2;
|
||||
margin-right: 0;
|
||||
margin-left: 12px;
|
||||
/* AI 气泡 */
|
||||
.message-ai .message-bubble {
|
||||
background-color: var(--ai-bubble-bg);
|
||||
color: var(--text-color);
|
||||
border-bottom-left-radius: 4px; /* 靠近头像的角变小 */
|
||||
}
|
||||
|
||||
/* USER 气泡 */
|
||||
.message-user .message-bubble {
|
||||
background-color: var(--user-bubble-bg);
|
||||
color: #303133;
|
||||
border-bottom-right-radius: 4px; /* 靠近头像的角变小 */
|
||||
}
|
||||
|
||||
.message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: calc(100% - 52px);
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 12px 16px;
|
||||
border-radius: 18px;
|
||||
line-height: 1.6;
|
||||
word-wrap: break-word;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-ai .message-bubble {
|
||||
background-color: white;
|
||||
color: #303133;
|
||||
border-top-left-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.message-user .message-bubble {
|
||||
background-color: #409EFF;
|
||||
color: white;
|
||||
border-top-right-radius: 4px;
|
||||
max-width: calc(100% - 60px); /* 气泡的最大宽度 */
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: 0.75rem;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
padding: 0 4px;
|
||||
align-self: flex-end; /* 时间戳靠右 */
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.message-ai .message-time {
|
||||
text-align: left;
|
||||
align-self: flex-start; /* AI时间戳靠左 */
|
||||
}
|
||||
|
||||
.message-user .message-time {
|
||||
text-align: right;
|
||||
|
||||
/* AI 思考中动画美化 */
|
||||
.typing-indicator-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 输入区域 */
|
||||
.thinking {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background-color: var(--ai-bubble-bg);
|
||||
min-width: 60px;
|
||||
height: 40px;
|
||||
padding: 10px 16px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 80%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
40% {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
.typing-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 50%;
|
||||
animation: bounce 1.2s infinite;
|
||||
}
|
||||
|
||||
.typing-dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.typing-dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
|
||||
/* Markdown 渲染样式穿透 */
|
||||
:deep(.markdown-body) {
|
||||
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
color: inherit; /* 继承气泡颜色,以防万一 */
|
||||
}
|
||||
|
||||
:deep(.markdown-body p) {
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
/* 代码块样式 */
|
||||
:deep(.markdown-body pre) {
|
||||
background-color: #282c34; /* 深色背景 */
|
||||
color: #abb2bf;
|
||||
padding: 1em;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
|
||||
/* 输入区域优化 */
|
||||
.input-area {
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid #e6e8eb;
|
||||
background: white;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.answer-input {
|
||||
margin-bottom: 12px;
|
||||
.answer-input :deep(.el-textarea__inner) {
|
||||
/* 移除 Element Plus 默认的内阴影 */
|
||||
box-shadow: none !important;
|
||||
/* 增加边框,使其更清晰 */
|
||||
border: 1px solid #DCDFE6;
|
||||
border-radius: 8px;
|
||||
padding: 10px 15px;
|
||||
font-size: 0.95rem;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.answer-input :deep(.el-textarea__inner:focus) {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
|
||||
.input-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.char-counter {
|
||||
@@ -415,77 +581,116 @@ const scrollToBottom = () => {
|
||||
}
|
||||
|
||||
.send-button {
|
||||
min-width: 100px;
|
||||
padding: 8px 20px;
|
||||
font-size: 1rem;
|
||||
height: auto; /* 自动高度 */
|
||||
}
|
||||
|
||||
/* AI思考动画 */
|
||||
.thinking span {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: #909399;
|
||||
margin: 0 2px;
|
||||
animation: thinking-dots 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
.thinking span:nth-child(1) {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.thinking span:nth-child(2) {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@keyframes thinking-dots {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 完成面试覆盖层 */
|
||||
/* 完成遮罩层 */
|
||||
.completion-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
backdrop-filter: blur(3px); /* 增加一点模糊效果 */
|
||||
}
|
||||
|
||||
.completion-content {
|
||||
text-align: center;
|
||||
max-width: 500px;
|
||||
padding: 24px;
|
||||
/* ----------------------------------------------------- */
|
||||
/* 响应式优化:平板 (769px < width <= 1024px) */
|
||||
/* ----------------------------------------------------- */
|
||||
@media (min-width: 769px) and (max-width: 1024px) {
|
||||
.chat-window-container {
|
||||
max-width: 95%; /* 平板上使用更大的宽度 */
|
||||
height: 95vh;
|
||||
margin: 2.5vh auto;
|
||||
}
|
||||
|
||||
.messages-container {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
/* ----------------------------------------------------- */
|
||||
/* 响应式优化:手机 (width <= 768px) */
|
||||
/* ----------------------------------------------------- */
|
||||
@media (max-width: 768px) {
|
||||
.message-row {
|
||||
max-width: 90%;
|
||||
.chat-window-container {
|
||||
height: 100vh; /* 手机上全屏高度 */
|
||||
max-height: 100vh;
|
||||
border-radius: 0; /* 移除圆角 */
|
||||
box-shadow: none; /* 移除阴影 */
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
padding: 12px 16px;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex-basis: 100%; /* 左侧标题占满一行 */
|
||||
margin-bottom: 8px;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.header-left h2 {
|
||||
font-size: 1.2rem;
|
||||
max-width: 70%; /* 限制标题长度 */
|
||||
}
|
||||
|
||||
.header-right {
|
||||
order: 2;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.header-button {
|
||||
font-size: 12px;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.messages-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* 手机上消息气泡占 90% */
|
||||
.message-row {
|
||||
max-width: 90%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
font-size: 0.9rem;
|
||||
padding: 10px 14px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.input-actions {
|
||||
flex-direction: column-reverse; /* 按钮在上,计数器在下 */
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.char-counter {
|
||||
align-self: flex-start; /* 计数器左对齐 */
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
width: 100%; /* 按钮全宽 */
|
||||
font-size: 0.95rem;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,123 +1,282 @@
|
||||
<template>
|
||||
<div class="home-container">
|
||||
<div class="hero-section">
|
||||
<h1>AI面试与对话系统</h1>
|
||||
<p>提升您的面试技能,与AI进行智能对话</p>
|
||||
<div class="hero-content">
|
||||
<h1 class="animate-in">AI面试与对话系统</h1>
|
||||
<p class="subtitle animate-in">智能驱动,高效提升您的面试技能与专业对话能力。</p>
|
||||
<el-button type="warning" size="large" round class="explore-btn" @click="navigateTo('interview')">
|
||||
立即开始模拟面试
|
||||
<el-icon class="el-icon--right"><ArrowRight /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-title-wrap">
|
||||
<h2>选择您的学习路径</h2>
|
||||
<p class="section-subtitle">专业功能,助力您在求职道路上脱颖而出</p>
|
||||
</div>
|
||||
|
||||
<div class="options-container">
|
||||
<!--
|
||||
<el-card class="option-card" shadow="hover" @click="navigateTo('chat')">
|
||||
|
||||
<el-card class="option-card primary-option" shadow="always" @click="navigateTo('interview')">
|
||||
<div class="card-content">
|
||||
<el-icon size="48" color="#409EFF">
|
||||
<ChatLineRound/>
|
||||
<el-icon size="56" color="#FFFFFF">
|
||||
<UserFilled/>
|
||||
</el-icon>
|
||||
<h3>AI对话</h3>
|
||||
<p>与AI进行自由对话,获取帮助和建议</p>
|
||||
<el-button type="primary" class="action-btn">开始对话</el-button>
|
||||
<h3>AI 模拟面试</h3>
|
||||
<p>针对特定岗位进行实战模拟,AI面试官实时反馈,快速发现并改进短板。</p>
|
||||
<el-button type="primary" class="action-btn" size="large" round>
|
||||
开始面试
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
-->
|
||||
<el-card class="option-card" shadow="hover" @click="navigateTo('interview')">
|
||||
|
||||
<el-card class="option-card secondary-option" shadow="hover" @click="navigateTo('history')">
|
||||
<div class="card-content">
|
||||
<el-icon size="48" color="#67C23A">
|
||||
<User/>
|
||||
<el-icon size="56" color="#409EFF">
|
||||
<Files/>
|
||||
</el-icon>
|
||||
<h3>AI面试</h3>
|
||||
<p>进行模拟面试,提升面试技巧</p>
|
||||
<el-button type="success" class="action-btn">开始面试</el-button>
|
||||
<h3>回顾与复盘</h3>
|
||||
<p>查看历史面试记录和AI评分报告,系统化复盘每一次对话与问答过程。</p>
|
||||
<el-button type="info" class="action-btn" size="large" plain round>
|
||||
查看记录
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useRouter} from 'vue-router'
|
||||
import {ChatLineRound, User} from '@element-plus/icons-vue'
|
||||
// 导入优化后的图标
|
||||
import {UserFilled, Files, ArrowRight} from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const navigateTo = (type) => {
|
||||
if (type === 'chat') {
|
||||
router.push('/chat')
|
||||
} else if (type === 'interview') {
|
||||
router.push('/interview')
|
||||
}
|
||||
const navigateTo = (path) => {
|
||||
router.push(`/${path}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ----------------------------------------------------- */
|
||||
/* 基础布局与容器 */
|
||||
/* ----------------------------------------------------- */
|
||||
.home-container {
|
||||
padding: 40px 20px;
|
||||
max-width: 1000px;
|
||||
padding: 0 0 60px 0; /* 移除顶部内边距,使 Hero Section 贴顶 */
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
min-height: calc(100vh - 60px); /* 确保页面有足够高度 */
|
||||
background-color: #f4f6f9;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------- */
|
||||
/* 英雄区域 (Hero Section) */
|
||||
/* ----------------------------------------------------- */
|
||||
.hero-section {
|
||||
margin-bottom: 60px;
|
||||
background: linear-gradient(135deg, #409EFF 0%, #79bbff 100%);
|
||||
color: white;
|
||||
padding: 80px 20px;
|
||||
margin-bottom: 50px;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hero-section h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #303133;
|
||||
margin-bottom: 16px;
|
||||
font-size: 3.5rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.hero-section p {
|
||||
font-size: 1.2rem;
|
||||
color: #606266;
|
||||
.subtitle {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 300;
|
||||
margin-bottom: 40px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.explore-btn {
|
||||
font-size: 1.1rem;
|
||||
padding: 25px 35px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
.animate-in {
|
||||
opacity: 0;
|
||||
animation: fadeIn 1s forwards;
|
||||
}
|
||||
.animate-in:nth-child(1) { animation-delay: 0.2s; }
|
||||
.animate-in:nth-child(2) { animation-delay: 0.4s; }
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------- */
|
||||
/* 核心选项区域 */
|
||||
/* ----------------------------------------------------- */
|
||||
.section-title-wrap {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.section-title-wrap h2 {
|
||||
font-size: 2rem;
|
||||
color: #303133;
|
||||
font-weight: 700;
|
||||
}
|
||||
.section-subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: #909399;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 30px;
|
||||
margin-top: 40px;
|
||||
max-width: 900px;
|
||||
margin: 0 auto 60px auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.option-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
height: 250px;
|
||||
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.option-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15) !important;
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.primary-option {
|
||||
background: #409EFF;
|
||||
color: white;
|
||||
}
|
||||
.primary-option .card-content p {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
.primary-option .action-btn {
|
||||
background-color: #FFFFFF;
|
||||
color: #409EFF;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.primary-option .action-btn:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
|
||||
.secondary-option {
|
||||
background-color: white;
|
||||
}
|
||||
.secondary-option h3 {
|
||||
color: #303133;
|
||||
}
|
||||
.secondary-option p {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.card-content h3 {
|
||||
font-size: 1.5rem;
|
||||
margin: 16px 0;
|
||||
color: #303133;
|
||||
font-size: 1.8rem;
|
||||
margin: 20px 0 10px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-content p {
|
||||
color: #606266;
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 30px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
margin-top: 10px;
|
||||
font-size: 1rem;
|
||||
padding: 20px 30px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------- */
|
||||
/* 响应式优化 (Mobile/Tablet) */
|
||||
/* ----------------------------------------------------- */
|
||||
@media (max-width: 1000px) {
|
||||
.hero-section {
|
||||
padding: 60px 20px;
|
||||
}
|
||||
.hero-section h1 {
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.options-container {
|
||||
grid-template-columns: 1fr;
|
||||
.hero-section {
|
||||
padding: 40px 15px;
|
||||
margin-bottom: 30px;
|
||||
border-radius: 0; /* 手机上取消圆角 */
|
||||
}
|
||||
|
||||
.hero-section h1 {
|
||||
font-size: 2rem;
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.explore-btn {
|
||||
padding: 18px 25px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.section-title-wrap h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.section-subtitle {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
|
||||
.options-container {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.option-card {
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.card-content h3 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.card-content p {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 15px 25px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,24 +2,24 @@
|
||||
<div class="interview-view-container">
|
||||
<div class="header-section">
|
||||
<h1>选择面试模式</h1>
|
||||
<p>根据您的需求选择合适的面试方式</p>
|
||||
<p>根据您的需求选择合适的面试方式,获得最专业的反馈报告</p>
|
||||
</div>
|
||||
|
||||
<div class="mode-cards-container">
|
||||
<!-- AI智能面试卡片 -->
|
||||
|
||||
<el-card
|
||||
:class="['mode-card', { 'active': selectedMode === 'ai' }]"
|
||||
shadow="hover"
|
||||
@click="selectedMode = 'ai'"
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="card-icon">
|
||||
<el-icon size="48" color="#409EFF">
|
||||
<div class="card-icon primary-icon">
|
||||
<el-icon size="48">
|
||||
<Cpu/>
|
||||
</el-icon>
|
||||
</div>
|
||||
<h3>AI智能面试</h3>
|
||||
<p class="description">由AI根据您的简历智能生成个性化面试题目</p>
|
||||
<h3>AI 智能面试</h3>
|
||||
<p class="description">由 **AI** 根据您的简历智能生成个性化面试题目,实现**高精度**模拟。</p>
|
||||
<ul class="features-list">
|
||||
<li>
|
||||
<el-icon>
|
||||
@@ -37,26 +37,25 @@
|
||||
<el-icon>
|
||||
<Check/>
|
||||
</el-icon>
|
||||
实时反馈与评分
|
||||
**实时**反馈与评分
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 本地题库面试卡片 -->
|
||||
<el-card
|
||||
:class="['mode-card', { 'active': selectedMode === 'local' }]"
|
||||
shadow="hover"
|
||||
@click="selectedMode = 'local'"
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="card-icon">
|
||||
<el-icon size="48" color="#67C23A">
|
||||
<div class="card-icon success-icon">
|
||||
<el-icon size="48">
|
||||
<Collection/>
|
||||
</el-icon>
|
||||
</div>
|
||||
<h3>本地题库面试</h3>
|
||||
<p class="description">从预设题库中选择题目进行系统化面试</p>
|
||||
<p class="description">从预设题库中选择题目进行系统化面试,**稳固**基础知识。</p>
|
||||
<ul class="features-list">
|
||||
<li>
|
||||
<el-icon>
|
||||
@@ -68,7 +67,7 @@
|
||||
<el-icon>
|
||||
<Check/>
|
||||
</el-icon>
|
||||
可自定义选择范围
|
||||
**自定义**选择范围
|
||||
</li>
|
||||
<li>
|
||||
<el-icon>
|
||||
@@ -81,15 +80,40 @@
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 题库选择区域(仅本地模式显示) -->
|
||||
<QuestionBankSection ref="questionBankSectionRef" v-if="selectedMode === 'local'"/>
|
||||
<div class="question-bank-section" v-if="selectedMode === 'local'">
|
||||
<el-alert title="请从下方题库中选择您希望参与面试的知识点或题集范围" type="info" :closable="false" center />
|
||||
<QuestionBankSection ref="questionBankSectionRef"/>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 开始面试表单 -->
|
||||
<div class="start-form-section">
|
||||
<el-card shadow="never">
|
||||
<el-form :model="formData" label-width="120px" size="large">
|
||||
<el-form-item label="您的姓名" required>
|
||||
<el-input v-model="formData.candidateName" placeholder="请输入您的姓名"/>
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ selectedMode === 'ai' ? 'AI 面试启动配置' : '题库面试启动配置' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :model="formData" size="large" label-position="top"> <el-row :gutter="20">
|
||||
<el-col :span="isMobile ? 24 : 12">
|
||||
<el-form-item label="您的姓名" required>
|
||||
<el-input v-model="formData.candidateName" placeholder="请输入您的姓名"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="isMobile ? 24 : 12">
|
||||
<el-form-item label="面试题目数量" prop="totalQuestions">
|
||||
<el-input-number v-model="formData.totalQuestions" :min="1" :max="100" controls-position="right"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="目标岗位要求" required>
|
||||
<el-input
|
||||
v-model="formData.jobRequirements"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入目标岗位的名称和核心要求(例如:3年经验的前端开发,熟悉Vue3、TypeScript)。AI 将根据此信息出题。"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="上传简历" required>
|
||||
@@ -100,37 +124,39 @@
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:on-exceed="handleFileExceed"
|
||||
:file-list="fileList"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-button type="primary">选择文件</el-button>
|
||||
</template>
|
||||
<template #tip>
|
||||
<div class="upload-tip">
|
||||
支持 PDF、Markdown 或文本格式,大小不超过10MB
|
||||
支持 PDF、Markdown 或文本格式,大小不超过10MB。简历是 AI 个性化出题的基础。
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="AI模型">
|
||||
|
||||
<el-form-item label="AI 模型">
|
||||
<el-select v-model="formData.aiModel" placeholder="请选择AI模型">
|
||||
<el-option label="DeepSeek" value="deepSeek"></el-option>
|
||||
<el-option label="阿里千问" value="qwen"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="面试题目数量">
|
||||
<el-input-number v-model="formData.totalQuestions" :min="1" :max="100"></el-input-number>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-divider />
|
||||
|
||||
<el-form-item class="form-actions">
|
||||
<el-button
|
||||
type="success"
|
||||
:loading="isLoading"
|
||||
@click="startInterviewAction"
|
||||
class="start-button"
|
||||
size="large"
|
||||
>
|
||||
{{ selectedMode === 'ai' ? '开始AI面试' : '开始题库面试' }}
|
||||
{{ selectedMode === 'ai' ? '开始 AI 面试' : '开始题库面试' }}
|
||||
</el-button>
|
||||
<el-button @click="$router.push('/')">返回首页</el-button>
|
||||
<el-button @click="$router.push('/')" size="large">返回首页</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
@@ -139,10 +165,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, reactive} from 'vue'
|
||||
import {ref, reactive, computed, onMounted, onBeforeUnmount} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {Cpu, Collection, Check} from '@element-plus/icons-vue'
|
||||
import {ElMessage, ElNotification} from 'element-plus'
|
||||
import {Cpu, Collection, Check, Finished} from '@element-plus/icons-vue' // 引入 Finished 图标用于上传成功提示
|
||||
import QuestionBankSection from '@/components/QuestionBankSection.vue'
|
||||
import {startInterview} from '@/api/interview.js';
|
||||
|
||||
@@ -151,52 +177,118 @@ const router = useRouter()
|
||||
|
||||
const questionBankSectionRef = ref(null)
|
||||
|
||||
// 响应式状态
|
||||
// --- 响应式状态 ---
|
||||
const selectedMode = ref('ai') // 'ai' 或 'local'
|
||||
const isLoading = ref(false)
|
||||
const fileList = ref([]) // 用于显示已上传的文件列表
|
||||
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
candidateName: '',
|
||||
resumeFiles: [],
|
||||
jobRequirements: '',
|
||||
resumeFiles: null,
|
||||
totalQuestions: 10,
|
||||
aiModel: 'deepSeek'
|
||||
})
|
||||
|
||||
// --- 响应式断点控制 ---
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
const MOBILE_WIDTH = 768;
|
||||
|
||||
// --- UI交互方法 ---
|
||||
const handleFileChange = (file) => {
|
||||
formData.value.resumeFiles = file.raw;
|
||||
const isMobile = computed(() => windowWidth.value <= MOBILE_WIDTH);
|
||||
|
||||
const handleResize = () => {
|
||||
windowWidth.value = window.innerWidth;
|
||||
};
|
||||
// 文件上传处理
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
|
||||
// --- 文件上传处理 ---
|
||||
const handleFileChange = (file) => {
|
||||
// 清除旧文件
|
||||
fileList.value = []
|
||||
|
||||
// 限制文件类型和大小(虽然主要由后端校验,前端也应提醒)
|
||||
const isAcceptedType = ['application/pdf', 'text/markdown', 'text/plain'].includes(file.raw.type) || file.name.endsWith('.md');
|
||||
const isLt10M = file.raw.size / 1024 / 1024 < 10;
|
||||
|
||||
if (!isAcceptedType) {
|
||||
ElMessage.error('只支持 PDF, Markdown (.md) 或文本 (.txt) 文件!');
|
||||
return false;
|
||||
}
|
||||
if (!isLt10M) {
|
||||
ElMessage.error('文件大小不能超过 10MB!');
|
||||
return false;
|
||||
}
|
||||
|
||||
formData.value.resumeFiles = file.raw;
|
||||
fileList.value.push({name: file.name, url: URL.createObjectURL(file.raw), status: 'success', uid: file.uid})
|
||||
|
||||
ElNotification({
|
||||
title: '简历上传成功',
|
||||
message: `${file.name} 已准备就绪。`,
|
||||
type: 'success',
|
||||
icon: Finished,
|
||||
duration: 3000
|
||||
});
|
||||
};
|
||||
|
||||
const handleFileExceed = () => {
|
||||
ElMessage.warning('只能上传一个简历文件')
|
||||
}
|
||||
|
||||
// --- 业务逻辑 ---
|
||||
const sessionId = ref('')
|
||||
// 开始面试
|
||||
|
||||
const validateForm = () => {
|
||||
if (!formData.value.candidateName) {
|
||||
ElMessage.error('请输入您的姓名。');
|
||||
return false;
|
||||
}
|
||||
if (!formData.value.jobRequirements) {
|
||||
ElMessage.error('请输入目标岗位要求。');
|
||||
return false;
|
||||
}
|
||||
if (!formData.value.resumeFiles) {
|
||||
ElMessage.error('请上传您的简历文件。');
|
||||
return false;
|
||||
}
|
||||
if (selectedMode.value === 'local' && (!questionBankSectionRef.value || questionBankSectionRef.value.getSelectionResult().selectedNodes.length === 0)) {
|
||||
ElMessage.error('请在本地题库模式下选择至少一个知识点或题集。');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const startInterviewAction = async () => {
|
||||
console.log(formData.value)
|
||||
if (!formData.value.candidateName || !formData.value.resumeFiles) {
|
||||
ElMessage.error('请输入您的姓名并上传简历。');
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
const sendFormData = new FormData();
|
||||
|
||||
// 准备发送到后端的 JSON 数据
|
||||
const sendData = {
|
||||
candidateName: formData.value.candidateName,
|
||||
jobRequirements: formData.value.jobRequirements,
|
||||
aiModel: formData.value.aiModel,
|
||||
totalQuestions: formData.value.totalQuestions,
|
||||
model: selectedMode.value,
|
||||
selectedNodes: []
|
||||
}
|
||||
|
||||
// 处理本地题库模式的选中节点
|
||||
if (selectedMode.value === 'local') {
|
||||
const selectionResult = questionBankSectionRef.value.getSelectionResult()
|
||||
if (!selectionResult.selectedNodes) {
|
||||
selectionResult.selectedNodes = []
|
||||
}
|
||||
if (selectionResult.selectedNodes && selectionResult.selectedNodes.length > 0) {
|
||||
const selectionResult = questionBankSectionRef.value?.getSelectionResult()
|
||||
if (selectionResult && selectionResult.selectedNodes && selectionResult.selectedNodes.length > 0) {
|
||||
const sendNodes = []
|
||||
selectionResult.selectedNodes.forEach(node => {
|
||||
sendNodes.push({
|
||||
@@ -209,15 +301,18 @@ const startInterviewAction = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 将 JSON 数据作为 Blob 添加到 FormData
|
||||
sendFormData.append('interviewStartDto', new Blob([JSON.stringify(sendData)], {
|
||||
type: 'application/json',
|
||||
}))
|
||||
// 添加简历文件
|
||||
sendFormData.append('resume', formData.value.resumeFiles);
|
||||
|
||||
try {
|
||||
console.log(sendFormData.values())
|
||||
const responseData = await startInterview(sendFormData);
|
||||
const data = responseData.data;
|
||||
sessionId.value = data.sessionId;
|
||||
|
||||
// 跳转到聊天界面
|
||||
router.push({
|
||||
path: '/interview-chat',
|
||||
@@ -228,101 +323,132 @@ const startInterviewAction = async () => {
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('开始面试失败:', error);
|
||||
ElMessage.error(`开始面试失败: ${error.message || '网络错误'}`);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 1. PC/宽屏 (默认样式) */
|
||||
.interview-view-container {
|
||||
padding: 24px;
|
||||
max-width: 1200px;
|
||||
padding: 30px 24px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header-section {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.header-section h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.header-section p {
|
||||
font-size: 1.1rem;
|
||||
color: #606266;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* --- 模式选择卡片 --- */
|
||||
.mode-cards-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.mode-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
border: 2px solid #e4e7ed;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.mode-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15) !important;
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 12px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.mode-card.active {
|
||||
border-color: #409EFF;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #e4e7ed 100%);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background-color: #f7f9fc;
|
||||
box-shadow: 0 4px 10px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.card-content { text-align: center; padding: 20px; }
|
||||
.card-icon {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 20px;
|
||||
/* 使得图标更突出 */
|
||||
display: inline-flex;
|
||||
padding: 15px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.primary-icon { background-color: #ecf5ff; color: #409EFF; }
|
||||
.success-icon { background-color: #f0f9eb; color: #67C23A; }
|
||||
|
||||
.card-content h3 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 12px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: #606266;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.card-content h3 { font-size: 1.6rem; margin-bottom: 10px; color: #303133; font-weight: 600; }
|
||||
.description { color: #909399; margin-bottom: 20px; line-height: 1.6; font-size: 0.95rem; }
|
||||
|
||||
.features-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
max-width: 250px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.features-list li {
|
||||
padding: 8px 0;
|
||||
padding: 6px 0;
|
||||
color: #606266;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.features-list .el-icon {
|
||||
color: #67C23A;
|
||||
margin-right: 8px;
|
||||
margin-right: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
/* --- 题库选择区 --- */
|
||||
.question-bank-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 15px;
|
||||
border: 1px dashed #dcdfe6;
|
||||
border-radius: 8px;
|
||||
background-color: #fcfcfc;
|
||||
}
|
||||
|
||||
|
||||
/* --- 启动表单区域 --- */
|
||||
.start-form-section {
|
||||
margin-top: 24px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.start-form-section .el-card {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.card-header span {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 覆盖 Element Plus 默认样式,使用 label-position="top" */
|
||||
.el-form :deep(.el-form-item) {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.el-form :deep(.el-form-item__label) {
|
||||
font-size: 1rem;
|
||||
color: #303133;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
@@ -331,17 +457,76 @@ const startInterviewAction = async () => {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.start-button {
|
||||
min-width: 140px;
|
||||
.el-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.start-button {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
|
||||
/* 2. 平板/小型PC优化 (769px < width <= 1000px) */
|
||||
@media (min-width: 769px) and (max-width: 1000px) {
|
||||
.interview-view-container {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 3. 手机/超小平板优化 (width <= 768px) */
|
||||
@media (max-width: 768px) {
|
||||
.mode-cards-container {
|
||||
grid-template-columns: 1fr;
|
||||
.interview-view-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.header-section h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.header-section p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 卡片堆叠成单列 */
|
||||
.mode-cards-container {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 卡片内容简化 */
|
||||
.card-content h3 { font-size: 1.4rem; }
|
||||
.description { font-size: 0.9rem; }
|
||||
.features-list { max-width: none; } /* 移动端列表全宽 */
|
||||
|
||||
/* 启动表单:强制全宽 */
|
||||
.el-row :deep(.el-col) {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 优化按钮区域布局 */
|
||||
.form-actions :deep(.el-form-item__content) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.el-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 移除数字输入框右侧边距 */
|
||||
.el-form-item .el-input-number {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -13,14 +13,14 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
// proxy: {
|
||||
// // Proxy API requests to the backend server
|
||||
// '/api': {
|
||||
// target: 'http://localhost:8080',
|
||||
// changeOrigin: true, // Needed for virtual hosted sites
|
||||
// secure: false, // Optional: if you are using https
|
||||
// rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
// },
|
||||
// },
|
||||
proxy: {
|
||||
// Proxy API requests to the backend server
|
||||
// '/api': {
|
||||
// target: 'http://localhost:8080',
|
||||
// changeOrigin: true, // Needed for virtual hosted sites
|
||||
// secure: false, // Optional: if you are using https
|
||||
// rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
// },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user