123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317 |
- <template>
- <fs-page>
- <div class="job-application-container">
- <!-- 添加顶部标签页 -->
- <div class="tabs-container">
- <el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
- <el-tab-pane label="职位申请" name="application">
- <!-- 第一个tab:保留现有功能 -->
- <div class="tab-content">
- <div class="sidebar">
- <div class="tree-container">
- <!-- 职位分类 -->
- <div class="category-title" @click="togglePositionList">
- <el-icon><Briefcase /></el-icon>
- <span>职位</span>
- <el-icon class="expand-icon" :class="{ 'is-expanded': showPositionList }">
- <ArrowRight />
- </el-icon>
- </div>
-
- <!-- 职位列表 -->
- <div v-if="showPositionList" class="position-list">
- <!-- 全部职位 -->
- <div class="tree-item" :class="{ active: activeNode === 'position-all' }" @click="handleNodeClick({ type: 'all', id: 'position-all' })">
- <div class="item-content">
- <span>全部</span>
- </div>
- </div>
-
- <!-- 职位列表项 -->
- <div
- v-for="position in positions"
- :key="'position-' + position.id"
- class="tree-item"
- :class="{ active: activeNode === 'position-' + position.id }"
- @click="handleNodeClick({ type: 'position', value: position.title, id: 'position-' + position.id })"
- >
- <div class="item-content">
- <el-icon><ArrowRight /></el-icon>
- <span>{{ position.title }}</span>
- </div>
- <div class="item-count" v-if="position.count">{{ position.count }}</div>
- </div>
- </div>
- <!-- 全部 -->
- <div class="tree-item" :class="{ active: activeNode === 'all' }" @click="handleNodeClick({ type: 'all' })">
- <div class="item-content">
- <el-icon><Grid /></el-icon>
- <span>全部</span>
- </div>
- <div class="item-count">{{ totalCount }}</div>
- </div>
-
- <!-- 待安排 -->
- <div class="tree-item" :class="{ active: activeNode === 'status-0' }" @click="handleNodeClick({ type: 'status', value: 0, id: 'status-0' })">
- <div class="item-content">
- <el-icon><Message /></el-icon>
- <span>待通知</span>
- </div>
- <div class="item-count" v-if="statusCounts[1]">{{ statusCounts[1] }}</div>
- </div>
- <!-- 待安排 -->
- <div class="tree-item" :class="{ active: activeNode === 'status-1' }" @click="handleNodeClick({ type: 'status', value: 1, id: 'status-1' })">
- <div class="item-content">
- <el-icon><Clock /></el-icon>
- <span>已通知待面试</span>
- </div>
- <div class="item-count" v-if="statusCounts[1]">{{ statusCounts[5] }}</div>
- </div>
- <!-- 推进中 -->
- <div class="tree-item" :class="{ active: activeNode === 'status-2' }" @click="handleNodeClick({ type: 'status', value: 2, id: 'status-2' })">
- <div class="item-content">
- <el-icon><ArrowRight /></el-icon>
- <span>已面试待处理</span>
- </div>
- <div class="item-count" v-if="statusCounts[2]">{{ statusCounts[2] }}</div>
- </div>
-
- <!-- 已入职 -->
- <div class="tree-item" :class="{ active: activeNode === 'status-3' }" @click="handleNodeClick({ type: 'status', value: 3, id: 'status-3' })">
- <div class="item-content">
- <el-icon><Check /></el-icon>
- <span>已录用</span>
- </div>
- <div class="item-count" v-if="statusCounts[3]">{{ statusCounts[3] }}</div>
- </div>
-
- <!-- 待召回 -->
- <div class="tree-item" :class="{ active: activeNode === 'status-4' }" @click="handleNodeClick({ type: 'status', value: 4, id: 'status-4' })">
- <div class="item-content">
- <el-icon><RefreshRight /></el-icon>
- <span>拒不录用</span>
- </div>
- <div class="item-count" v-if="statusCounts[4]">{{ statusCounts[4] }}</div>
- </div>
-
-
- </div>
- </div>
- <div class="content">
- <fs-crud ref="crudRef" v-bind="crudBinding">
- <!-- <template #actionbar-right>
-
- </template> -->
- </fs-crud>
- </div>
- </div>
- </el-tab-pane>
-
- <el-tab-pane label="待通知列表" name="resume">
- <!-- 第二个tab:待通知列表 -->
- <div class="tab-content">
- <div class="notification-container">
- <!-- 搜索表单 -->
- <div class="search-form">
- <el-form :model="notificationSearchForm" :inline="true" size="default">
- <el-form-item label="姓名">
- <el-input
- v-model="notificationSearchForm.name"
- placeholder="请输入姓名"
- clearable
- style="width: 200px"
- />
- </el-form-item>
- <el-form-item label="手机号">
- <el-input
- v-model="notificationSearchForm.phone"
- placeholder="请输入手机号"
- clearable
- style="width: 200px"
- />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleNotificationSearch">
- <el-icon><Search /></el-icon>
- 搜索
- </el-button>
- <el-button @click="handleNotificationReset">
- <el-icon><Refresh /></el-icon>
- 重置
- </el-button>
- </el-form-item>
- </el-form>
- </div>
- <!-- 操作按钮 -->
- <div class="action-bar">
- <div class="left-actions">
- <el-button type="primary" @click="handleBatchNotify" size="small" :disabled="!selectedNotificationRows.length">
- <el-icon><Message /></el-icon>
- 批量通知
- </el-button>
- <el-button type="danger" @click="handleBatchDelete" size="small" :disabled="!selectedNotificationRows.length">
- <el-icon><Delete /></el-icon>
- 批量删除
- </el-button>
- <div class="action-button-wrapper">
- <el-upload
- class="upload-demo"
- :action="uploadUrl"
- :headers="{
- Authorization: `JWT ${Session.get('token')}`
- }"
- :on-success="handleImportSuccess"
- :on-error="handleImportError"
- accept=".xlsx,.xls"
- :show-file-list="false"
- >
- <el-button type="primary" size="small">
- <el-icon><Download /></el-icon>
- 导入
- </el-button>
- </el-upload>
- </div>
- <el-button type="primary" @click="handleBatchInterview" size="small" >
- <!-- <el-icon><Clock /></el-icon> -->
- 下载导入模板
- </el-button>
- </div>
- <!-- <div class="right-actions">
- <el-button @click="handleNotificationRefresh">
- <el-icon><Refresh /></el-icon>
- 刷新
- </el-button>
- </div> -->
- </div>
- <!-- 数据表格 -->
- <div class="table-container">
- <el-table
- :data="notificationList"
- @selection-change="handleNotificationSelectionChange"
- :loading="notificationLoading"
- style="width: 100%"
- height="calc(100vh - 400px)"
- >
- <el-table-column type="selection" width="55" />
- <el-table-column prop="name" label="姓名" />
- <el-table-column prop="phone" label="手机号" />
- <el-table-column prop="status" label="状态">
- <template #default="scope">
- <el-tag type="warning">待通知</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip />
- <el-table-column label="操作" fixed="right">
- <template #default="scope">
- <el-button
- type="text"
- size="small"
- @click="handleViewDetail(scope.row)"
- >
- 查看详情
- </el-button>
- <el-button
- type="text"
- size="small"
- @click="handleNotify(scope.row)"
- >
- 编辑
- </el-button>
- <el-button
- type="text"
- size="small"
- @click="handleInterview(scope.row)"
- >
- 删除
- </el-button>
-
- </template>
- </el-table-column>
- </el-table>
- </div>
- <!-- 分页 -->
- <div class="pagination-container">
- <el-pagination
- v-model:current-page="notificationPagination.current"
- v-model:page-size="notificationPagination.size"
- :page-sizes="[10, 20, 50, 100]"
- :total="notificationPagination.total"
- layout="total, sizes, prev, pager, next, jumper"
- @size-change="handleNotificationSizeChange"
- @current-change="handleNotificationCurrentChange"
- />
- </div>
- </div>
- </div>
- </el-tab-pane>
- </el-tabs>
- </div>
- </div>
- <BatchTagsDialog ref="batchTagsDialogRef" :crudExpose="crudExpose" />
- <BatchStatusDialog ref="batchStatusDialogRef" :crudExpose="crudExpose" @success="handleBatchStatusSuccess" />
- <smSnotification ref="smSnotificationRef" :crudExpose="crudExpose" />
-
- <!-- 详情弹窗 -->
- <el-dialog
- v-model="detailDialogVisible"
- title="职位申请详情"
- width="60%"
- :before-close="handleDetailDialogClose"
- >
- <div v-if="currentDetailData" class="detail-content">
- <el-descriptions :column="2" border>
- <el-descriptions-item label="申请人姓名">{{ currentDetailData.name }}</el-descriptions-item>
- <el-descriptions-item label="联系电话">{{ currentDetailData.phone }}</el-descriptions-item>
- <el-descriptions-item label="申请职位">{{ currentDetailData.position_title }}</el-descriptions-item>
- <el-descriptions-item label="申请时间">{{ currentDetailData.apply_time }}</el-descriptions-item>
- <el-descriptions-item label="当前状态">
- <el-tag :type="getStatusTagType(currentDetailData.status)">
- {{ getStatusText(currentDetailData.status) }}
- </el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="来源渠道">{{ currentDetailData.source }}</el-descriptions-item>
- <el-descriptions-item label="备注信息" :span="2">{{ currentDetailData.remark || '无' }}</el-descriptions-item>
- </el-descriptions>
- </div>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="detailDialogVisible = false">关闭</el-button>
- <el-button type="primary" @click="handleEditDetail">编辑</el-button>
- </span>
- </template>
- </el-dialog>
- <!-- 编辑弹窗 -->
- <el-dialog
- v-model="editDialogVisible"
- title="编辑通知信息"
- width="520px"
- :before-close="handleEditDialogClose"
- >
- <el-form :model="editForm" :rules="editRules" ref="editFormRef" label-width="90px">
- <el-form-item label="姓名" prop="name">
- <el-input v-model="editForm.name" placeholder="请输入姓名" />
- </el-form-item>
- <el-form-item label="手机号" prop="phone">
- <el-input v-model="editForm.phone" placeholder="请输入手机号" />
- </el-form-item>
- <el-form-item label="身份证" prop="id_card">
- <el-input v-model="editForm.id_card" placeholder="请输入身份证号" />
- </el-form-item>
- <el-form-item label="备注" prop="description">
- <el-input type="textarea" v-model="editForm.description" placeholder="请输入备注" :rows="3" />
- </el-form-item>
- </el-form>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="editDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="handleEditSubmit">提交</el-button>
- </span>
- </template>
- </el-dialog>
- </fs-page>
- </template>
- <script lang="ts" setup name="areas">
- import { onMounted, ref, reactive, computed } from 'vue';
- import { useFs } from '@fast-crud/fast-crud';
- import { createCrudOptions } from './crud';
- import { GetPermission } from './api';
- import { handleColumnPermission } from '/@/utils/columnPermission';
- import { Grid, Clock, ArrowRight, Check, RefreshRight, Briefcase, Message, Upload, Download, Search, Refresh } from '@element-plus/icons-vue';
- import BatchTagsDialog from './components/index.vue';
- import BatchStatusDialog from './components/BatchStatusDialog.vue';
- import smSnotification from './components/smSnotification.vue';
- import { getApplicationStatusSummary,downloadImportTemplate,bulkDelete, updateApplicationStatus, getApplicationDetail, batchUpdateApplicationStatus, updateSmsNotification } from './api';
- import axios from 'axios';
- import { useRoute } from 'vue-router';
- import { ElMessage,ElMessageBox } from 'element-plus';
- import { Session } from '/@/utils/storage';
- import { Delete } from '@element-plus/icons-vue'
- const route = useRoute();
- // 添加标签页相关变量
- const activeTab = ref('application');
- // 待通知列表相关数据
- interface NotificationItem {
- id: number;
- name: string;
- phone: string;
- position_title: string;
- apply_time: string;
- status: string;
- source: string;
- remark: string;
- }
- const notificationList = ref<NotificationItem[]>([]);
- const notificationLoading = ref(false);
- const selectedNotificationRows = ref<NotificationItem[]>([]);
- const notificationSearchForm = reactive({
- name: '',
- phone: '',
- position: '',
- dateRange: [] as string[]
- });
- const notificationPagination = reactive({
- current: 1,
- size: 20,
- total: 0
- });
- // 定义上传地址
- const uploadUrl = `${import.meta.env.VITE_API_URL}/api/system/sms_notifications/import_data/`;
- const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({
- createCrudOptions,
- context: {
- openBatchTagsDialog: (selection: any) => {
- batchTagsDialogRef.value.open(selection);
- },
- openBatchStatusDialog: (selection: any) => {
- batchStatusDialogRef.value.open(selection);
- },
- openSmsNotification:(selection:any)=>{
- smSnotificationRef.value.open(selection)
- },
- selectedRows: [] // 存储选中的行
- }
- });
- const batchTagsDialogRef = ref();
- const batchStatusDialogRef = ref();
- const smSnotificationRef=ref()
- // 状态计数
- const totalCount = ref(0);
- const statusCounts = reactive<Record<number, number>>({
- 1: 0, // 待面试
- 2: 0, // 已面试
- 3: 0, // 已录用
- 4: 0, // 已拒绝
- 5:0//已通知待面试
- });
- // 职位列表
- const showPositionList = ref(true);
- const getStepStyle = computed(() => (step: { name: string }) => {
- return {
- marginLeft: (step.name === '已通过' || step.name === '已淘汰') ? '32px' : '0'
- };
- });
- const positions = ref<Array<{id: number|string, title: string, count?: number}>>([]);
- // 获取职位列表
- const fetchPositions = async () => {
- try {
- const res = await fetch(`${import.meta.env.VITE_API_URL}/api/system/job/list?page=1&limit=100&tenant_id=${Session.get('tenant_id')}`);
- const data = await res.json();
-
- if (data.code === 2000 && data.data ) {
- positions.value = data.data.map((item: any) => ({
- id: item.id,
- title: item.title || item.name || item.job_name,
- count: 0
- }));
- } else {
- console.error('获取职位列表失败:', data.msg || '未知错误');
- }
- } catch (error) {
- console.error('获取职位列表异常:', error);
- // 设置默认职位,以防接口失败
- positions.value = [{ id: 1, title: '流水线操作工', count: 0 }];
- }
- };
- // 获取申请状态统计数据
- const fetchStatusSummary = async () => {
- try {
- const res = await getApplicationStatusSummary();
- const data = res;
- console.log(data)
- if (data.code === 2000 && data.data) {
- totalCount.value = data.data.total || 0;
-
- // 更新状态计数
- if (data.data.status_data && Array.isArray(data.data.status_data)) {
- data.data.status_data.forEach((item: any) => {
- // 注意:API返回的status可能与前端定义的不完全一致,需要映射
- const statusMap: Record<number, number> = {
- 0: 1, // API中的0对应前端的1(待面试)
- 2: 2, // 已面试
- 3: 3, // 已录用
- 4: 4, // 已拒绝
- 1:5//已通知待面试
- };
-
- const frontendStatus = statusMap[item.status];
- if (frontendStatus && frontendStatus in statusCounts) {
- statusCounts[frontendStatus] = item.count;
- }
- });
- } else {
- // 如果没有status_data,则使用单独的字段
- statusCounts[1] = data.data.pending || 0;
- statusCounts[2] = data.data.interviewed || 0;
- statusCounts[3] = data.data.hired || 0;
- statusCounts[4] = data.data.rejected || 0;
- statusCounts[5] = data.data.notified || 0;
- }
- } else {
- console.error('获取申请状态统计失败:', data.message || '未知错误');
- }
- } catch (error) {
- console.error('获取申请状态统计异常:', error);
- }
- };
- // 切换职位列表显示
- const togglePositionList = () => {
- showPositionList.value = !showPositionList.value;
- };
- // 当前激活的节点
- const activeNode = ref('all');
- // 处理树节点点击
- const handleNodeClick = (data: any) => {
- activeNode.value = data.id || 'all';
-
- if (data.type === 'all') {
- try {
- // 尝试重置表单,如果方法存在的话
- const searchRef = crudExpose.getSearchRef();
- if (searchRef && typeof searchRef.resetFields === 'function') {
- searchRef.resetFields();
- }
- } catch (error) {
- console.warn('重置表单失败:', error);
- }
-
- // 无论如何都执行搜索,确保数据刷新
- crudExpose.doSearch({
- form: {}
- });
- } else if (data.type === 'status') {
- // 按状态筛选
- crudExpose.doSearch({
- form: {
- status: data.value
- }
- });
- } else if (data.type === 'position') {
- // 按职位筛选
- crudExpose.doSearch({
- form: {
- position_title: data.value
- }
- });
- }
- };
- // 更新计数
- const updateCounts = (data: any) => {
- if (!data || !data.records) return;
-
- // 统计各职位数量
- // 重置职位计数
- positions.value.forEach(position => {
- position.count = 0;
- });
-
- // 统计职位数量
- data.records.forEach((record: any) => {
- if (record.position_title) {
- const position = positions.value.find(p => p.title === record.position_title);
- if (position) {
- position.count = (position.count || 0) + 1;
- }
- }
- });
- };
- // 处理批量状态修改成功
- const handleBatchStatusSuccess = () => {
- // 刷新数据和状态统计
- crudExpose.doRefresh();
- fetchStatusSummary();
- };
- // 处理导入成功
- const handleImportSuccess = (response: any) => {
- console.log(response)
- if (response.code === 2000) {
- ElMessage.success('导入成功');
- // 刷新数据和状态统计
- crudExpose.doRefresh();
- fetchNotifications();
- } else {
- ElMessage.error(response.message || '导入失败');
- }
- };
- // 处理导入失败
- const handleImportError = (error: any) => {
- console.error('导入失败:', error);
- ElMessage.error('导入失败,请检查文件格式或网络连接');
- };
- // 处理标签页切换
- const handleTabClick = (tab: any) => {
- console.log('切换到标签页:', tab.name);
- // 这里可以根据需要添加标签页切换时的逻辑
- if (tab.name === 'application') {
- // 切换到职位申请标签页时的处理
- console.log('显示职位申请功能');
- } else if (tab.name === 'resume') {
- // 切换到待通知列表标签页时的处理
- console.log('显示待通知列表功能');
- fetchNotificationList();
- }
- };
- // 获取待通知列表数据
- const fetchNotificationList = async () => {
- notificationLoading.value = true;
- try {
- // 构建查询参数
- const params: Record<string, string | number> = {
- page: notificationPagination.current,
- limit: notificationPagination.size,
- status: 0, // 待通知状态
- };
-
- // 添加搜索条件
- if (notificationSearchForm.name) params.name = notificationSearchForm.name;
- if (notificationSearchForm.phone) params.phone = notificationSearchForm.phone;
- if (notificationSearchForm.position) params.position = notificationSearchForm.position;
-
-
- // 调用API获取待通知列表
- const response = await fetch(`${import.meta.env.VITE_API_URL}/api/job/application/list?${new URLSearchParams(params as Record<string, string>)}`, {
- headers: {
- 'Authorization': `Bearer ${Session.get('token')}`,
- 'Content-Type': 'application/json'
- }
- });
-
- const data = await response.json();
-
- if (data.code === 2000 && data.data) {
- notificationList.value = data.data.records || [];
- notificationPagination.total = data.data.total || 0;
- } else {
- ElMessage.error(data.message || '获取待通知列表失败');
- // 如果API调用失败,显示测试数据
- showTestData();
- }
- } catch (error) {
- console.error('获取待通知列表失败:', error);
- ElMessage.error('获取待通知列表失败');
- // 如果API调用失败,显示测试数据
- showTestData();
- } finally {
- notificationLoading.value = false;
- }
- };
- // 显示测试数据
- const showTestData = () => {
- notificationList.value = [];
- notificationPagination.total = 3;
- };
- // 搜索待通知列表
- const handleNotificationSearch = () => {
- notificationPagination.current = 1;
- fetchNotifications();
- };
- // 重置搜索条件
- const handleNotificationReset = () => {
- Object.assign(notificationSearchForm, {
- name: '',
- phone: '',
- position: '',
- dateRange: []
- });
- notificationPagination.current = 1;
- fetchNotifications();
- };
- // 刷新待通知列表
- const handleNotificationRefresh = () => {
- fetchNotificationList();
- };
- // 处理表格选择变化
- const handleNotificationSelectionChange = (selection: NotificationItem[]) => {
- selectedNotificationRows.value = selection;
- };
- // 处理分页大小变化
- const handleNotificationSizeChange = (size: number) => {
- notificationPagination.size = size;
- notificationPagination.current = 1;
- fetchNotifications();
- };
- // 处理当前页变化
- const handleNotificationCurrentChange = (current: number) => {
- notificationPagination.current = current;
- fetchNotifications();
- };
- // 单个通知(此处打开编辑弹窗回显数据)
- const handleNotify = async (row: NotificationItem) => {
- try {
- currentDetailData.value = row;
- editForm.id = (row as any).id;
- editForm.name = (row as any).name || '';
- editForm.phone = (row as any).phone || '';
- editForm.id_card = (row as any).id_card || '';
- editForm.description = (row as any).description || '';
- editDialogVisible.value = true;
- } catch (error) {
- console.error('打开编辑弹窗失败:', error);
- ElMessage.error('操作失败,请重试');
- }
- };
- // 安排面试
- const handleInterview = async (row: NotificationItem) => {
- try {
- const ids = [row.id]
- // 使用API函数更新申请状态为"已安排面试"
- const response = await bulkDelete({
- ids
- });
-
- if (response.code === 2000) {
- ElMessage.success('删除成功');
- // 刷新列表数据
- await fetchNotifications();
- } else {
- ElMessage.error(response.message || '删除失败');
- }
- } catch (error) {
- console.error('删除失败:', error);
- ElMessage.error('删除失败,请重试');
- }
- };
- // 查看详情
- const handleViewDetail = async (row: NotificationItem) => {
- try {
- showDetailDialog(row);
- } catch (error) {
- console.error('获取详情失败:', error);
- ElMessage.error('获取详情失败,请重试');
- }
- };
- // 详情弹窗相关变量
- const detailDialogVisible = ref(false);
- const currentDetailData = ref<any>(null);
- // 编辑弹窗相关变量
- const editDialogVisible = ref(false);
- const editFormRef = ref();
- const editForm = reactive<{ id?: number; name: string; phone: string; id_card: string; description: string }>({
- id: undefined,
- name: '',
- phone: '',
- id_card: '',
- description: ''
- });
- const editRules = {
- name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
- phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
- id_card: [{ required: true, message: '请输入身份证号', trigger: 'blur' }],
- description: [{ required: true, message: '请输入备注', trigger: 'blur' }]
- };
- // 显示详情弹窗
- const showDetailDialog = (detailData: any) => {
- currentDetailData.value = detailData;
- detailDialogVisible.value = true;
- };
- // 关闭详情弹窗
- const handleDetailDialogClose = () => {
- detailDialogVisible.value = false;
- currentDetailData.value = null;
- };
- // 编辑详情
- const handleEditDetail = () => {
- if (currentDetailData.value) {
- // 打开编辑弹窗并回显当前数据
- editForm.id = (currentDetailData.value as any).id;
- editForm.name = (currentDetailData.value as any).name || '';
- editForm.phone = (currentDetailData.value as any).phone || '';
- editForm.id_card = (currentDetailData.value as any).id_card || '';
- editForm.description = (currentDetailData.value as any).description || '';
- editDialogVisible.value = true;
- }
- };
- // 关闭编辑弹窗
- const handleEditDialogClose = () => {
- editDialogVisible.value = false;
- };
- // 提交编辑
- const handleEditSubmit = () => {
- (editFormRef.value as any).validate(async (valid: boolean) => {
- if (!valid) return;
- try {
- if (!editForm.id) {
- ElMessage.error('缺少记录ID');
- return;
- }
- const res = await updateSmsNotification(editForm.id, {
- name: editForm.name,
- phone: editForm.phone,
- id_card: editForm.id_card,
- description: editForm.description,
- });
- if ((res as any).code === 2000) {
- ElMessage.success('更新成功');
- editDialogVisible.value = false;
- // 刷新列表
- await fetchNotifications();
- } else {
- ElMessage.error((res as any).message || '更新失败');
- }
- } catch (e) {
- console.error(e);
- ElMessage.error('更新失败,请重试');
- }
- });
- };
- // 获取状态文本
- const getStatusText = (status: number) => {
- const statusMap: Record<number, string> = {
- 0: '待通知',
- 1: '已通知待面试',
- 2: '已面试待处理',
- 3: '已录用',
- 4: '拒不录用'
- };
- return statusMap[status] || '未知状态';
- };
- // 获取状态标签类型
- const getStatusTagType = (status: number) => {
- const typeMap: Record<number, string> = {
- 0: 'warning',
- 1: 'info',
- 2: 'primary',
- 3: 'success',
- 4: 'danger'
- };
- return typeMap[status] || 'info';
- };
- // 批量通知
- const handleBatchNotify = () => {
- if (selectedNotificationRows.value.length === 0) {
- ElMessage.warning('请选择要通知的申请');
- return;
- }
- console.log(selectedNotificationRows.value);
- smSnotificationRef.value.open(selectedNotificationRows.value);
- };
- // 批量删除方法
- const handleBatchDelete = async () => {
- if (!selectedNotificationRows.value || selectedNotificationRows.value.length === 0) {
- ElMessage.warning('请选择要删除的记录')
- return
- }
- try {
- await ElMessageBox.confirm(
- `确定要删除选中的 ${selectedNotificationRows.value.length} 条记录吗?`,
- '确认删除',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }
- )
- const ids = selectedNotificationRows.value.map(row => row.id)
- const response = await bulkDelete({ ids })
- console.log(response)
- if (response.code==2000) {
- ElMessage.success('批量删除成功')
- // 刷新数据
- await fetchNotifications()
- // 清空选中状态
- selectedNotificationRows.value = []
- } else {
- const errorData = await response.json()
- ElMessage.error(errorData.message || '删除失败')
- }
- } catch (error) {
- if (error !== 'cancel') {
- console.error('批量删除失败:', error)
- ElMessage.error('删除失败,请重试')
- }
- }
- }
- // 批量更新状态方法
- const handleBatchUpdateStatus = async (newStatus: number, action: string) => {
- if (!selectedNotificationRows.value || selectedNotificationRows.value.length === 0) {
- ElMessage.warning('请选择要更新的记录')
- return
- }
- try {
- const statusText = {
- 1: '已通知',
- 2: '已安排面试',
- 3: '已录用',
- 4: '拒不录用'
- }[newStatus] || '未知状态'
- await ElMessageBox.confirm(
- `确定要将选中的 ${selectedNotificationRows.value.length} 条记录状态更新为"${statusText}"吗?`,
- '确认更新',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }
- )
- const ids = selectedNotificationRows.value.map(row => row.id)
- const response = await batchUpdateApplicationStatus({
- ids,
- status: newStatus,
- action,
- note: `批量更新为${statusText}`
- })
- if (response.code === 2000) {
- ElMessage.success(`批量更新状态成功,已更新为${statusText}`)
- // 刷新数据
- await fetchNotificationList()
- // 清空选中状态
- selectedNotificationRows.value = []
- } else {
- ElMessage.error(response.message || '批量更新状态失败')
- }
- } catch (error) {
- if (error !== 'cancel') {
- console.error('批量更新状态失败:', error)
- ElMessage.error('批量更新状态失败,请重试')
- }
- }
- }
- // 下载模板
- const handleBatchInterview = async () => {
- try {
- const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/system/sms_notifications/download_template/`, {
- headers: {
- 'Authorization': `JWT ${Session.get('token')}`
- },
- responseType: 'blob' // 确保响应类型为 blob,以便下载文件
- });
- // 创建一个 URL 对象
- const url = window.URL.createObjectURL(new Blob([response.data]));
- const link = document.createElement('a');
- link.href = url;
- link.setAttribute('download', 'template.xlsx'); // 设置下载文件名
- document.body.appendChild(link);
- link.click();
- link.remove();
- } catch (error) {
- console.error('下载模板失败:', error);
- }
- };
- // 页面打开后获取列表数据
- onMounted(async () => {
- // 获取职位列表
- await fetchPositions();
-
- // 获取申请状态统计
- await fetchStatusSummary();
-
- // 设置列权限
- const newOptions = await handleColumnPermission(GetPermission, crudOptions);
-
- // 添加数据加载后的回调,用于更新职位计数
- if (newOptions && newOptions.crudOptions && newOptions.crudOptions.request) {
- const originalPageRequest = newOptions.crudOptions.request.pageRequest;
- newOptions.crudOptions.request.pageRequest = async (query: any) => {
- const res = await originalPageRequest(query);
- updateCounts(res.data);
- return res;
- };
- }
-
- // 重置crudBinding
- resetCrudOptions(newOptions);
- /* getPositionList() */
- // 处理URL参数
- const urlStatus = route.query.status;
- console.log(urlStatus)
- if (urlStatus) {
- // 根据URL状态参数设置活动节点
- const statusValue = Number(urlStatus);
- switch (statusValue) {
- case 0:
- activeNode.value = 'status-0';
- break;
- case 1:
- activeNode.value = 'status-1';
- break;
- case 2:
- activeNode.value = 'status-2';
- break;
- case 3:
- activeNode.value = 'status-3';
- break;
- case 4:
- activeNode.value = 'status-4';
- break;
- default:
- activeNode.value = 'all';
- }
-
- // 使用doSearch进行带状态参数的搜索
- crudExpose.doSearch({
- form: {
- status: statusValue
- }
- });
- } else {
- // 没有URL参数时,执行普通刷新
- crudExpose.doRefresh();
- }
- fetchNotifications()
- });
- const fetchNotifications=async()=> {
- notificationLoading.value = true; // 开始加载
- try {
- const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/system/sms_notifications/`,{
- params:{
- page: notificationPagination.current,
- limit: notificationPagination.size,
- name: notificationSearchForm.name,
- phone: notificationSearchForm.phone,
- },
- headers: {
- 'Authorization': `JWT ${Session.get('token')}`
- }
- });
- console.log(response.data)
- if(response.data.code === 2000){
- notificationList.value = response.data.data; // 更新通知列表
- notificationPagination.total = response.data.total || 0;
- }else{
- ElMessage.error(response.data.message || '获取通知列表失败');
- }
- } catch (error) {
- console.error('获取通知列表失败:', error);
- } finally {
- notificationLoading.value = false; // 结束加载
- }
- }
- const getPositionList = async () => {
- try {
- // 从环境变量或配置中获取
- const appid = 'wxc9655eeaa3223b75';
- const secret = 'c0f031b6e07ded1928fded435a913902';
-
- if (!appid || !secret) {
- throw new Error('微信配置信息不完整');
- }
- const response = await fetch(`${import.meta.env.VITE_API_URL}/wechat/generatescheme`);
- const data = await response.json();
-
- if (data.access_token) {
- return data.access_token;
- } else {
- throw new Error(data.errmsg || '获取access_token失败');
- }
- } catch (error) {
- console.error('获取微信access_token失败:', error);
- ElMessage.error('获取微信access_token失败');
- throw error;
- }
- }
- </script>
- <style scoped>
- .job-application-container {
- display: flex;
- flex-direction: column;
- height: 100%;
- margin-top: 15px;
- overflow: hidden;
- }
- /* 标签页容器样式 */
- .tabs-container {
- background-color: #fff;
- border-radius: 8px;
- margin-bottom: 10px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- }
- .tabs-container :deep(.el-tabs__header) {
- margin: 0;
- padding: 0 20px;
- height: auto;
- }
- .tabs-container :deep(.el-tabs__nav-wrap) {
- padding: 0;
- }
- .tabs-container :deep(.el-tabs__item) {
- height: 50px;
- line-height: 50px;
- font-size: 14px;
- font-weight: 500;
- }
- .tabs-container :deep(.el-tabs__content) {
- padding: 20px;
- }
- /* 标签页内容样式 */
- .tab-content {
- display: flex;
- height: calc(100vh - 200px);
- }
- /* 简历库样式 */
- .resume-container {
- width: 100%;
- padding: 20px;
- }
- .resume-header {
- text-align: center;
- margin-bottom: 40px;
- }
- .resume-header h3 {
- font-size: 24px;
- color: #303133;
- margin-bottom: 10px;
- }
- .resume-header p {
- color: #909399;
- font-size: 14px;
- }
- .resume-content {
- display: flex;
- justify-content: center;
- align-items: center;
- min-height: 300px;
- }
- /* 待通知列表样式 */
- .notification-container {
- width: 100%;
- padding: 0;
- }
- /* 详情弹窗样式 */
- .detail-content {
- padding: 20px 0;
- }
- .detail-content .el-descriptions {
- margin-bottom: 20px;
- }
- .detail-content .el-descriptions__label {
- font-weight: 600;
- color: #606266;
- }
- .detail-content .el-descriptions__content {
- color: #303133;
- }
- .dialog-footer {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
- }
- /* 状态标签样式 */
- .el-tag {
- font-weight: 500;
- }
- .search-form {
- background-color: #fff;
- padding: 20px;
- border-radius: 8px;
- }
- .action-bar {
- display: flex;
- justify-content: space-between;
- align-items: center;
- background-color: #fff;
- padding: 16px 20px;
- border-radius: 8px;
- }
- .left-actions {
- display: flex;
- gap: 12px;
- }
- .right-actions {
- display: flex;
- gap: 12px;
- }
- .table-container {
- background-color: #fff;
- border-radius: 8px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- overflow: hidden;
- }
- .pagination-container {
- background-color: #fff;
- padding: 16px 20px;
- border-radius: 8px;
- /* display: flex;
- justify-content: center; */
- }
- /* 原有的侧边栏和内容样式 */
- .sidebar {
- overflow: auto;
- margin-top: 0;
- width: 200px;
- margin-right: 5px;
- flex-shrink: 0;
- background-color: #fff;
- border-right: 1px solid #ebeef5;
- border-radius: 10px;
- }
- .tree-container {
- padding: 8px 0;
- }
- .tree-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 10px 16px;
- cursor: pointer;
- transition: background-color 0.3s;
- width: 185px;
- }
- .item-content span{
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- width: 100%;
- }
- .tree-item:hover {
- background-color: #f5f7fa;
- }
- .tree-item.active {
- background-color: #f0f7ff;
- color: #409eff;
- border-right: 2px solid #409eff;
- }
- .item-content {
- display: flex;
- align-items: center;
- gap: 8px;
- min-width: 0;
- flex: 1;
- }
- .item-count {
- font-size: 12px;
- color: #909399;
- }
- .category-title {
- display: flex;
- align-items: center;
- padding: 12px 16px;
- margin-top: 8px;
- font-weight: 500;
- border-top: 1px solid #ebeef5;
- border-bottom: 1px solid #ebeef5;
- gap: 8px;
- cursor: pointer;
- }
- .expand-icon {
- margin-left: auto;
- transition: transform 0.3s;
- }
- .expand-icon.is-expanded {
- transform: rotate(90deg);
- }
- .position-list {
- padding-left: 8px;
- }
- .content {
- flex: 1;
- overflow: hidden;
- }
- .action-button-wrapper {
- display: flex;
- align-items: center;
- height: 100%;
- margin-left: 8px;
- }
- .action-button-wrapper :deep(.el-upload) {
- display: flex;
- align-items: center;
- }
- .action-button-wrapper :deep(.el-button) {
- margin: 0;
- }
- :deep(.upload-demo){
- height: 24px;
- }
- </style>
|