|
|
@@ -1,11 +1,12 @@
|
|
|
-import React, { useState, useEffect } from 'react';
|
|
|
+import React, { useState, useEffect, useCallback } from 'react';
|
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
import { message } from 'antd';
|
|
|
import dayjs from 'dayjs';
|
|
|
import { BriefcaseIcon, MapPinIcon, ClockIcon, CurrencyDollarIcon, AcademicCapIcon, CalendarIcon } from '@heroicons/react/24/outline';
|
|
|
import { useAuth } from '@/client/mobile/hooks/AuthProvider';
|
|
|
-import { jobClient } from '@/client/api';
|
|
|
+import { jobClient, silverCompaniesClient } from '@/client/api';
|
|
|
import type { CreateJobDto } from '@/server/modules/silver-jobs/job.entity';
|
|
|
+import type { InferResponseType } from 'hono/client';
|
|
|
|
|
|
interface Company {
|
|
|
id: number;
|
|
|
@@ -64,6 +65,10 @@ export const PublishJobForm: React.FC<PublishJobFormProps> = ({ onSuccess, onCan
|
|
|
const { user } = useAuth();
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
const [companies, setCompanies] = useState<Company[]>([]);
|
|
|
+ const [searchKeyword, setSearchKeyword] = useState('');
|
|
|
+ const [loadingCompanies, setLoadingCompanies] = useState(false);
|
|
|
+ const [showCompanyDropdown, setShowCompanyDropdown] = useState(false);
|
|
|
+ const [debounceTimer, setDebounceTimer] = useState<NodeJS.Timeout | null>(null);
|
|
|
const [formData, setFormData] = useState<FormDataState>({
|
|
|
companyId: 0,
|
|
|
title: '',
|
|
|
@@ -82,29 +87,85 @@ export const PublishJobForm: React.FC<PublishJobFormProps> = ({ onSuccess, onCan
|
|
|
const educationOptions = ['不限', '初中', '高中', '大专', '本科', '硕士', '博士'];
|
|
|
const experienceOptions = ['不限', '1年以下', '1-3年', '3-5年', '5-10年', '10年以上'];
|
|
|
|
|
|
- // 获取企业列表
|
|
|
- useEffect(() => {
|
|
|
- const fetchCompanies = async () => {
|
|
|
- try {
|
|
|
- // 模拟获取企业数据 - 实际项目中应从API获取
|
|
|
- setCompanies([
|
|
|
- { id: 1, name: '北京科技有限公司' },
|
|
|
- { id: 2, name: '上海互联网公司' },
|
|
|
- { id: 3, name: '广州教育科技公司' },
|
|
|
- ]);
|
|
|
- } catch (error) {
|
|
|
- console.error('获取企业列表失败:', error);
|
|
|
- message.error('获取企业列表失败');
|
|
|
+ // 获取认证企业列表
|
|
|
+ const fetchCertifiedCompanies = useCallback(async (keyword?: string) => {
|
|
|
+ try {
|
|
|
+ setLoadingCompanies(true);
|
|
|
+ const response = await silverCompaniesClient.certified.$get({
|
|
|
+ query: {
|
|
|
+ page: 1,
|
|
|
+ pageSize: 50,
|
|
|
+ ...(keyword ? { keyword } : {})
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ setCompanies(data.data.map(company => ({
|
|
|
+ id: company.id,
|
|
|
+ name: company.name
|
|
|
+ })));
|
|
|
}
|
|
|
- };
|
|
|
-
|
|
|
- fetchCompanies();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取认证企业列表失败:', error);
|
|
|
+ message.error('获取认证企业列表失败');
|
|
|
+ } finally {
|
|
|
+ setLoadingCompanies(false);
|
|
|
+ }
|
|
|
}, []);
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
+ fetchCertifiedCompanies();
|
|
|
+ }, [fetchCertifiedCompanies]);
|
|
|
+
|
|
|
const handleChange = (field: keyof FormDataState, value: string | number) => {
|
|
|
setFormData(prev => ({ ...prev, [field]: value }));
|
|
|
};
|
|
|
|
|
|
+ const handleCompanySearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
+ const keyword = e.target.value;
|
|
|
+ setSearchKeyword(keyword);
|
|
|
+
|
|
|
+ if (debounceTimer) {
|
|
|
+ clearTimeout(debounceTimer);
|
|
|
+ }
|
|
|
+
|
|
|
+ const timer = setTimeout(() => {
|
|
|
+ fetchCertifiedCompanies(keyword || undefined);
|
|
|
+ }, 300);
|
|
|
+
|
|
|
+ setDebounceTimer(timer);
|
|
|
+ setShowCompanyDropdown(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleCompanySelect = (company: Company) => {
|
|
|
+ setFormData(prev => ({ ...prev, companyId: company.id }));
|
|
|
+ setSearchKeyword(company.name);
|
|
|
+ setShowCompanyDropdown(false);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 点击外部关闭下拉框
|
|
|
+ useEffect(() => {
|
|
|
+ const handleClickOutside = (event: MouseEvent) => {
|
|
|
+ const target = event.target as HTMLElement;
|
|
|
+ if (!target.closest('.company-select-container')) {
|
|
|
+ setShowCompanyDropdown(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ document.addEventListener('mousedown', handleClickOutside);
|
|
|
+ return () => {
|
|
|
+ document.removeEventListener('mousedown', handleClickOutside);
|
|
|
+ if (debounceTimer) {
|
|
|
+ clearTimeout(debounceTimer);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }, [debounceTimer]);
|
|
|
+
|
|
|
+ const filteredCompanies = companies.filter(company =>
|
|
|
+ company.name.toLowerCase().includes(searchKeyword.toLowerCase())
|
|
|
+ );
|
|
|
+
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
|
e.preventDefault();
|
|
|
|
|
|
@@ -184,26 +245,55 @@ export const PublishJobForm: React.FC<PublishJobFormProps> = ({ onSuccess, onCan
|
|
|
return (
|
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
|
{/* 企业选择 */}
|
|
|
- <div>
|
|
|
+ <div className="relative company-select-container">
|
|
|
<label className={`block mb-2 ${FONT_STYLES.caption} font-medium`} style={{ color: INK_COLORS.text.primary }}>
|
|
|
选择企业 *
|
|
|
</label>
|
|
|
- <select
|
|
|
- value={formData.companyId}
|
|
|
- onChange={(e) => handleChange('companyId', parseInt(e.target.value))}
|
|
|
- className={`w-full px-4 py-3 rounded-xl border transition-all duration-300 ${FONT_STYLES.body}`}
|
|
|
- style={{
|
|
|
- backgroundColor: 'rgba(255,255,255,0.7)',
|
|
|
- borderColor: INK_COLORS.ink.medium,
|
|
|
- color: INK_COLORS.text.primary
|
|
|
- }}
|
|
|
- required
|
|
|
- >
|
|
|
- <option value={0}>请选择企业</option>
|
|
|
- {companies.map(company => (
|
|
|
- <option key={company.id} value={company.id}>{company.name}</option>
|
|
|
- ))}
|
|
|
- </select>
|
|
|
+ <div className="relative">
|
|
|
+ <BriefcaseIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5" style={{ color: INK_COLORS.text.light }} />
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ value={searchKeyword}
|
|
|
+ onChange={handleCompanySearch}
|
|
|
+ onFocus={() => setShowCompanyDropdown(true)}
|
|
|
+ placeholder="请输入企业名称搜索"
|
|
|
+ className={`w-full pl-10 pr-4 py-3 rounded-xl border transition-all duration-300 ${FONT_STYLES.body}`}
|
|
|
+ style={{
|
|
|
+ backgroundColor: 'rgba(255,255,255,0.7)',
|
|
|
+ borderColor: INK_COLORS.ink.medium,
|
|
|
+ color: INK_COLORS.text.primary
|
|
|
+ }}
|
|
|
+ required
|
|
|
+ />
|
|
|
+ {loadingCompanies && (
|
|
|
+ <div className="absolute right-3 top-1/2 transform -translate-y-1/2">
|
|
|
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-[#8b7355]"></div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {showCompanyDropdown && filteredCompanies.length > 0 && (
|
|
|
+ <div className="absolute z-10 w-full mt-1 bg-white border border-[#d4c4a8] rounded-xl shadow-lg max-h-60 overflow-y-auto">
|
|
|
+ {filteredCompanies.map(company => (
|
|
|
+ <div
|
|
|
+ key={company.id}
|
|
|
+ onClick={() => handleCompanySelect(company)}
|
|
|
+ className="px-4 py-3 hover:bg-[#f5f3f0] cursor-pointer transition-colors duration-200"
|
|
|
+ style={{ color: INK_COLORS.text.primary }}
|
|
|
+ >
|
|
|
+ {company.name}
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {showCompanyDropdown && filteredCompanies.length === 0 && !loadingCompanies && (
|
|
|
+ <div className="absolute z-10 w-full mt-1 bg-white border border-[#d4c4a8] rounded-xl shadow-lg p-4 text-center" style={{ color: INK_COLORS.text.secondary }}>
|
|
|
+ 未找到匹配的企业
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <input type="hidden" value={formData.companyId} name="companyId" required />
|
|
|
</div>
|
|
|
|
|
|
{/* 岗位名称 */}
|