Browse Source

添加侧边栏

yangg 2 months ago
parent
commit
27f548912a
2 changed files with 287 additions and 6 deletions
  1. 18 3
      src/views/JobApplication/list/crud.tsx
  2. 269 3
      src/views/JobApplication/list/index.vue

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

@@ -88,6 +88,20 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 			search: {
 				show: true,
 				layout: 'auto',
+				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: {
 				_index: {
@@ -176,10 +190,11 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 					title: '申请状态',
 					search: {
 						show: true,
-						component: {
+						placeholder: '请选择申请状态',
+						/* component: {
 							name: 'el-select',
-							placeholder: '请选择申请状态',
-						},
+							
+						}, */
 					},
 					type: 'dict-select',
 					column: {

+ 269 - 3
src/views/JobApplication/list/index.vue

@@ -1,25 +1,291 @@
 <template>
 	<fs-page>
-		<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
+		<div class="job-application-container">
+			<div class="sidebar">
+				<div class="tree-container">
+					<!-- 全部 -->
+					<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: 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[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 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>
+			</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, 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';
 
 const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({ createCrudOptions });
 
+// 状态计数
+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}>>([
+	{ id: 1, title: '流水线操作工', count: 0 },
+	{ id: 2, title: '咖啡师', count: 0 },
+	{ id: 3, title: '餐厅服务员', count: 0 }
+]);
+
+// 切换职位列表显示
+const togglePositionList = () => {
+	showPositionList.value = !showPositionList.value;
+};
+
+// 当前激活的节点
+const activeNode = ref('all');
+
+// 处理树节点点击
+const handleNodeClick = (data: any) => {
+	activeNode.value = data.id || 'all';
+	
+	if (data.type === 'all') {
+		// 清除所有筛选条件
+		crudExpose.getSearchRef().resetFields();
+		crudExpose.doRefresh();
+	} 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;
+	
+	totalCount.value = data.total || 0;
+	
+	// 重置计数
+	Object.keys(statusCounts).forEach(key => {
+		statusCounts[Number(key)] = 0;
+	});
+	
+	// 统计各状态数量
+	data.records.forEach((record: any) => {
+		const status = record.status;
+		if (status && typeof status === 'number' && status in statusCounts) {
+			statusCounts[status]++;
+		}
+	});
+	
+	// 统计各职位数量
+	// 重置职位计数
+	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;
+			}
+		}
+	});
+};
+
 // 页面打开后获取列表数据
 onMounted(async () => {
 	// 设置列权限
 	const newOptions = await handleColumnPermission(GetPermission, crudOptions);
-	//重置crudBinding
+	
+	// 添加数据加载后的回调,用于更新计数
+	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: 16px;
+	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>