yangg hai 1 semana
pai
achega
43a3b24e94

+ 5 - 5
src/views/JobApplication/list/components/BatchStatusDialog.vue

@@ -9,11 +9,11 @@
     <el-form :model="form" label-width="100px" ref="formRef" :rules="rules">
       <el-form-item label="新状态" prop="new_status">
         <el-select v-model="form.new_status" placeholder="请选择新状态" style="width: 100%">
-          <el-option :value="0" label="待面试" />
-          <el-option :value="1" label="已通知面试" />
-          <el-option :value="2" label="已面试" />
-          <el-option :value="3" label="已录用" />
-          <el-option :value="4" label="已拒绝" />
+          <el-option :value="0" label="待通知" />
+          <el-option :value="1" label="已通知面试" />
+          <el-option :value="3" label="录用" />
+          <el-option :value="4" label="拒绝" />
+          <el-option :value="5" label="拒绝并加入人才库" />
         </el-select>
       </el-form-item>
       <el-form-item label="备注" prop="note">

+ 4 - 3
src/views/JobApplication/list/crud.tsx

@@ -509,11 +509,12 @@ export const createCrudOptions = function ({ crudExpose, context}: CreateCrudOpt
 					},
 					dict: dict({
 						data: [
-							{ value: 0, label: '待面试' },
-							{ value: 1, label: '已通知面试' },
-							{ value: 2, label: '已面试' },
+							{ value: 0, label: '待通知' },
+							{ value: 1, label: '已通知面试' },
+							{ value: 2, label: '已面试待处理' },
 							{ value: 3, label: '已录用' },
 							{ value: 4, label: '已拒绝' },
+							{ value: 5, label: '拒绝并加入人才库' },
 						],
 					}),
 				},

+ 2 - 2
src/views/digitalHuman/list/index.vue

@@ -371,9 +371,9 @@ const voiceList = ref([
 	{ label: '女声稳重风格', value: 'female2' },
 	{ label: '男声清新风格', value: 'male1' },
 	{ label: '男声稳重风格', value: 'male2' }, */
-	{ label: '女声中英文', value: 'loongstella' },
+/* 	{ label: '女声中英文', value: 'loongstella' }, */
 	{ label: '男声中文', value: 'longxiang' },
-	{ label: '男童声中英文', value: 'longjielidou' },
+/* 	{ label: '男童声中英文', value: 'longjielidou' }, */
 ]);
 /* 获取语音列表 */
 const getVoiceList = async () => {

+ 9 - 0
src/views/position/Jobcompetency/api.ts

@@ -62,3 +62,12 @@ export function GetPositionList(query: UserPageQuery) {
 		params: {...query,tenant_id:1},
 	});
 }
+
+/* 职位列表 */
+export function GetJobList(query: UserPageQuery) {
+	return request({
+		url: '/api/system/job/list',
+		method: 'get',
+		params: {...query,tenant_id:1},
+	});
+}

+ 4 - 0
src/views/position/Jobcompetency/crud.tsx

