目录
- 一、Flask后端部分
- model
- Service
- route
- 二、Vue前端部分
- index.js
- main.vue
- 功能界面
- template
- script
- style
一般就是三个层面,model层面用来建立数据库的字段,service用来对model进行操作,写一些数据库操作的代码,route就是具体的功能了,其中会包含一些数据库service层的函数
一、Flask后端部分
model
# 创建基础模型类
Base = declarative_base()
# 创建数据库连接
engine = create_engine('sqlite:data/hyq/code/llf/yuanshenqidong/backend/app/models/test.db')
Session = sessionmaker(bind=engine)
class ExternalResume(Base):
__tablename__ = 'external_resume'
uuid = Column(String(255), primary_key=True, unique=True)
person_id = Column(Integer, ForeignKey('person.person_id'))
name = Column(String(255), nullable=False)
gender = Column(String(255))
age = Column(Integer)
brith_date = Column(DateTime)
email = Column(String(255))
phone = Column(String(255))
educational_background = Column(String(255))
work_experience = Column(String(255))
project_experience = Column(String(255))
skill = Column(String(255))
research_achievement = Column(String(255))
award = Column(String(255))
person = relationship("Person", back_populates="external_resume")
tablename = ‘external_resume’ 指定了数据库中对应的表名
类继承自 Base,这是 SQLAlchemy 的声明性基类
person = relationship(“Person”, back_populates=“external_resume”):定义了与 Person 模型的双向关系
declarative_base() 是 SQLAlchemy 提供的一个工厂函数,用于创建所有模型类的基类
继承自这个 Base 的类会被 SQLAlchemy 自动识别为数据库模型
create_engine() 创建了一个数据库引擎实例
注意 SQLite 的连接字符串是三个斜杠 (sqlite:///) 后接文件路径
sessionmaker 创建了一个会话工厂类
bind=engine 将这个会话工厂绑定到之前创建的数据库引擎
之后可以通过 Session() 来创建实际的数据库会话实例,用于执行数据库操作
将不同的模型分类到 model.py 和 newmodel.py 中,便于管理。通过 init.py 统一导出,使外部代码可以方便地导入所有模型。
潜在问题
User 类重复
model.py 和 newmodel.py 都定义了 User 类,可能会导致冲突。
如果两个 User 是不同的模型,应该重命名其中一个(如 NewUser)。
如果相同,应该统一放在一个文件中。
Session 可能混淆
Session 是 SQLAlchemy 的会话类,但通常建议命名为 DBSession 或类似名称,避免与标准库的 session 混淆。
Base 的导出
Base 是 SQLAlchemy 的基类,通常不需要在 all 中导出,除非外部代码需要直接访问它(如创建表)。
Service
def get_all_resume_data():
"""
查询数据库中所有简历数据
返回:
List[Dict]: 包含所有简历数据的列表,每个简历是一个字典
"""
try:
with Session() as session:
resumes = session.query(ExternalResume).all()
results=[]
for resume in resumes:
resume_dict = {
'name': resume.name,
'gender': resume.gender,
'age': resume.age,
'birth_date': resume.brith_date.strftime('%Y-%m-%d') if resume.brith_date else None,
'email': resume.email,
'phone': resume.phone,
'educational_background': resume.educational_background,
'work_experience': resume.work_experience,
'project_experience': resume.project_experience,
'skill': resume.skill,
'research_achievement': resume.research_achievement,
'award': resume.award
}
results.append(resume_dict)
print(resume_dict)
print(f"成功查询到 {len(results)} 条简历记录")
return results
except Exception as e:
print(f"查询数据库时发生错误: {str(e)}")
return []
with Session():
使用上下文管理器创建数据库会话,确保会话在使用后自动关闭。(这个写法很像打开文件)
session.query(ExternalResume).all():
查询 ExternalResume 表的所有记录,返回一个包含所有简历对象的列表。对象列表,遍历每一个对象,并获取数据库对象的属性
在返回的时候,也构造类似的数据,列表中是字典,每个字典对应着数据库中的一行,键是每一行的属性。字典列表。
route
# 查询知识库
@resume_bp.route('/resume-files', methods=['GET'])
def get_resume_files():
try:
result = get_all_resume_data()
if result is None:
return jsonify({
'error': '获取简历文件失败'
}), 500
return jsonify(result), 200
except Exception as e:
print(f"获取简历文件时出错: {str(e)}")
return jsonify({
'error': f'服务器内部错误: {str(e)}'
}), 500
字典列表是可以直接用jsonify加工成json数据的,然后回传给前端
jsonify 是 Flask 框架中一个非常实用的函数,用于将 Python 数据结构转换为 JSON 格式的 HTTP 响应。
二、Vue前端部分
前端部分主要为三部分,一个是路由.js文件,一个main.vue主界面,还有一个我们的功能界面
index.js
import RecruitmentView from '@/views/RecruitmentView.vue'
import AbilityView from '@/views/AbilityView.vue'
用于导入页面文件
const routes = [
{ path: '/main', component: Main },
{ path: '/', component: Login },
{ path: '/register', component: Register },
{ path: '/modify', component: Modify },
{
path: '/recruitment',
name: 'recruitment',
component: RecruitmentView
},
{
path: '/ability',
name: 'ability',
component: AbilityView
},
];
routes 是一个数组,定义了应用程序的所有路由规则。
每个路由规则是一个对象,包含:
path: URL 路径(就是浏览器访问的地址)
component: 对应的 Vue 组件(页面)
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
使用 createRouter 创建路由实例。
history: 使用 createWebHistory() 创建基于 HTML5 History API 的路由模式(干净的 URL,无 #)
routes: 传入上面定义的路由配置
导出创建的路由实例,以便在 Vue 应用程序的主文件(通常是 main.js 或 app.js)中使用。
main.vue
<el-button
class="aside-btn upload-file-btn"
type="primary"
:icon="Plus"
:class="{ 'icon-only': isCollapsed }"
@click="goToRecruitmentPage"
>
{{ isCollapsed ? '' : '人才招聘分析' }}
</el-button>
type=“primary”:
设置按钮类型为 primary(主按钮,通常是蓝色)
:icon=“Plus”:
动态绑定图标属性,使用 Element Plus 的 Plus(加号)图标
需要从 Element Plus 导入 Plus 图标:import { Plus } from ‘@element-plus/icons-vue’
:class=“{ ‘icon-only’: isCollapsed }”:
动态绑定 class,当 isCollapsed 为 true 时,添加 icon-only 类
这通常用于侧边栏折叠时只显示图标不显示文字的场景
@click=“goToRecruitmentPage”:
点击事件绑定,点击按钮时调用 goToRecruitmentPage 方法
goToRecruitmentPage() {
// 使用 Vue Router 进行页面跳转
this.$router.push('/recruitment');
},
在script的methods下完成跳转
.push() 方法:
Vue Router 的核心导航方法
作用:向浏览器的历史记录栈添加一个新记录,并跳转到指定路由
等效于用户点击 的效果
功能界面
template
整体结构,分为三行
<div class="recruitment-container">
<el-row :gutter="20">
<!-- 三个主要部分 -->
<el-col :span="24">标题和总览卡片</el-col>
<div v-show="showAnalysis">数据分析图表区域</div>
<el-col :span="24">表格区域</el-col>
</el-row>
</div>
标题和总览区域
<el-card class="overview-card" shadow="hover">
<div class="dashboard-header">
<h2>人才池运营看板</h2>
<div class="header-actions">
<el-button
type="primary"
@click="showAnalysis = !showAnalysis"
:icon="showAnalysis ? 'Close' : 'DataAnalysis'"
>
{{ showAnalysis ? '关闭分析' : '数据分析' }}
</el-button>
</div>
<div class="total-stats">
<div class="stat-item">
<div class="stat-value">{{ total }}</div>
<div class="stat-label">简历总数</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ todayNew }}</div>
<div class="stat-label">今日新增</div>
</div>
</div>
</div>
</el-card>
使用 el-card 创建卡片式布局
包含:
主标题 “人才池运营看板”
一个切换按钮,用于显示/隐藏分析图表区域
两个统计数字:简历总数和今日新增简历
卡片式布局,应该就是标签并排着一个一个
数据分析图表区域 (条件渲染)
<div v-show="showAnalysis" class="analysis-container">
<el-row :gutter="20">
<el-col :span="24">
<!-- 技术栈分布图表 -->
<el-card class="chart-card" shadow="hover">
<template #header>
<span class="chart-title">技术栈分布</span>
</template>
<div ref="skillChart" class="chart-container skill-chart"></div>
</el-card>
<!-- 学历分布图表 -->
<el-card class="chart-card" shadow="hover">
<template #header>
<span class="chart-title">学历分布</span>
</template>
<div ref="educationChart" class="chart-container education-chart"></div>
</el-card>
</el-col>
</el-row>
</div>
使用 v-show 控制显示/隐藏
包含两个图表卡片:
技术栈分布图表
学历分布图表
使用 ref 属性为图表容器注册引用,便于后续用 ECharts 等库渲染图表
表格区域
<el-card class="table-card" shadow="hover">
<el-table
:data="resumeList"
stripe
v-loading="loading"
height="calc(100vh - 600px)"
>
<!-- 多个表格列 -->
<el-table-column prop="name" label="姓名" />
<el-table-column prop="gender" label="性别" />
<!-- 其他列... -->
<!-- 特殊处理的列 -->
<el-table-column prop="skill" label="技能">
<template #default="scope">
<el-tag v-for="skill in scope.row.skill?.split(',')">
{{ skill.trim() }}
</el-tag>
</template>
</el-table-column>
<!-- 长文本使用 tooltip 显示 -->
<el-table-column prop="work_experience" label="工作经验">
<template #default="scope">
<el-tooltip :content="scope.row.work_experience">
<div class="truncate">{{ scope.row.work_experience }}</div>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
script
导入依赖
import { ref, onMounted, computed, watch, nextTick } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'
import * as echarts from 'echarts'
import { DataAnalysis, Close } from '@element-plus/icons-vue'
Vue 相关:Composition API 的核心方法
Axios:用于 HTTP 请求
Element Plus:消息提示组件和图标
ECharts:数据可视化图表库
响应式数据
const allResumeList = ref([]) // 所有简历数据
const currentPage = ref(1) // 当前页码
const pageSize = ref(10) // 每页条数
const loading = ref(false) // 加载状态
const total = ref(0) // 数据总数
const todayNew = ref(0) // 今日新增简历数
const showAnalysis = ref(false) // 是否显示分析图表
const analysisType = ref('skill') // 当前分析维度
// 图表 DOM 引用
const skillChart = ref(null)
const educationChart = ref(null)
// 图表实例
let skillChartInstance = null
let educationChartInstance = null
计算属性
const resumeList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return allResumeList.value.slice(start, end)
})
根据当前分页参数计算当前页显示的数据
核心方法
数据获取
const fetchResumeData = async () => {
loading.value = true
try {
const response = await axios.get('http://10.1.108.220:18000/resume/resume-files')
allResumeList.value = response.data
total.value = response.data.length
todayNew.value = Math.floor(Math.random() * 10) // 模拟数据
if (response.data.length === 0) {
ElMessage.warning('暂无简历数据')
} else {
ElMessage.success('数据加载成功')
}
} catch (error) {
ElMessage.error('获取数据失败')
} finally {
loading.value = false
}
}
当点击事件发生的时候,就获取简历数据
返回值
return {
resumeList,
currentPage,
pageSize,
total,
loading,
handleSizeChange,
handleCurrentChange,
skillChart,
educationChart,
todayNew,
showAnalysis,
DataAnalysis,
Close
}
暴露给模板使用的所有变量和方法