From d653cd97d676ebe513be21f4de9767d3242214c2 Mon Sep 17 00:00:00 2001 From: francis-fh Date: Fri, 24 Jul 2026 20:58:01 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=A1=8C=E4=B8=BA=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ProFromMap/index.tsx | 4 +- src/pages/JobPortal/Detail/index.tsx | 51 ++++++++++++++++++++- src/pages/JobPortal/List/index.tsx | 66 +++++++++++++++++++++++++++- src/pages/Management/List/edit.tsx | 15 +++++++ src/pages/Management/List/index.tsx | 5 +++ 5 files changed, 137 insertions(+), 4 deletions(-) diff --git a/src/components/ProFromMap/index.tsx b/src/components/ProFromMap/index.tsx index 96c1749..92a6313 100644 --- a/src/components/ProFromMap/index.tsx +++ b/src/components/ProFromMap/index.tsx @@ -19,8 +19,8 @@ export type MapProps = { open: boolean; }; export const aMapConfig = { - key: '9cfc9370bd8a941951da1cea0308e9e3', - securityJsCode: '7b16386c7f744c3ca05595965f2b037f', + key: 'b79ea14cb17704ab1a1a32c12d72f634', + securityJsCode: '04795ef5b20919d34cadc2e33e01d609', }; /** 石河子市地图默认中心点与行政区划编码 */ diff --git a/src/pages/JobPortal/Detail/index.tsx b/src/pages/JobPortal/Detail/index.tsx index 45c1d55..0bbcf3e 100644 --- a/src/pages/JobPortal/Detail/index.tsx +++ b/src/pages/JobPortal/Detail/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useParams, history, useLocation, useSearchParams } from '@umijs/max'; import { Card, @@ -206,6 +206,55 @@ const JobDetailPage: React.FC = () => { const [industryTree, setIndustryTree] = useState([]); const [complaintVisible, setComplaintVisible] = useState(false); + // 行为上报:浏览岗位时长统计 + const enterTimeRef = useRef(0); + const reportedRef = useRef(false); + const jobIdRef = useRef(''); + + // 页面进入/离开时上报浏览行为 + useEffect(() => { + enterTimeRef.current = Date.now(); + reportedRef.current = false; + jobIdRef.current = String(originalJobData?.jobId || originalJobData?.id || id || ''); + + const calcDuration = () => + Math.max(1, Math.floor((Date.now() - enterTimeRef.current) / 1000)); + + const doReport = (durationSeconds: number) => { + if (reportedRef.current) return; + reportedRef.current = true; + + const actorId = localStorage.getItem('appUserId'); + const targetId = jobIdRef.current; + + if (!actorId || !targetId || durationSeconds <= 0) return; + + fetch('/api/cms/behavior/report', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + actorType: '1', + actorId: String(actorId), + targetType: '2', + targetId: String(targetId), + viewDuration: String(durationSeconds), + }), + keepalive: true, + }).catch(() => {}); + }; + + const handleBeforeUnload = () => { + doReport(calcDuration()); + }; + + window.addEventListener('beforeunload', handleBeforeUnload); + + return () => { + window.removeEventListener('beforeunload', handleBeforeUnload); + doReport(calcDuration()); + }; + }, []); + const detailTagGroups = useMemo( () => [ { label: '薪资范围与构成', values: jobDetail.salaryComposition }, diff --git a/src/pages/JobPortal/List/index.tsx b/src/pages/JobPortal/List/index.tsx index 1884681..e7ad95c 100644 --- a/src/pages/JobPortal/List/index.tsx +++ b/src/pages/JobPortal/List/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Card, Row, @@ -216,6 +216,70 @@ const JobListPage: React.FC = () => { const [complaintVisible, setComplaintVisible] = useState(false); const selectedJobId = selectedJob?.jobId || selectedJob?.id; + // 行为上报:浏览岗位时长统计 + const jobStartTimeRef = useRef(0); + const behaviorReportedRef = useRef(false); + const currentJobIdRef = useRef(''); + + const reportJobBehavior = useCallback((jobId: string) => { + if (behaviorReportedRef.current) return; + behaviorReportedRef.current = true; + + const actorId = localStorage.getItem('appUserId'); + if (!actorId || !jobId) return; + + const duration = Math.max(1, Math.floor((Date.now() - jobStartTimeRef.current) / 1000)); + if (duration <= 0) return; + + fetch('/api/cms/behavior/report', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + actorType: '1', + actorId: String(actorId), + targetType: '2', + targetId: String(jobId), + viewDuration: String(duration), + }), + keepalive: true, + }).catch(() => {}); + }, []); + + // 切换岗位时上报上一个岗位的浏览时长 + useEffect(() => { + if (!isJobPortalLoggedIn()) return; + + const newJobId = String(selectedJobId || ''); + + // 如果之前有浏览中的岗位,先上报 + if (currentJobIdRef.current && currentJobIdRef.current !== newJobId) { + reportJobBehavior(currentJobIdRef.current); + } + + // 重置计时器,开始计时新岗位 + behaviorReportedRef.current = false; + jobStartTimeRef.current = Date.now(); + currentJobIdRef.current = newJobId; + }, [selectedJobId, reportJobBehavior]); + + // 页面离开时上报当前岗位的浏览时长 + useEffect(() => { + const handleBeforeUnload = () => { + if (currentJobIdRef.current) { + reportJobBehavior(currentJobIdRef.current); + } + }; + + window.addEventListener('beforeunload', handleBeforeUnload); + + return () => { + window.removeEventListener('beforeunload', handleBeforeUnload); + if (currentJobIdRef.current) { + reportJobBehavior(currentJobIdRef.current); + } + }; + }, [reportJobBehavior]); + const selectedJobDetailTagGroups = useMemo( () => [ diff --git a/src/pages/Management/List/edit.tsx b/src/pages/Management/List/edit.tsx index aa282de..4f954e9 100644 --- a/src/pages/Management/List/edit.tsx +++ b/src/pages/Management/List/edit.tsx @@ -66,6 +66,7 @@ export type ListFormProps = { welfareBenefitsEnum: DictValueEnumObj; workScheduleEnum: DictValueEnumObj; keyPopulationsEnum: DictValueEnumObj; + jobFeatureEnum: DictValueEnumObj; mode?: 'view' | 'edit' | 'create'; }; @@ -98,6 +99,7 @@ const listEdit: React.FC = (props) => { welfareBenefitsEnum, workScheduleEnum, keyPopulationsEnum, + jobFeatureEnum, } = props; const { mode = props.values ? 'edit' : 'create' } = props; const [showKeyPopulations, setShowKeyPopulations] = useState(false); @@ -269,6 +271,7 @@ const listEdit: React.FC = (props) => { }), ), jobCategory: values.jobCategory, + jobFeature: values.jobFeature, isUrgent: values.isUrgent ? 1 : 0, isKeyPopulations: showKeyPopulations ? '0' : '1', keyPopulations: showKeyPopulations ? values.keyPopulations : undefined, @@ -355,6 +358,11 @@ const listEdit: React.FC = (props) => { + = (props) => { style={{ width: 328 }} /> +
diff --git a/src/pages/Management/List/index.tsx b/src/pages/Management/List/index.tsx index c1da99f..82075ee 100644 --- a/src/pages/Management/List/index.tsx +++ b/src/pages/Management/List/index.tsx @@ -98,6 +98,7 @@ function ManagementList() { const [welfareBenefitsEnum, setWelfareBenefitsEnum] = useState([]); const [workScheduleEnum, setWorkScheduleEnum] = useState([]); const [keyPopulationsEnum, setKeyPopulationsEnum] = useState([]); + const [jobFeatureEnum, setJobFeatureEnum] = useState([]); const [currentRow, setCurrentRow] = useState(); const [modalVisible, setModalVisible] = useState(false); const [mode, setMode] = useState<'view' | 'edit' | 'create'>('create'); @@ -132,6 +133,9 @@ function ManagementList() { getDictValueEnum('kp_type', false, true).then((data) => { setKeyPopulationsEnum(data); }); + getDictValueEnum('job_feature', false, true).then((data) => { + setJobFeatureEnum(data); + }); }, []); async function changeRelease(record: API.ManagementList.Manage) { @@ -541,6 +545,7 @@ function ManagementList() { welfareBenefitsEnum={welfareBenefitsEnum} workScheduleEnum={workScheduleEnum} keyPopulationsEnum={keyPopulationsEnum} + jobFeatureEnum={jobFeatureEnum} > );