@@ -12,6 +12,10 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 	
 	const pageRequest = async (query: any) => {
 		try {
+			// 如果有职位ID,添加到查询参数中
+			if (query.form && query.form.job_id) {
+				query.job_id = query.form.job_id;
+			}
 			return await api.GetList(query);
 		} catch (error) {
 			console.error('Failed to fetch list:', error);

+ 72 - 3
src/views/position/Jobcompetency/index.vue

@@ -1,21 +1,64 @@
 <template>
 	<fs-page>
-		<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
+		<div class="job-competency-container">
+			<div class="sidebar">
+				<div class="tree-container">
+					<JobIndex
+						:tree-data="documentTreeData"
+						@tree-click="handleTreeClick"
+					/>
+				</div>
+			</div>
+			<div class="content">
+				<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
+			</div>
+		</div>
 	</fs-page>
 </template>
 
 <script lang="ts" setup name="areas">
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import { useFs } from '@fast-crud/fast-crud';
 import { createCrudOptions } from './crud';
-import { GetPermission } from './api';
+import { GetPermission, GetJobList } from './api';
 import { handleColumnPermission } from '/@/utils/columnPermission';
+import JobIndex from './jobIndex.vue';
 
 const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({ createCrudOptions });
 
+// 文档树数据
+const documentTreeData = ref([]);
+// 当前选中的职位ID
+const selectedJobId = ref('');
+
+// 获取职位树数据
+const getTreeData = () => {
+	GetJobList({}).then((ret: any) => {
+		documentTreeData.value = ret.data;
+	});
+};
+
+// 处理树节点点击
+const handleTreeClick = (record: any) => {
+	selectedJobId.value = record?.id || '';
+	
+	// 构建搜索参数
+	const searchParams = {
+		form: {
+			position_id: selectedJobId.value
+		}
+	};
+	
+	// 执行搜索
+	crudExpose.doSearch(searchParams);
+};
+
 // 页面打开后获取列表数据
 onMounted(async () => {
 	try {
+		// 获取职位树数据
+		getTreeData();
+		
 		// 设置列权限
 		const newOptions = await handleColumnPermission(GetPermission, crudOptions);
 		// 重置crudBinding
@@ -29,3 +72,29 @@ onMounted(async () => {
 	}
 });
 </script>
+
+<style scoped>
+.job-competency-container {
+	display: flex;
+	height: 100%;
+}
+
+.sidebar {
+	margin-top: 10px;
+	width: 200px;
+	margin-right: 5px;
+	flex-shrink: 0;
+	background-color: #fff;
+	border-right: 1px solid #ebeef5;
+	border-radius: 10px;
+}
+
+.tree-container {
+	padding: 8px 0;
+}
+
+.content {
+	flex: 1;
+	overflow: hidden;
+}
+</style>

+ 139 - 0
src/views/position/Jobcompetency/jobIndex.vue

@@ -0,0 +1,139 @@
+<template>
+    <div class="document-tree-container">
+      <div class="document-tree-header">
+        <span class="document-tree-title">职位</span>
+       <!--  <el-button type="primary" size="small" @click="handleAddDocument">
+          <el-icon><Plus /></el-icon>添加分类
+        </el-button> -->
+      </div>
+      <el-tree
+        ref="treeRef"
+        :data="treeData"
+        node-key="id"
+        :props="defaultProps"
+        default-expand-all
+        highlight-current
+        @node-click="handleNodeClick"
+      >
+        <template #default="{ node, data }">
+          <div class="custom-tree-node">
+            <div class="node-label">
+              <el-icon><Folder /></el-icon>
+              <span class="ml-2">{{ data.title }}</span>
+            </div>
+          <!--   <div class="node-actions" v-if="data.id">
+              <el-button type="primary" link size="small" @click.stop="handleEdit(data)">
+                <el-icon><Edit /></el-icon>
+              </el-button>
+              <el-button type="danger" link size="small" @click.stop="handleDelete(node, data)">
+                <el-icon><Delete /></el-icon>
+              </el-button>
+            </div> -->
+          </div>
+        </template>
+      </el-tree>
+    </div>
+  </template>
+  
+  <script lang="ts" setup>
+  import { ref, defineProps, defineEmits } from 'vue';
+  import { Plus, Folder, Edit, Delete } from '@element-plus/icons-vue';
+  
+  const props = defineProps({
+    treeData: {
+      type: Array,
+      default: () => []
+    }
+  });
+  
+  const emit = defineEmits(['treeClick', 'updateDocument', 'deleteDocument']);
+  
+  const treeRef = ref(null);
+  
+  const defaultProps = {
+    children: 'children',
+    label: 'name'
+  };
+  
+  // 处理节点点击
+  const handleNodeClick = (data: any) => {
+    emit('treeClick', data);
+  };
+  
+  // 添加文档分类
+  const handleAddDocument = () => {
+    emit('updateDocument', 'add');
+  };
+  
+  // 编辑文档分类
+  const handleEdit = (data: any) => {
+    emit('updateDocument', 'update', data);
+  };
+  
+  // 删除文档分类
+  const handleDelete = (node: any, data: any) => {
+    emit('deleteDocument', data.id, () => {
+      // 回调函数,删除成功后可以执行
+    });
+  };
+  
+  defineExpose({
+    treeRef
+  });
+  </script>
+  
+  <style lang="scss" scoped>
+  .document-tree-container {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+  }
+  
+  .document-tree-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+  }
+  
+  .document-tree-title {
+    padding-left: 10px;
+    font-size: 16px;
+    font-weight: bold;
+  }
+  
+  .custom-tree-node {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-right: 8px;
+  }
+  
+  .node-label {
+    display: flex;
+    align-items: center;
+    flex: 1;
+    min-width: 0;
+    position: relative;
+    
+    .ml-2 {
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      flex: 1;
+    }
+  }
+  
+  
+  .node-actions {
+    display: none;
+  }
+  
+  .custom-tree-node:hover .node-actions {
+    display: flex;
+    overflow: hidden;
+       white-space: nowrap;
+       text-overflow: ellipsis;
+  }
+  </style> 

+ 100 - 15
src/views/position/create/index.vue

@@ -97,20 +97,6 @@
             <el-option label="交通补贴" value="交通补贴" />
           </el-select>
         </el-form-item>
-        
-        <el-form-item label="职位要求" prop="requirements">
-          <div class="editor-container">
-            <div ref="quillEditor" class="quill-editor"></div>
-          </div>
-        </el-form-item>
-        
-        <!-- 添加职位描述字段 -->
-        <el-form-item label="职位描述" prop="description">
-          <div class="editor-container">
-            <div ref="descriptionEditor" class="quill-editor"></div>
-          </div>
-        </el-form-item>
-        
         <!-- 添加职位薪资字段 -->
         <el-form-item label="职位薪资" prop="salary_range">
           <div class="salary-range-container">
@@ -205,6 +191,20 @@
             value-format="YYYY-MM-DD HH:mm:ss"
           />
         </el-form-item>
+        <el-form-item label="职位要求" prop="requirements">
+          <div class="editor-container">
+            <div ref="quillEditor" class="quill-editor"></div>
+          </div>
+        </el-form-item>
+        
+        <!-- 添加职位描述字段 -->
+        <el-form-item label="职位描述" prop="description">
+          <div class="editor-container">
+            <div ref="descriptionEditor" class="quill-editor"></div>
+          </div>
+        </el-form-item>
+        
+        
         
         <el-form-item>
           <el-button type="primary" @click="submitForm">保存</el-button>
@@ -216,7 +216,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
+import { ref, reactive, onMounted, onBeforeUnmount, watch } from 'vue';
 import { useRouter } from 'vue-router';
 import { ElMessage } from 'element-plus';
 import * as api from '../list/api';
@@ -226,6 +226,17 @@ import 'quill/dist/quill.snow.css'; // 引入样式
 import axios from 'axios';
 import { getPCAData } from '../../../utils/pcaData';
 
+// 添加防抖函数
+const debounce = (fn: Function, delay: number) => {
+  let timer: any = null;
+  return (...args: any[]) => {
+    if (timer) clearTimeout(timer);
+    timer = setTimeout(() => {
+      fn(...args);
+    }, delay);
+  };
+};
+
 const router = useRouter();
 const formRef = ref<FormInstance>();
 
@@ -363,6 +374,80 @@ const updateSalaryRange = () => {
   }
 };
 
+// 添加生成职位描述的方法
+const generatePositionDescription = async () => {
+  // 检查必要字段是否已填写
+  if (!formData.title || !formData.work_experience_required || 
+      !formData.education_required || !formData.salary_range) {
+    return;
+  }
+
+  try {
+    // 调用生成描述接口
+    const response = await api.GeneratePositionDescription({
+      position_title: formData.title,
+      experience_req: formData.work_experience_required,
+      education_req: formData.education_required,
+      salary_range: formData.salary_range,
+      tenant_id: 1
+    });
+
+    if (response.code === 2000 && response.data) {
+      // 格式化返回的数据
+      const formattedDescription = `
+
+<ul>
+${response.data.responsibilities.map((item: string) => `<li>${item}</li>`).join('\n')}
+</ul>
+
+
+<p><strong>经验要求:</strong>${response.data.experience}</p>
+<p><strong>学历要求:</strong>${response.data.education}</p>
+
+
+<ul>
+${response.data.skills.map((item: string) => `<li>${item}</li>`).join('\n')}
+</ul>
+
+
+<ul>
+${response.data.qualities.map((item: string) => `<li>${item}</li>`).join('\n')}
+</ul>
+
+
+<ul>
+${response.data.bonus.map((item: string) => `<li>${item}</li>`).join('\n')}
+</ul>`;
+
+      // 更新编辑器内容
+      if (descEditor) {
+        descEditor.clipboard.dangerouslyPasteHTML(formattedDescription);
+        formData.description = formattedDescription;
+      }
+    }
+  } catch (error) {
+    console.error('生成职位描述失败:', error);
+    ElMessage.error('生成职位描述失败,请稍后重试');
+  }
+};
+
+// 创建防抖后的生成函数
+const debouncedGenerateDescription = debounce(generatePositionDescription, 1000);
+
+// 监听相关字段变化
+watch(
+  () => [
+    formData.title,
+    formData.work_experience_required,
+    formData.education_required,
+    formData.salary_range
+  ],
+  () => {
+    debouncedGenerateDescription();
+  },
+  { deep: true }
+);
+
 // 在 setup 中添加胜任力相关的响应式数据
 const competencyTags = ref<CompetencyTag[]>([]);
 const competencyDescriptions = ref<Record<number, string>>({});

+ 88 - 4
src/views/position/detail/index.vue

@@ -375,9 +375,9 @@
         <div v-for="(step, index) in recruitmentProcess" :key="step.id">
           <!-- 添加在每个步骤上方的加号按钮 -->
           <div class="add-process-btn-top" v-if="index === 0 || true">
-           <!--  <el-button type="text" @click="showStepOptions(index, $event)">
+            <el-button type="text" @click="showStepOptions(index, $event)">
               <el-icon><Plus /></el-icon>
-            </el-button> -->
+            </el-button>
           </div>
           
           <div 
@@ -1940,6 +1940,81 @@
         </span>
       </template>
     </el-dialog>
+    <!-- 绑定问题 -->
+    <el-dialog
+      title="选择题目"
+      v-model="QuestionSelectDialog"
+      width="70%"
+      :close-on-click-modal="false"
+      class="question-select-dialog"
+    >
+      <div class="question-select-content">
+        <!-- 搜索栏 -->
+        <div class="search-bar">
+          <div class="search-inputs">
+            <el-input
+              v-model="questionSearchKeyword"
+              placeholder="请输入关键词搜索题目"
+              clearable
+              @clear="handleQuestionSearch"
+              @keyup.enter="handleQuestionSearch"
+              style="width: 300px; margin-right: 10px;"
+            >
+              <template #append>
+                <el-button @click="handleQuestionSearch">
+                  <el-icon><Search /></el-icon>
+                </el-button>
+              </template>
+            </el-input>
+            <el-select 
+              v-model="questionListQuery.question_form" 
+              placeholder="题目类型"
+              clearable
+              @change="handleQuestionSearch"
+              style="width: 120px;"
+            >
+              <el-option label="开放问题" :value="0" />
+              <el-option label="单选题" :value="1" />
+            </el-select>
+          </div>
+        </div>
+
+        <!-- 题目列表 -->
+        <div class="question-lists">
+          <el-table
+            :data="questionList"
+            style="width: 100%"
+            @selection-change="handleQuestionSelectionChange"
+          >
+            <el-table-column type="selection" width="55" />
+            <el-table-column prop="question" label="题目标题" />
+            <el-table-column prop="question_form_name" label="题目类型" width="100"/>
+            <!-- <el-table-column prop="content" label="面试内容" show-overflow-tooltip />
+            <el-table-column prop="target" label="对话目标" show-overflow-tooltip /> -->
+          </el-table>
+
+          <!-- 分页 -->
+          <div class="pagination-container">
+            <el-pagination
+              v-model:current-page="questionListQuery.page"
+              v-model:page-size="questionListQuery.pageSize"
+              :total="totalQuestions"
+              :page-sizes="[10, 20, 50, 100]"
+              layout="total, sizes, prev, pager, next"
+              @size-change="handleQuestionSizeChange"
+              @current-change="handleQuestionPageChange"
+            />
+          </div>
+        </div>
+      </div>
+      
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="cancelQuestionSelect">取消</el-button>
+          <el-button type="primary" @click="confirmQuestionSelect" style="background-color: #ed7d31;border-color: #ed7d31;">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -2042,6 +2117,8 @@ const positionData = reactive({
 // 招聘流程数据 - 修改为普通数组而非 ref 对象
 const recruitmentProcess = reactive([
   { id: 5, name: '资料收集', description: '资料收集', active: true },
+  { id: 7, name: '常识问题', description: '常识问题', active: true },
+  { id: 6, name: '心理问题', description: '心理问题', active: true },
   { id: 2, name: 'AI视频', description: 'AI视频', active: true },
   { id: 1, name: '待核验', description: '待核验', active: true },
   { id: 3, name: '已通过', description: '已通过', active: true },
@@ -2059,9 +2136,15 @@ const processStepOptions = [
   { label: 'AI实时对话', value: 'ai_chat' },
   { label: '资料收集', value: 'data_collection' },
   { label: '简历收集', value: 'resume_collection' },
+  { label: '心理问题', value: 'psychological_problem' },
+  { label: '常识问题', value: 'common_sense' },
   /* { label: '代码测试', value: 'code_test' },
   { label: '打字测试', value: 'typing_test' } */
 ];
+/* 选择问题 */
+const QuestionSelectDialog = ref(false);
+
+
 
 const handleAddCompetency = () => {
   competencyLoading.value = true;
@@ -2626,7 +2709,7 @@ const getJobTypeText = (type: number) => {
 
 // 显示选项菜单
 const showStepOptions = (index: number, event: MouseEvent) => {
-  currentAddIndex.value = index;
+  /* currentAddIndex.value = index;
   showOptionsMenu.value = true;
   
   // 计算菜单位置 - 获取按钮元素
@@ -2649,7 +2732,8 @@ const showStepOptions = (index: number, event: MouseEvent) => {
   }
   
   // 阻止事件冒泡
-  event.stopPropagation();
+  event.stopPropagation(); */
+  
 };
 // 修改添加选定类型的步骤方法
 const addSelectedStepType = async (type: string, label: string) => {

+ 13 - 0
src/views/position/list/api.ts

@@ -87,4 +87,17 @@ export function batch_publish(data:any) {
 			'Content-Type': 'application/json'
 		}
 	});
+}
+
+/* AI生成职位描述 */
+export function GeneratePositionDescription(data:any) {
+	return request({
+		url: '/api/ai/generate_job_description',
+		method: 'post',
+		data: JSON.stringify(data),
+		headers: {
+			'Content-Type': 'application/json',
+			'Authorization': 'JWT ' +Session.get('token')
+		}
+	});
 }

+ 1 - 1
src/views/questionBank/positionList/components/DocumentTreeCom/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="document-tree-container">
     <div class="document-tree-header">
-      <span class="document-tree-title">职位</span>
+      <span class="document-tree-title">题库归属</span>
      <!--  <el-button type="primary" size="small" @click="handleAddDocument">
         <el-icon><Plus /></el-icon>添加分类
       </el-button> -->

+ 84 - 0
src/views/talent/overseas/api.ts

@@ -0,0 +1,84 @@
+import { request } from '/@/utils/service';
+import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
+
+export const apiPrefix = '/api/system/talent_pool/list';
+export function GetList(query: UserPageQuery) {
+	return request({
+		url: apiPrefix,
+		method: 'get',
+		params: {...query,tenant_id:1},
+	});
+}
+export function GetObj(id: InfoReq) {
+	return request({
+		url: apiPrefix + id,
+		method: 'get',
+	});
+}
+
+export function AddObj(obj: AddReq) {
+	return request({
+		url: apiPrefix,
+		method: 'post',
+		data: obj,
+	});
+}
+
+export function UpdateObj(obj: EditReq) {
+	return request({
+		url: apiPrefix + obj.id + '/',
+		method: 'put',
+		data: obj,
+	});
+}
+
+export function DelObj(id: DelReq) {
+	return request({
+		url:`/api/system/job_applications/${id}/?tenant_id=1`,
+		method: 'delete',
+		data: { id },
+	});
+}
+export function GetPermission(query: UserPageQuery) {
+    return request({
+        url: apiPrefix,
+        method: 'get',
+		params: {...query,tenant_id:1}
+    });
+}
+
+export function BulkUpdateStatus(data: {
+	application_ids: number[];
+	new_status: number;
+	note?: string;
+	tenant_id: string;
+}) {
+	return request({
+		url: '/api/system/job_applications/bulk_update_status/',
+		method: 'post',
+		data
+	});
+}
+
+export function updateBatchStatus(data: any) {
+	return request({
+		url: '/api/system/job_applications/bulk_update_status/',
+		method: 'post',
+		data
+	});
+}
+
+/* export function updateBatchTags(data) {
+	return request({
+		url: '/job-application/batch-update-tags',
+		method: 'post',
+		data
+	});
+} */
+	export function getApplicationStatusSummary() {
+		return request({
+			url: '/api/system/job/application_status_summary/?tenant_id=1',
+			method: 'get',
+		});
+	}
+	

+ 153 - 0
src/views/talent/overseas/components/index.vue

@@ -0,0 +1,153 @@
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    title="批量操作"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+  >
+    <el-form :model="form" label-width="100px">
+     <!--  <el-form-item label="操作类型">
+        <el-select v-model="form.operationType" placeholder="请选择操作类型" style="width: 100%">
+          <el-option label="修改状态" value="status"></el-option>
+          <el-option label="添加标签" value="tags"></el-option>
+        </el-select>
+      </el-form-item> -->
+
+      <el-form-item v-if="form.operationType === 'status'" label="状态">
+        <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
+          <el-option label="待处理" :value="0"></el-option>
+          <el-option label="已通知面试" :value="1"></el-option>
+          <el-option label="已面试" :value="2"></el-option>
+          <el-option label="已录用" :value="3"></el-option>
+          <el-option label="已拒绝" :value="4"></el-option>
+        </el-select>
+      </el-form-item>
+
+      <el-form-item v-if="form.operationType === 'tags'" label="标签">
+        <el-select
+          v-model="form.tags"
+          multiple
+          filterable
+          allow-create
+          default-first-option
+          placeholder="请选择或创建标签"
+          style="width: 100%"
+        >
+          <el-option
+            v-for="tag in tagOptions"
+            :key="tag.value"
+            :label="tag.label"
+            :value="tag.value"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive } from 'vue';
+import { ElMessage } from 'element-plus';
+import { updateBatchStatus } from '../api';
+
+const props = defineProps({
+  crudExpose: {
+    type: Object,
+    required: true
+  }
+});
+
+const dialogVisible = ref(false);
+const loading = ref(false);
+const selectedItems = ref([]);
+
+const form = reactive({
+  operationType: 'status',
+  status: undefined,
+  tags: []
+});
+
+// 标签选项,可以根据实际需求从API获取
+const tagOptions = ref([
+  { value: '优质候选人', label: '优质候选人' },
+  { value: '有经验', label: '有经验' },
+  { value: '应届毕业生', label: '应届毕业生' },
+  { value: '技能熟练', label: '技能熟练' }
+]);
+
+// 打开对话框
+const open = (selection: any) => {
+  if (!selection || selection.length === 0) {
+    ElMessage.warning('请至少选择一条记录');
+    return;
+  }
+  
+  selectedItems.value = selection;
+  dialogVisible.value = true;
+  
+  // 重置表单
+  form.operationType = 'status';
+  form.status = undefined;
+  form.tags = [];
+};
+
+// 提交处理
+const handleSubmit = async () => {
+  
+  if (form.operationType === 'tags' && (!form.tags || form.tags.length === 0)) {
+    ElMessage.warning('请至少选择一个标签');
+    return;
+  }
+  
+  try {
+    loading.value = true;
+    const application_ids = selectedItems.value.map((item: any) => item.id);
+    
+    if (form.operationType === 'status') {
+      await updateBatchStatus({
+        application_ids,
+        new_status: form.status,
+        tenant_id: 1
+      });
+      ElMessage.success('批量更新状态成功');
+    } /* else if (form.operationType === 'tags') {
+      await updateBatchTags({
+        ids,
+        tags: form.tags
+      });
+      ElMessage.success('批量更新标签成功');
+    } */
+    
+    // 关闭对话框
+    dialogVisible.value = false;
+    
+    // 刷新列表
+    props.crudExpose.doRefresh();
+  } catch (error) {
+    console.error('批量操作失败:', error);
+    ElMessage.error('操作失败,请重试');
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 暴露方法给父组件
+defineExpose({
+  open
+});
+</script>
+
+<style scoped>
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+}
+</style>

+ 555 - 0
src/views/talent/overseas/crud.tsx

@@ -0,0 +1,555 @@
+import * as api from './api';
+import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
+import { dictionary } from '/@/utils/dictionary';
+import { successMessage,warningMessage } from '/@/utils/message';
+import { auth } from '/@/utils/authFunction';
+import tableSelector from '/@/components/tableSelector/index.vue';
+import { shallowRef, h, createVNode, render } from 'vue';
+import { useRouter } from 'vue-router';
+import { ElDialog, ElDescriptions, ElDescriptionsItem, ElTabs, ElTabPane, ElTable, ElTableColumn, ElButton } from 'element-plus';
+import axios from 'axios';
+
+export const createCrudOptions = function ({ crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
+	const router = useRouter();
+	
+	const pageRequest = async (query: UserPageQuery) => {
+		console.log(query);
+		return await api.GetList(query);
+	};
+	const editRequest = async ({ form, row }: EditReq) => {
+		form.id = row.id;
+		return await api.UpdateObj(form);
+	};
+	const delRequest = async ({ row }: DelReq) => {
+		return await api.DelObj(row.id);
+	};
+	const addRequest = async ({ form }: AddReq) => {
+		return await api.AddObj(form);
+	};
+
+	/**
+	 * 懒加载
+	 * @param row
+	 * @returns {Promise<unknown>}
+	 */
+	const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
+		pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
+			resolve(res.data);
+		});
+	};
+
+	// 修改获取用户信息的方法
+	const getUserProfile = async (userId: number) => {
+		try {
+			const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/system/wechat/user/profile/get?user_id=${userId}&tenant_id=1`);
+			return response.data;
+		} catch (error) {
+			console.error('获取用户信息失败:', error);
+			return null;
+		}
+	};
+
+	// 显示用户信息弹窗
+	const showUserProfileDialog = async (userId: number) => {
+		const profileData = await getUserProfile(userId);
+		if (!profileData || profileData.code !== 2000) {
+			warningMessage('获取用户信息失败');
+			return;
+		}
+
+		// 创建弹窗内容
+		const { user_info, profile, educations, family_members, trainings, work_experiences } = profileData.data;
+
+		// 创建一个容器元素
+		const container = document.createElement('div');
+		
+		// 创建对话框内容
+		const dialogContent = createVNode(
+			ElDialog,
+			{
+				title: '个人信息详情',
+				width: '70%',
+				modelValue: true,
+				'onUpdate:modelValue': (val: boolean) => {
+					if (!val) {
+						// 关闭对话框时销毁组件
+						render(null, container);
+						document.body.removeChild(container);
+					}
+				},
+				beforeClose: () => {
+					render(null, container);
+					document.body.removeChild(container);
+				}
+			},
+			{
+				default: () => createVNode(ElTabs, { type: 'border-card' }, {
+					default: () => [
+						// 基本信息标签页
+						createVNode(ElTabPane, { label: '基本信息' }, {
+							default: () => createVNode(ElDescriptions, { column: 3, border: true }, {
+								default: () => [
+									createVNode(ElDescriptionsItem, { label: '姓名' }, { default: () => user_info?.name || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '电话' }, { default: () => user_info?.phone || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '年龄' }, { default: () => user_info?.age || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '出生日期' }, { default: () => user_info?.birth_date || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '性别' }, { default: () => user_info?.gender_name || '未知' }),
+									createVNode(ElDescriptionsItem, { label: '身份证号' }, { default: () => user_info?.id_card || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '政治面貌' }, { default: () => profile?.political_status || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '民族' }, { default: () => profile?.ethnicity || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '身高' }, { default: () => profile?.height ? `${profile.height}cm` : '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '体重' }, { default: () => profile?.weight ? `${profile.weight}kg` : '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '籍贯' }, { default: () => profile?.native_place || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '户口所在地' }, { default: () => profile?.household_location || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '现居地址' }, { default: () => profile?.current_address || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '婚姻状况' }, { default: () => profile?.marital_status_name || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '是否有子女' }, { default: () => profile?.has_children !== undefined ? (profile.has_children ? '是' : '否') : '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '期望薪资' }, { default: () => profile?.expected_salary || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '紧急联系人' }, { default: () => profile?.emergency_contact || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '紧急联系电话' }, { default: () => profile?.emergency_phone || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '特长' }, { default: () => profile?.specialties || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '人生格言' }, { default: () => profile?.life_motto || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '招聘来源' }, { default: () => profile?.recruitment_source_name || '未填写' }),
+									createVNode(ElDescriptionsItem, { label: '招聘来源详情' }, { default: () => profile?.recruitment_source_detail || '未填写' }),
+								]
+							})
+						}),
+						
+						// 教育经历标签页 - 只在有数据时显示
+						educations && educations.length > 0 ? createVNode(ElTabPane, { label: '教育经历' }, {
+							default: () => createVNode(ElTable, { data: educations, border: true, stripe: true }, {
+								default: () => [
+									createVNode(ElTableColumn, { prop: 'education_type_name', label: '学历类型' }),
+									createVNode(ElTableColumn, { prop: 'degree_name', label: '学位' }),
+									createVNode(ElTableColumn, { prop: 'school_name', label: '学校名称' }),
+									createVNode(ElTableColumn, { prop: 'major', label: '专业' }),
+									createVNode(ElTableColumn, { prop: 'start_date', label: '开始日期' }),
+									createVNode(ElTableColumn, { prop: 'end_date', label: '结束日期' }),
+								]
+							})
+						}) : null,
+						
+						// 家庭成员标签页 - 只在有数据时显示
+						family_members && family_members.length > 0 ? createVNode(ElTabPane, { label: '家庭成员' }, {
+							default: () => createVNode(ElTable, { data: family_members, border: true, stripe: true }, {
+								default: () => [
+									createVNode(ElTableColumn, { prop: 'relation', label: '关系' }),
+									createVNode(ElTableColumn, { prop: 'name', label: '姓名' }),
+									createVNode(ElTableColumn, { prop: 'workplace', label: '工作单位' }),
+									createVNode(ElTableColumn, { prop: 'position', label: '职位' }),
+									createVNode(ElTableColumn, { prop: 'phone', label: '联系电话' }),
+								]
+							})
+						}) : null,
+						
+						// 工作经历标签页 - 只在有数据时显示
+						work_experiences && work_experiences.length > 0 ? createVNode(ElTabPane, { label: '工作经历' }, {
+							default: () => createVNode(ElTable, { data: work_experiences, border: true, stripe: true }, {
+								default: () => [
+									createVNode(ElTableColumn, { prop: 'company_name', label: '公司名称' }),
+									createVNode(ElTableColumn, { prop: 'department', label: '部门' }),
+									createVNode(ElTableColumn, { prop: 'position', label: '职位' }),
+									createVNode(ElTableColumn, { prop: 'start_date', label: '开始日期' }),
+									createVNode(ElTableColumn, { prop: 'end_date', label: '结束日期' }),
+									createVNode(ElTableColumn, { prop: 'monthly_salary', label: '月薪' }),
+									createVNode(ElTableColumn, { prop: 'company_size', label: '公司规模' }),
+									createVNode(ElTableColumn, { prop: 'supervisor_name', label: '主管姓名' }),
+									createVNode(ElTableColumn, { prop: 'supervisor_phone', label: '主管电话' }),
+									createVNode(ElTableColumn, { prop: 'resignation_reason', label: '离职原因' }),
+								]
+							})
+						}) : null,
+						
+						// 培训经历标签页 - 只在有数据时显示
+						trainings && trainings.length > 0 ? createVNode(ElTabPane, { label: '培训经历' }, {
+							default: () => createVNode(ElTable, { data: trainings, border: true, stripe: true }, {
+								default: () => [
+									createVNode(ElTableColumn, { prop: 'training_name', label: '培训名称' }),
+									createVNode(ElTableColumn, { prop: 'institution', label: '培训机构' }),
+									createVNode(ElTableColumn, { prop: 'start_date', label: '开始日期' }),
+									createVNode(ElTableColumn, { prop: 'end_date', label: '结束日期' }),
+									createVNode(ElTableColumn, { prop: 'description', label: '描述' }),
+									createVNode(ElTableColumn, { prop: 'certificate', label: '证书' }),
+								]
+							})
+						}) : null,
+					].filter(Boolean) // 过滤掉null值
+				}),
+				footer: () => createVNode(ElButton, {
+					onClick: () => {
+						render(null, container);
+						document.body.removeChild(container);
+					}
+				}, { default: () => '关闭' })
+			}
+		);
+
+		// 将容器添加到body
+		document.body.appendChild(container);
+		
+		// 渲染对话框到容器
+		render(dialogContent, container);
+	};
+
+	return {
+		crudOptions: {
+			toolbar:{
+				buttons:{
+					search:{show:false},
+					// 刷新按钮
+					refresh:{show:false},
+					// 紧凑模式
+					compact:{show:false},
+					// 导出按钮
+					export:{
+						text: '导出',
+						type: 'primary',
+						size: 'small',
+						icon: 'upload',
+						circle: false,
+						display: true
+					},
+					// 列设置按钮
+					columns:{
+						show:false
+					},
+				}
+			},
+			request: {
+				pageRequest,
+				addRequest,
+				editRequest,
+				delRequest,
+			},
+			actionbar: {
+				buttons: {
+					add: {
+						show: false,//auth('area:Create'),
+					},
+					// 添加批量操作按钮
+					// 添加批量绑定标签按钮
+					/* batchBindTags: {
+						text: '批量绑定标签',
+						type: 'primary',
+						show: true,
+						order: 2,
+						click: () => {
+							// 使用正确的方法获取选中行
+							const selection = context.selectedRows || [];
+							console.log('选中的行:', selection);
+							
+							if (!selection || selection.length === 0) {
+								warningMessage('请先选择要操作的申请');
+								return;
+							}
+							// 打开批量绑定标签对话框
+							context.openBatchTagsDialog(selection);
+						},
+					}, */
+				},
+			},
+			rowHandle: {
+				//固定右侧
+				fixed: 'right',
+				width: 280, // 增加宽度以容纳新按钮
+				buttons: {
+					view: {
+						text: '查看报告',
+						iconRight: 'view',
+						show: false,
+						type: 'text',
+						click: ({ row }) => {
+							// 在新窗口中打开报告详情页面
+							const baseUrl = window.location.origin;
+							const url = `${baseUrl}/#/report?id=${row.id}&tenant_id=${1}&application_id=${row.id}`;
+							window.open(url, '_blank');
+						}
+					},
+					profile: { // 添加查看个人信息按钮
+						text: '查看个人信息',
+						iconRight: 'User',
+						type: 'text',
+						show: true,
+						order: 1,
+						click: ({ row }) => {
+							showUserProfileDialog(row.id);
+						}
+					},
+					edit: {
+						text: '编辑',
+						iconRight: 'Edit',
+						type: 'text',
+						show: false,//auth('area:Update'),
+					},
+					remove: {
+						text: '删除',
+						iconRight: 'Delete',
+						type: 'text',
+						show: false,//auth('area:Delete'),
+					},
+				},
+			},
+			pagination: {
+				show: true,
+			},
+			table: {
+				selection: true,
+				onSelectionChange: (selection: any[]) => {
+					// 存储选中的行到一个全局变量中
+					context.selectedRows = selection;
+				},
+			},
+			search: {
+				show: true,
+				layout: 'auto',
+				buttons: {
+					search: {
+						size: 'small', // 设置查询按钮大小为small
+					},
+					reset: {
+						size: 'small', // 设置重置按钮大小为small
+					}
+				},		
+				resetBtn: {
+					show: true,
+					click: () => {
+						// 重置搜索时,也重置树的选中状态
+						const treeRef = document.querySelector('.el-tree');
+						if (treeRef) {
+							// 尝试清除当前高亮
+							const highlightNode = treeRef.querySelector('.is-current');
+							if (highlightNode) {
+								highlightNode.classList.remove('is-current');
+							}
+						}
+					}
+				}
+			},
+			columns: {
+				_selection: {
+					title: '选择',
+					form: { show: false },
+					column: {
+						type: 'selection',
+						align: 'center',
+						width: 50,
+						fixed: 'left',
+						columnSetDisabled: true,
+					},
+				},
+				id: {
+					title: 'ID',
+					search: {
+						show: false,
+						component: {
+							placeholder: '请输入ID',
+						},
+					},
+					type: 'number',
+					column: {
+						width: 80,
+					},
+					form: {
+						show: false,
+					},
+				},
+				name: {
+					title: '姓名',
+					search: {
+						show: true,
+						component: {
+							placeholder: '请输入姓名',
+						},
+						size: 'small',
+						col:{ span:3},
+					},
+					type: 'input',
+					column: {
+						minWidth: 100,
+						formatter: ({ row }) => {
+							return row.name || row.nikename || '未填写';
+						}
+					},
+				},
+				phone: {
+					title: '电话',
+					search: {
+						show: true,
+						component: {
+							placeholder: '请输入电话',
+						},
+						size: 'small',
+						col:{ span:3},
+					},
+					type: 'input',
+					column: {
+						minWidth: 120,
+						formatter: ({ row }) => {
+							return row.phone || '未填写';
+						}
+					},
+				},
+				gender_name: {
+					title: '性别',
+					search: {
+						show: true,
+						component: {
+							name: 'el-select',
+							options: [
+								{ value: '男', label: '男' },
+								{ value: '女', label: '女' },
+								{ value: '未知', label: '未知' },
+							]
+						},
+						size: 'small',
+						col:{ span:3},
+					},
+					type: 'input',
+					column: {
+						minWidth: 80,
+					},
+				},
+				age: {
+					title: '年龄',
+					type: 'number',
+					column: {
+						minWidth: 80,
+						formatter: ({ row }) => {
+							return row.age || '未填写';
+						}
+					},
+				},
+				/* 'profile_summary.political_status': {
+					title: '政治面貌',
+					type: 'input',
+					column: {
+						minWidth: 100,
+						formatter: ({ row }) => {
+							return row.profile_summary?.political_status || '未填写';
+						}
+					},
+				},
+				'profile_summary.ethnicity': {
+					title: '民族',
+					type: 'input',
+					column: {
+						minWidth: 80,
+						formatter: ({ row }) => {
+							return row.profile_summary?.ethnicity || '未填写';
+						}
+					},
+				}, 
+				'profile_summary.marital_status_name': {
+					title: '婚姻状况',
+					type: 'input',
+					column: {
+						minWidth: 100,
+						formatter: ({ row }) => {
+							return row.profile_summary?.marital_status_name || '未填写';
+						}
+					},
+				},*/
+				'profile_summary.expected_salary': {
+					title: '期望薪资',
+					type: 'number',
+					column: {
+						minWidth: 100,
+						formatter: ({ row }) => {
+							return row.profile_summary?.expected_salary ? `${row.profile_summary.expected_salary}元` : '未填写';
+						}
+					},
+				},
+				'application_summary.latest_position': {
+					title: '最近申请职位',
+					search: {
+						show: true,
+						component: {
+							placeholder: '请输入职位',
+						},
+						size: 'small',
+						col:{ span:3},
+					},
+					type: 'input',
+					column: {
+						minWidth: 120,
+						formatter: ({ row }) => {
+							return row.application_summary?.latest_position || '未填写';
+						}
+					},
+				},
+				'application_summary.latest_status': {
+					title: '申请状态',
+					search: {
+						show: true,
+						component: {
+							name: 'el-select',
+							options: [
+								{ value: '待面试', label: '待面试' },
+								{ value: '已面试', label: '已面试' },
+								{ value: '已录用', label: '已录用' },
+								{ value: '已拒绝', label: '已拒绝' },
+							]
+						},
+						size: 'small',
+						col:{ span:3},
+					},
+					type: 'input',
+					column: {
+						minWidth: 100,
+						formatter: ({ row }) => {
+							return row.application_summary?.latest_status || '未填写';
+						}
+					},
+				},
+				'application_summary.latest_date': {
+					title: '申请日期',
+					type: 'input',
+					column: {
+						minWidth: 100,
+						formatter: ({ row }) => {
+							return row.application_summary?.latest_date || '未填写';
+						}
+					},
+				},
+				/*'application_summary.highest_score': {
+					title: '最高评分',
+					type: 'number',
+					column: {
+						minWidth: 100,
+						formatter: ({ row }) => {
+							return row.application_summary?.highest_score || '未评分';
+						}
+					},
+				},
+				'application_summary.total_count': {
+					title: '申请次数',
+					type: 'number',
+					column: {
+						minWidth: 100,
+						formatter: ({ row }) => {
+							return row.application_summary?.total_count || 0;
+						}
+					},
+				},
+				 status_name: {
+					title: '账号状态',
+					type: 'input',
+					column: {
+						minWidth: 100,
+					},
+				},
+				create_datetime: {
+					title: '创建时间',
+					type: 'datetime',
+					column: {
+						minWidth: 160,
+					},
+					form: {
+						show: false,
+					},
+				}, */
+			},
+			
+		},
+	};
+};

+ 380 - 0
src/views/talent/overseas/index.vue

@@ -0,0 +1,380 @@
+<template>
+	<fs-page>
+		<div class="job-application-container">
+			<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-1' }" @click="handleNodeClick({ type: 'status', value: 0, id: 'status-1' })">
+						<div class="item-content">
+							<el-icon><Clock /></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-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">
+					<!-- 可以添加自定义插槽,如果需要 -->
+				</fs-crud>
+			</div>
+		</div>
+		<BatchTagsDialog ref="batchTagsDialogRef" :crudExpose="crudExpose" />
+		<!-- <BatchStatusDialog ref="batchStatusDialogRef" :crudExpose="crudExpose" @success="handleBatchStatusSuccess" /> -->
+	</fs-page>
+</template>
+
+<script lang="ts" setup name="areas">
+import { onMounted, ref, reactive } 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 } from '@element-plus/icons-vue';
+import BatchTagsDialog from './components/index.vue';
+/* import BatchStatusDialog from './components/BatchStatusDialog.vue'; */
+import { getApplicationStatusSummary } from './api';
+
+const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({ 
+	createCrudOptions,
+	context: {
+		openBatchTagsDialog: (selection: any) => {
+			batchTagsDialogRef.value.open(selection);
+		},
+		openBatchStatusDialog: (selection: any) => {
+			batchStatusDialogRef.value.open(selection);
+		},
+		selectedRows: [] // 存储选中的行
+	}
+});
+
+const batchTagsDialogRef = ref();
+const batchStatusDialogRef = ref();
+
+// 状态计数
+const totalCount = ref(0);
+const statusCounts = reactive<Record<number, number>>({
+	1: 0, // 待面试
+	2: 0, // 已面试
+	3: 0, // 已录用
+	4: 0  // 已拒绝
+});
+
+// 职位列表
+const showPositionList = ref(true);
+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=1`);
+		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  // 已拒绝
+					};
+					
+					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;
+			}
+		} 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();
+};
+
+// 页面打开后获取列表数据
+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);
+	
+	// 刷新
+	crudExpose.doRefresh();
+});
+</script>
+
+<style scoped>
+.job-application-container {
+	display: flex;
+	height: 100%;
+}
+
+.sidebar {
+	margin-top: 10px;
+	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;
+}
+
+.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;
+}
+
+.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;
+}
+</style>