Просмотр исходного кода

✨ feat(mobile): 新增企业信息管理和岗位详情功能

- 新增企业信息管理页面,支持查看和编辑企业基本信息
- 新增岗位详情页面,展示完整的岗位信息和交互功能
- 新增企业信息API接口,支持获取、创建和更新企业信息
- 在个人资料页面新增企业信息入口按钮
- 优化银龄岗位页面,支持点击跳转到岗位详情
- 完善企业实体类,新增userId关联字段
yourname 7 месяцев назад
Родитель
Сommit
21c6cecfd6

+ 7 - 1
src/client/api.ts

@@ -20,7 +20,8 @@ import type {
   SilverUsersKnowledgeTagsRoutes,
   SilverUsersKnowledgeStatsRoutes,
   SilverUsersKnowledgeInteractionsRoutes,
-  SilverUsersKnowledgeRankingsRoutes
+  SilverUsersKnowledgeRankingsRoutes,
+  MyCompanyRoutes
 } from '@/server/api';
 import axios from 'axios';
 
@@ -69,6 +70,11 @@ export const fileClient = hc<FileRoutes>('/', {
   fetch: axiosFetch,
 }).api.v1.files;
 
+// 企业信息客户端 - 新增
+export const myCompanyClient = hc<MyCompanyRoutes>('/', {
+  fetch: axiosFetch,
+}).api.v1['companies'].my;
+ 
 // 银龄岗具体资源客户端(调整为标准格式)
 export const companyClient = hc<CompanyRoutes>('/', {
   fetch: axiosFetch,

+ 368 - 0
src/client/mobile/pages/CompanyProfilePage.tsx

@@ -0,0 +1,368 @@
+import React, { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useAuth } from '../hooks/AuthProvider';
+import { Button, Card, Form, Input, Select, message, Space, Statistic, Row, Col, Badge, Divider } from 'antd';
+import { EditOutlined, SaveOutlined, CloseOutlined, EnvironmentOutlined, PhoneOutlined, UserOutlined, SafetyCertificateOutlined, EyeOutlined, HeartOutlined } from '@ant-design/icons';
+
+const { TextArea } = Input;
+const { Option } = Select;
+
+interface CompanyData {
+  id?: number;
+  name: string;
+  shortName?: string;
+  industryCategory: string;
+  companyType: string;
+  employeeCount: string;
+  address: string;
+  longitude: number;
+  latitude: number;
+  contactPerson: string;
+  contactPhone: string;
+  isCertified: number;
+  viewCount: number;
+  favoriteCount: number;
+  createdAt?: string;
+  updatedAt?: string;
+}
+
+const industryCategories = [
+  '互联网/IT', '金融', '教育', '医疗', '房地产', '制造业', '零售', '物流', '文化传媒', '其他'
+];
+
+const companyTypes = [
+  '国有企业', '民营企业', '外资企业', '合资企业', '事业单位', '其他'
+];
+
+const employeeCounts = [
+  '1-50人', '50-100人', '100-500人', '500-1000人', '1000人以上'
+];
+
+const CompanyProfilePage: React.FC = () => {
+  const { user } = useAuth();
+  const navigate = useNavigate();
+  const [form] = Form.useForm();
+  const [loading, setLoading] = useState(false);
+  const [isEditing, setIsEditing] = useState(false);
+  const [companyData, setCompanyData] = useState<CompanyData | null>(null);
+
+  useEffect(() => {
+    fetchCompanyInfo();
+  }, []);
+
+  const fetchCompanyInfo = async () => {
+    if (!user) return;
+    
+    setLoading(true);
+    try {
+      const response = await fetch('/api/v1/companies/my', {
+        headers: {
+          'Authorization': `Bearer ${localStorage.getItem('token')}`,
+        },
+      });
+      
+      if (response.ok) {
+        const data = await response.json();
+        if (data.data) {
+          setCompanyData(data.data);
+          form.setFieldsValue(data.data);
+        } else {
+          setIsEditing(true);
+        }
+      } else {
+        message.error('获取企业信息失败');
+      }
+    } catch (error) {
+      console.error('获取企业信息失败:', error);
+      message.error('获取企业信息失败,请重试');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleSubmit = async (values: any) => {
+    if (!user) return;
+    
+    setLoading(true);
+    try {
+      const method = companyData ? 'PUT' : 'POST';
+      
+      const response = await fetch('/api/v1/companies/my', {
+        method: method,
+        headers: {
+          'Content-Type': 'application/json',
+          'Authorization': `Bearer ${localStorage.getItem('token')}`,
+        },
+        body: JSON.stringify(values),
+      });
+
+      if (response.ok) {
+        const data = await response.json();
+        setCompanyData(data);
+        form.setFieldsValue(data);
+        setIsEditing(false);
+        message.success('企业信息保存成功');
+      } else {
+        message.error('保存失败,请重试');
+      }
+    } catch (error) {
+      console.error('保存企业信息失败:', error);
+      message.error('保存失败,请重试');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleCancel = () => {
+    form.resetFields();
+    if (companyData) {
+      form.setFieldsValue(companyData);
+    }
+    setIsEditing(false);
+  };
+
+  const renderCompanyStats = () => (
+    <Row gutter={16}>
+      <Col span={8}>
+        <Statistic
+          title="查看人数"
+          value={companyData?.viewCount || 0}
+          prefix={<EyeOutlined />}
+        />
+      </Col>
+      <Col span={8}>
+        <Statistic
+          title="收藏人数"
+          value={companyData?.favoriteCount || 0}
+          prefix={<HeartOutlined />}
+        />
+      </Col>
+      <Col span={8}>
+        <Statistic
+          title="认证状态"
+          valueRender={() => (
+            <Badge
+              status={companyData?.isCertified === 1 ? "success" : "default"}
+              text={companyData?.isCertified === 1 ? "已认证" : "未认证"}
+            />
+          )}
+        />
+      </Col>
+    </Row>
+  );
+
+  const renderViewMode = () => (
+    <div className="space-y-4">
+      <Card title="企业基本信息" extra={
+        <Button type="link" icon={<EditOutlined />} onClick={() => setIsEditing(true)}>
+          编辑
+        </Button>
+      }>
+        <Space direction="vertical" size="large" className="w-full">
+          <div>
+            <strong>企业名称:</strong>{companyData?.name}
+          </div>
+          <div>
+            <strong>企业简称:</strong>{companyData?.shortName || '-'}
+          </div>
+          <div>
+            <strong>行业类别:</strong>{companyData?.industryCategory}
+          </div>
+          <div>
+            <strong>企业类别:</strong>{companyData?.companyType}
+          </div>
+          <div>
+            <strong>人员规模:</strong>{companyData?.employeeCount}
+          </div>
+        </Space>
+      </Card>
+
+      <Card title="联系信息">
+        <Space direction="vertical" size="large" className="w-full">
+          <div>
+            <strong><EnvironmentOutlined /> 企业地址:</strong>{companyData?.address}
+          </div>
+          <div>
+            <strong><UserOutlined /> 联系人:</strong>{companyData?.contactPerson}
+          </div>
+          <div>
+            <strong><PhoneOutlined /> 联系电话:</strong>{companyData?.contactPhone}
+          </div>
+        </Space>
+      </Card>
+
+      <Card title="位置信息">
+        <div>
+          <strong>坐标:</strong>
+          经度: {companyData?.longitude}° 纬度: {companyData?.latitude}°
+        </div>
+      </Card>
+
+      <Card title="统计信息">
+        {renderCompanyStats()}
+      </Card>
+    </div>
+  );
+
+  const renderEditMode = () => (
+    <Form form={form} layout="vertical" onFinish={handleSubmit}>
+      <Card title="企业基本信息" className="mb-4">
+        <Form.Item
+          label="企业名称"
+          name="name"
+          rules={[{ required: true, message: '请输入企业名称' }]}
+        >
+          <Input placeholder="请输入企业名称" maxLength={100} />
+        </Form.Item>
+
+        <Form.Item
+          label="企业简称"
+          name="shortName"
+        >
+          <Input placeholder="请输入企业简称" maxLength={50} />
+        </Form.Item>
+
+        <Form.Item
+          label="行业类别"
+          name="industryCategory"
+          rules={[{ required: true, message: '请选择行业类别' }]}
+        >
+          <Select placeholder="请选择行业类别">
+            {industryCategories.map(category => (
+              <Option key={category} value={category}>{category}</Option>
+            ))}
+          </Select>
+        </Form.Item>
+
+        <Form.Item
+          label="企业类别"
+          name="companyType"
+          rules={[{ required: true, message: '请选择企业类别' }]}
+        >
+          <Select placeholder="请选择企业类别">
+            {companyTypes.map(type => (
+              <Option key={type} value={type}>{type}</Option>
+            ))}
+          </Select>
+        </Form.Item>
+
+        <Form.Item
+          label="人员规模"
+          name="employeeCount"
+          rules={[{ required: true, message: '请选择人员规模' }]}
+        >
+          <Select placeholder="请选择人员规模">
+            {employeeCounts.map(count => (
+              <Option key={count} value={count}>{count}</Option>
+            ))}
+          </Select>
+        </Form.Item>
+      </Card>
+
+      <Card title="联系信息" className="mb-4">
+        <Form.Item
+          label="企业地址"
+          name="address"
+          rules={[{ required: true, message: '请输入企业地址' }]}
+        >
+          <TextArea rows={2} placeholder="请输入详细地址" maxLength={200} />
+        </Form.Item>
+
+        <Form.Item
+          label="联系人"
+          name="contactPerson"
+          rules={[{ required: true, message: '请输入联系人姓名' }]}
+        >
+          <Input placeholder="请输入联系人姓名" maxLength={50} />
+        </Form.Item>
+
+        <Form.Item
+          label="联系电话"
+          name="contactPhone"
+          rules={[
+            { required: true, message: '请输入联系电话' },
+            { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }
+          ]}
+        >
+          <Input placeholder="请输入联系电话" maxLength={20} />
+        </Form.Item>
+      </Card>
+
+      <Card title="位置信息" className="mb-4">
+        <Form.Item
+          label="经度"
+          name="longitude"
+          rules={[{ required: true, message: '请输入经度' }]}
+        >
+          <Input type="number" step="0.000001" placeholder="请输入经度" />
+        </Form.Item>
+
+        <Form.Item
+          label="纬度"
+          name="latitude"
+          rules={[{ required: true, message: '请输入纬度' }]}
+        >
+          <Input type="number" step="0.000001" placeholder="请输入纬度" />
+        </Form.Item>
+      </Card>
+
+      <div className="flex justify-end space-x-2">
+        <Button onClick={handleCancel} icon={<CloseOutlined />}>
+          取消
+        </Button>
+        <Button type="primary" htmlType="submit" loading={loading} icon={<SaveOutlined />}>
+          保存
+        </Button>
+      </div>
+    </Form>
+  );
+
+  if (!user) {
+    return (
+      <div className="min-h-screen bg-gray-50 p-4">
+        <div className="text-center">
+          <h1 className="text-2xl font-bold text-gray-900 mb-4">企业信息</h1>
+          <button
+            onClick={() => navigate('/login')}
+            className="bg-blue-500 text-white px-6 py-2 rounded"
+          >
+            请先登录
+          </button>
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="min-h-screen bg-gray-50">
+      {/* 页面头部 */}
+      <div className="bg-white shadow-sm px-4 py-3 flex items-center justify-between">
+        <div className="flex items-center">
+          <button 
+            onClick={() => navigate(-1)} 
+            className="mr-2 text-gray-600 hover:text-gray-900"
+          >
+            ←
+          </button>
+          <h1 className="text-lg font-semibold">企业信息</h1>
+        </div>
+        {!isEditing && companyData && (
+          <Button 
+            type="primary" 
+            size="small" 
+            icon={<EditOutlined />}
+            onClick={() => setIsEditing(true)}
+          >
+            编辑
+          </Button>
+        )}
+      </div>
+
+      <div className="p-4">
+        {isEditing ? renderEditMode() : renderViewMode()}
+      </div>
+    </div>
+  );
+};
+
+export default CompanyProfilePage;

+ 444 - 0
src/client/mobile/pages/JobDetailPage.tsx

@@ -0,0 +1,444 @@
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { 
+  ArrowLeftIcon, 
+  HeartIcon, 
+  ChatBubbleLeftIcon,
+  ShareIcon,
+  MapPinIcon,
+  ClockIcon,
+  AcademicCapIcon,
+  BriefcaseIcon
+} from '@heroicons/react/24/outline';
+import { HeartIcon as HeartSolidIcon } from '@heroicons/react/24/solid';
+import { INK_COLORS } from '@/client/mobile/styles/colors';
+import { JobItem } from '@/client/mobile/hooks/useJobs';
+
+// 扩展JobItem类型以包含更多详情字段
+interface JobDetailItem extends JobItem {
+  workLocation?: string;
+  workTime?: string;
+  educationRequired?: string;
+  experienceRequired?: string;
+  details?: string;
+  company?: {
+    industry: string;
+    size: string;
+    tags: string[];
+  };
+  favoriteCount?: number;
+}
+
+const JobDetailPage: React.FC = () => {
+  const { id } = useParams<{ id: string }>();
+  const navigate = useNavigate();
+  const [job, setJob] = useState<JobDetailItem | null>(null);
+  const [loading, setLoading] = useState(true);
+  const [isFavorited, setIsFavorited] = useState(false);
+  const [showShareModal, setShowShareModal] = useState(false);
+
+  // 模拟获取岗位详情数据
+  useEffect(() => {
+    const fetchJobDetail = async () => {
+      setLoading(true);
+      // 这里应该从API获取真实数据
+      setTimeout(() => {
+        const mockJob: JobDetailItem = {
+          id: parseInt(id || '1'),
+          title: '银龄社区服务专员',
+          companyName: '智慧养老服务中心',
+          companyLogo: '/images/company-logo1.jpg',
+          location: '北京市朝阳区',
+          salaryMin: 4000,
+          salaryMax: 6000,
+          jobType: '社区服务',
+          description: '为社区老年人提供日常陪伴、生活协助和情感关怀服务',
+          requirements: '有责任心,热爱老年事业',
+          benefits: ['五险一金', '带薪年假', '节日福利', '培训机会', '弹性工作'],
+          views: 256,
+          applications: 18,
+          publishDate: '2024-07-20',
+          workLocation: '北京市朝阳区各社区服务中心',
+          workTime: '朝九晚五,周末双休',
+          educationRequired: '高中及以上',
+          experienceRequired: '1-3年',
+          details: '负责为社区老年人提供日常陪伴、生活协助和情感关怀服务,需要具备良好的沟通能力和耐心,能够理解和满足老年人的需求。工作地点分布在朝阳区各社区服务中心,工作时间灵活,可根据个人情况调整。',
+          company: {
+            industry: '养老服务',
+            size: '50-100人',
+            tags: ['社区服务', '养老行业', '政府合作']
+          },
+          favoriteCount: 12
+        };
+        setJob(mockJob);
+        setLoading(false);
+      }, 1000);
+    };
+
+    if (id) {
+      fetchJobDetail();
+    }
+  }, [id]);
+
+  const handleFavorite = () => {
+    setIsFavorited(!isFavorited);
+    // 这里应该调用收藏API
+  };
+
+  const handleApply = () => {
+    // 这里应该调用投递简历API
+    alert('简历投递成功!');
+  };
+
+  const handleShare = () => {
+    setShowShareModal(true);
+  };
+
+  const formatSalary = (min: number, max: number) => {
+    return `${min.toLocaleString()}-${max.toLocaleString()}元/月`;
+  };
+
+  const formatPublishDate = (date: string) => {
+    const publishDate = new Date(date);
+    const now = new Date();
+    const diffTime = Math.abs(now.getTime() - publishDate.getTime());
+    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+    
+    if (diffDays === 1) return '今天';
+    if (diffDays < 7) return `${diffDays}天前`;
+    if (diffDays < 30) return `${Math.floor(diffDays / 7)}周前`;
+    return `${Math.floor(diffDays / 30)}月前`;
+  };
+
+  if (loading) {
+    return (
+      <div className="min-h-screen" style={{ backgroundColor: INK_COLORS.ink.light }}>
+        {/* 骨架屏 */}
+        <div className="animate-pulse">
+          <div className="h-16 bg-gray-200 mb-4"></div>
+          <div className="mx-4 mb-4">
+            <div className="h-20 bg-gray-200 rounded-xl mb-4"></div>
+            <div className="h-32 bg-gray-200 rounded-xl mb-4"></div>
+            <div className="h-40 bg-gray-200 rounded-xl mb-4"></div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  if (!job) {
+    return (
+      <div className="min-h-screen flex items-center justify-center" style={{ backgroundColor: INK_COLORS.ink.light }}>
+        <div className="text-center">
+          <div className="text-6xl mb-4">🔍</div>
+          <p className="text-lg mb-4" style={{ color: INK_COLORS.text.secondary }}>岗位信息不存在</p>
+          <button 
+            onClick={() => navigate(-1)}
+            className="px-6 py-2 rounded-full text-sm"
+            style={{ backgroundColor: INK_COLORS.accent.blue, color: 'white' }}
+          >
+            返回
+          </button>
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="min-h-screen pb-20" style={{ backgroundColor: INK_COLORS.ink.light }}>
+      {/* 顶部导航栏 */}
+      <header 
+        className="sticky top-0 z-20 shadow-sm px-4 py-3 flex items-center justify-between"
+        style={{ backgroundColor: INK_COLORS.ink.light, borderColor: INK_COLORS.ink.medium }}
+      >
+        <button 
+          onClick={() => navigate(-1)}
+          className="p-2 rounded-full hover:bg-gray-100 transition-colors"
+        >
+          <ArrowLeftIcon className="w-5 h-5" style={{ color: INK_COLORS.ink.deep }} />
+        </button>
+        <h1 className="font-serif text-lg font-medium" style={{ color: INK_COLORS.text.primary }}>
+          岗位详情
+        </h1>
+        <button 
+          onClick={handleShare}
+          className="p-2 rounded-full hover:bg-gray-100 transition-colors"
+        >
+          <ShareIcon className="w-5 h-5" style={{ color: INK_COLORS.ink.deep }} />
+        </button>
+      </header>
+
+      {/* 企业信息卡片 */}
+      <div className="mx-4 mt-4 p-4 rounded-xl shadow-sm backdrop-blur-sm"
+        style={{ backgroundColor: 'rgba(255,255,255,0.8)', border: `1px solid ${INK_COLORS.ink.medium}` }}
+      >
+        <div className="flex items-center mb-3">
+          <div className="w-12 h-12 rounded-full mr-3 flex items-center justify-center text-lg font-bold border-2"
+            style={{ borderColor: INK_COLORS.ink.medium, backgroundColor: INK_COLORS.ink.light }}
+          >
+            {job.companyLogo ? (
+              <img 
+                src={job.companyLogo} 
+                alt={job.companyName} 
+                className="w-full h-full rounded-full object-cover"
+              />
+            ) : (
+              <span style={{ color: INK_COLORS.ink.dark }}>
+                {job.companyName.slice(0, 2)}
+              </span>
+            )}
+          </div>
+          <div>
+            <h3 className="font-serif text-lg font-medium" style={{ color: INK_COLORS.text.primary }}>
+              {job.companyName}
+            </h3>
+            {job.company && (
+              <p className="text-sm" style={{ color: INK_COLORS.text.secondary }}>
+                {job.company.industry} · {job.company.size}
+              </p>
+            )}
+          </div>
+        </div>
+        
+        {job.company?.tags && (
+          <div className="flex flex-wrap gap-1">
+            {job.company.tags.map((tag, index) => (
+              <span
+                key={index}
+                className="px-2 py-1 text-xs rounded-full"
+                style={{ 
+                  backgroundColor: `${INK_COLORS.accent.blue}20`, 
+                  color: INK_COLORS.accent.blue,
+                  border: `1px solid ${INK_COLORS.accent.blue}30`
+                }}
+              >
+                {tag}
+              </span>
+            ))}
+          </div>
+        )}
+      </div>
+
+      {/* 岗位基本信息 */}
+      <div className="mx-4 mt-4 p-4 rounded-xl shadow-sm backdrop-blur-sm"
+        style={{ backgroundColor: 'rgba(255,255,255,0.8)', border: `1px solid ${INK_COLORS.ink.medium}` }}
+      >
+        <h2 className="font-serif text-xl font-bold mb-2" style={{ color: INK_COLORS.text.primary }}>
+          {job.title}
+        </h2>
+        
+        <div className="text-lg font-bold mb-3" style={{ color: INK_COLORS.accent.red }}>
+          💰 {formatSalary(job.salaryMin, job.salaryMax)}
+        </div>
+        
+        <div className="space-y-2">
+          {job.workLocation && (
+            <div className="flex items-center">
+              <MapPinIcon className="w-4 h-4 mr-2" style={{ color: INK_COLORS.text.secondary }} />
+              <span className="text-sm" style={{ color: INK_COLORS.text.secondary }}>{job.workLocation}</span>
+            </div>
+          )}
+          {job.workTime && (
+            <div className="flex items-center">
+              <ClockIcon className="w-4 h-4 mr-2" style={{ color: INK_COLORS.text.secondary }} />
+              <span className="text-sm" style={{ color: INK_COLORS.text.secondary }}>{job.workTime}</span>
+            </div>
+          )}
+          {job.educationRequired && (
+            <div className="flex items-center">
+              <AcademicCapIcon className="w-4 h-4 mr-2" style={{ color: INK_COLORS.text.secondary }} />
+              <span className="text-sm" style={{ color: INK_COLORS.text.secondary }}>{job.educationRequired}</span>
+            </div>
+          )}
+          {job.experienceRequired && (
+            <div className="flex items-center">
+              <BriefcaseIcon className="w-4 h-4 mr-2" style={{ color: INK_COLORS.text.secondary }} />
+              <span className="text-sm" style={{ color: INK_COLORS.text.secondary }}>{job.experienceRequired}</span>
+            </div>
+          )}
+        </div>
+      </div>
+
+      {/* 岗位介绍 */}
+      <div className="mx-4 mt-4 p-4 rounded-xl shadow-sm backdrop-blur-sm"
+        style={{ backgroundColor: 'rgba(255,255,255,0.8)', border: `1px solid ${INK_COLORS.ink.medium}` }}
+      >
+        <h3 className="font-serif text-lg font-medium mb-2" style={{ color: INK_COLORS.text.primary }}>
+          岗位介绍
+        </h3>
+        <p className="text-sm leading-relaxed" style={{ color: INK_COLORS.text.secondary }}>
+          {job.description}
+        </p>
+      </div>
+
+      {/* 详细职责 */}
+      <div className="mx-4 mt-4 p-4 rounded-xl shadow-sm backdrop-blur-sm"
+        style={{ backgroundColor: 'rgba(255,255,255,0.8)', border: `1px solid ${INK_COLORS.ink.medium}` }}
+      >
+        <h3 className="font-serif text-lg font-medium mb-2" style={{ color: INK_COLORS.text.primary }}>
+          详细职责
+        </h3>
+        <p className="text-sm leading-relaxed" style={{ color: INK_COLORS.text.secondary }}>
+          {job.details || job.requirements || '负责为社区老年人提供日常陪伴、生活协助和情感关怀服务,需要具备良好的沟通能力和耐心,能够理解和满足老年人的需求。'}
+        </p>
+      </div>
+
+      {/* 福利待遇 */}
+      <div className="mx-4 mt-4 p-4 rounded-xl shadow-sm backdrop-blur-sm"
+        style={{ backgroundColor: 'rgba(255,255,255,0.8)', border: `1px solid ${INK_COLORS.ink.medium}` }}
+      >
+        <h3 className="font-serif text-lg font-medium mb-2" style={{ color: INK_COLORS.text.primary }}>
+          福利待遇
+        </h3>
+        <div className="flex flex-wrap gap-2">
+          {job.benefits.map((benefit, index) => (
+            <span
+              key={index}
+              className="px-3 py-1 text-sm rounded-full"
+              style={{ 
+                backgroundColor: `${INK_COLORS.accent.green}20`, 
+                color: INK_COLORS.accent.green,
+                border: `1px solid ${INK_COLORS.accent.green}30`
+              }}
+            >
+              {benefit}
+            </span>
+          ))}
+        </div>
+      </div>
+
+      {/* 岗位热度 */}
+      <div className="mx-4 mt-4 p-4 rounded-xl shadow-sm backdrop-blur-sm"
+        style={{ backgroundColor: 'rgba(255,255,255,0.8)', border: `1px solid ${INK_COLORS.ink.medium}` }}
+      >
+        <h3 className="font-serif text-lg font-medium mb-2" style={{ color: INK_COLORS.text.primary }}>
+          岗位热度
+        </h3>
+        <div className="grid grid-cols-3 gap-2 text-center">
+          <div>
+            <div className="text-lg font-bold" style={{ color: INK_COLORS.accent.blue }}>
+              {job.views}
+            </div>
+            <div className="text-xs" style={{ color: INK_COLORS.text.light }}>浏览</div>
+          </div>
+          <div>
+            <div className="text-lg font-bold" style={{ color: INK_COLORS.accent.green }}>
+              {job.favoriteCount || 0}
+            </div>
+            <div className="text-xs" style={{ color: INK_COLORS.text.light }}>收藏</div>
+          </div>
+          <div>
+            <div className="text-lg font-bold" style={{ color: INK_COLORS.accent.red }}>
+              {job.applications}
+            </div>
+            <div className="text-xs" style={{ color: INK_COLORS.text.light }}>投递</div>
+          </div>
+        </div>
+      </div>
+
+      {/* 发布信息 */}
+      <div className="mx-4 mt-4 mb-4 p-4 rounded-xl shadow-sm backdrop-blur-sm"
+        style={{ backgroundColor: 'rgba(255,255,255,0.8)', border: `1px solid ${INK_COLORS.ink.medium}` }}
+      >
+        <div className="flex items-center justify-between">
+          <span className="text-sm" style={{ color: INK_COLORS.text.light }}>
+            发布时间: {formatPublishDate(job.publishDate)}
+          </span>
+          <span className="text-sm" style={{ color: INK_COLORS.text.light }}>
+            岗位编号: #{job.id.toString().padStart(6, '0')}
+          </span>
+        </div>
+      </div>
+
+      {/* 底部操作栏 */}
+      <div className="fixed bottom-0 left-0 right-0 p-4 shadow-lg border-t"
+        style={{ backgroundColor: INK_COLORS.ink.light, borderColor: INK_COLORS.ink.medium }}
+      >
+        <div className="flex space-x-3">
+          <button 
+            onClick={handleApply}
+            className="flex-1 px-4 py-3 rounded-full text-sm font-medium transition-all duration-300 hover:scale-105 active:scale-95"
+            style={{ 
+              backgroundColor: INK_COLORS.accent.blue, 
+              color: 'white',
+              border: `1px solid ${INK_COLORS.accent.blue}`
+            }}
+          >
+            立即投递
+          </button>
+          <button 
+            onClick={handleFavorite}
+            className={`p-3 rounded-full transition-all duration-300 hover:scale-105 active:scale-95 ${
+              isFavorited ? 'bg-red-500 text-white' : 'border-2'
+            }`}
+            style={!isFavorited ? { 
+              backgroundColor: 'transparent', 
+              color: INK_COLORS.accent.red,
+              border: `2px solid ${INK_COLORS.accent.red}`
+            } : {}}
+          >
+            {isFavorited ? (
+              <HeartSolidIcon className="w-5 h-5" />
+            ) : (
+              <HeartIcon className="w-5 h-5" />
+            )}
+          </button>
+          <button 
+            className="p-3 rounded-full transition-all duration-300 hover:scale-105 active:scale-95 border-2"
+            style={{ 
+              backgroundColor: 'transparent', 
+              color: INK_COLORS.accent.green,
+              border: `2px solid ${INK_COLORS.accent.green}`
+            }}
+          >
+            <ChatBubbleLeftIcon className="w-5 h-5" />
+          </button>
+        </div>
+      </div>
+
+      {/* 分享模态框 */}
+      {showShareModal && (
+        <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-30">
+          <div className="bg-white rounded-xl p-4 m-4 max-w-sm">
+            <h3 className="font-serif text-lg font-medium mb-4 text-center">分享给朋友</h3>
+            <div className="grid grid-cols-4 gap-4">
+              <button className="flex flex-col items-center">
+                <div className="w-12 h-12 rounded-full bg-green-500 flex items-center justify-center mb-2">
+                  <span className="text-white text-xl">📱</span>
+                </div>
+                <span className="text-xs">微信</span>
+              </button>
+              <button className="flex flex-col items-center">
+                <div className="w-12 h-12 rounded-full bg-blue-500 flex items-center justify-center mb-2">
+                  <span className="text-white text-xl">📧</span>
+                </div>
+                <span className="text-xs">邮件</span>
+              </button>
+              <button className="flex flex-col items-center">
+                <div className="w-12 h-12 rounded-full bg-red-500 flex items-center justify-center mb-2">
+                  <span className="text-white text-xl">🔗</span>
+                </div>
+                <span className="text-xs">复制链接</span>
+              </button>
+              <button className="flex flex-col items-center">
+                <div className="w-12 h-12 rounded-full bg-purple-500 flex items-center justify-center mb-2">
+                  <span className="text-white text-xl">📋</span>
+                </div>
+                <span className="text-xs">更多</span>
+              </button>
+            </div>
+            <button 
+              onClick={() => setShowShareModal(false)}
+              className="w-full mt-4 py-2 rounded-full text-sm"
+              style={{ backgroundColor: INK_COLORS.ink.medium, color: INK_COLORS.text.primary }}
+            >
+              取消
+            </button>
+          </div>
+        </div>
+      )}
+    </div>
+  );
+};
+
+export default JobDetailPage;

+ 18 - 8
src/client/mobile/pages/ProfilePage.tsx

@@ -156,16 +156,26 @@ const ProfilePage: React.FC = () => {
         <UserStatsCard stats={homeData.userStats} />
       )}
 
-      {/* 用户信息卡片 */}
+      {/* 用户信息卡片 - 添加企业信息按钮 */}
       <div className="bg-white rounded-lg shadow p-6 mb-4">
-        <div className="flex items-center">
-          <div className="w-16 h-16 bg-blue-500 rounded-full flex items-center justify-center text-white text-2xl">
-            {user.username.charAt(0).toUpperCase()}
-          </div>
-          <div className="ml-4">
-            <h2 className="text-lg font-semibold text-gray-900">{user.username}</h2>
-            <p className="text-sm text-gray-600">{user.email}</p>
+        <div className="flex items-center justify-between">
+          <div className="flex items-center">
+            <div className="w-16 h-16 bg-blue-500 rounded-full flex items-center justify-center text-white text-2xl">
+              {user.username.charAt(0).toUpperCase()}
+            </div>
+            <div className="ml-4">
+              <h2 className="text-lg font-semibold text-gray-900">{user.username}</h2>
+              <p className="text-sm text-gray-600">{user.email}</p>
+            </div>
           </div>
+          <Button
+            type="primary"
+            size="small"
+            onClick={() => navigate('/profile/company')}
+            className="bg-blue-500 hover:bg-blue-600"
+          >
+            企业信息
+          </Button>
         </div>
       </div>
 

+ 3 - 2
src/client/mobile/pages/SilverJobsPage.tsx

@@ -1,15 +1,16 @@
 import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
 import { JobCard } from '@/client/mobile/components/silver-jobs/JobCard';
 import { useJobs, JobItem } from '@/client/mobile/hooks/useJobs';
 import { INK_COLORS } from '@/client/mobile/styles/colors';
 
 const SilverJobsPage: React.FC = () => {
   const { jobs, loading, error } = useJobs();
+  const navigate = useNavigate();
   const [activeFilter, setActiveFilter] = useState('全部');
 
   const handleJobPress = (job: JobItem) => {
-    console.log('查看岗位详情:', job.title);
-    // TODO: 导航到岗位详情页
+    navigate(`/silver-jobs/${job.id}`);
   };
 
   const filteredJobs = jobs.filter(job => {

+ 10 - 0
src/client/mobile/routes.tsx

@@ -10,6 +10,7 @@ import { silverPlatformTabs } from './components/BottomTabBar';
 // 页面组件导入
 import HomePage from './pages/HomePage';
 import SilverJobsPage from './pages/SilverJobsPage';
+import JobDetailPage from './pages/JobDetailPage';
 import SilverTalentsPage from './pages/SilverTalentsPage';
 import SilverWisdomPage from './pages/SilverWisdomPage';
 import ElderlyUniversityPage from './pages/ElderlyUniversityPage';
@@ -19,6 +20,7 @@ import PolicyNewsDetailPage from './pages/PolicyNewsDetailPage';
 import PublishPage from './pages/PublishPage';
 import ProfilePage from './pages/ProfilePage';
 import ProfileEditPage from './pages/ProfileEditPage';
+import CompanyProfilePage from './pages/CompanyProfilePage';
 import PointsPage from './pages/PointsPage';
 import MyPostsPage from './pages/MyPostsPage';
 import MyFavoritesPage from './pages/MyFavoritesPage';
@@ -92,6 +94,14 @@ export const router = createBrowserRouter([
           </ProtectedRoute>
         )
       },
+      {
+        path: 'profile/company',
+        element: (
+          <ProtectedRoute>
+            <CompanyProfilePage />
+          </ProtectedRoute>
+        )
+      },
       {
         path: 'profile/points',
         element: (

+ 8 - 0
src/server/api.ts

@@ -5,6 +5,7 @@ import authRoute from './api/auth/index'
 import rolesRoute from './api/roles/index'
 import fileRoutes from './api/files/index'
 import companyRoutes from './api/silver-jobs/companies'
+import myCompanyRoutes from './api/silver-jobs/companies/my'
 import jobRoutes from './api/silver-jobs/jobs'
 import applicationRoutes from './api/silver-jobs/applications'
 import favoriteRoutes from './api/silver-jobs/favorites'
@@ -66,6 +67,7 @@ const authRoutes = api.route('/api/v1/auth', authRoute)
 const roleRoutes = api.route('/api/v1/roles', rolesRoute)
 const fileApiRoutes = api.route('/api/v1/files', fileRoutes)
 const companyApiRoutes = api.route('/api/v1/silver-jobs/companies', companyRoutes)
+const myCompanyApiRoutes = api.route('/api/v1/companies/my', myCompanyRoutes)
 const jobApiRoutes = api.route('/api/v1/silver-jobs/jobs', jobRoutes)
 const applicationApiRoutes = api.route('/api/v1/silver-jobs/applications', applicationRoutes)
 const favoriteApiRoutes = api.route('/api/v1/silver-jobs/favorites', favoriteRoutes)
@@ -85,11 +87,16 @@ const silverUsersKnowledgeStatsApiRoutes = api.route('/api/v1/silver-users/knowl
 const silverUsersKnowledgeInteractionsApiRoutes = api.route('/api/v1/silver-users/knowledge-interactions', silverUsersRoutes.knowledgeInteractions)
 const silverUsersKnowledgeRankingsApiRoutes = api.route('/api/v1/silver-users/knowledge-rankings', silverUsersRoutes.knowledgeRankings)
 
+// 注册 silver-user-profiles 路由
+import silverUserProfileRoutes from './api/silver-users/profiles/index'
+const silverUserProfileApiRoutes = api.route('/api/v1/silver-users/profiles', silverUserProfileRoutes)
+
 export type AuthRoutes = typeof authRoutes
 export type UserRoutes = typeof userRoutes
 export type RoleRoutes = typeof roleRoutes
 export type FileRoutes = typeof fileApiRoutes
 export type CompanyRoutes = typeof companyApiRoutes
+export type MyCompanyRoutes = typeof myCompanyApiRoutes
 export type JobRoutes = typeof jobApiRoutes
 export type ApplicationRoutes = typeof applicationApiRoutes
 export type FavoriteRoutes = typeof favoriteApiRoutes
@@ -106,5 +113,6 @@ export type SilverUsersKnowledgeTagsRoutes = typeof silverUsersKnowledgeTagsApiR
 export type SilverUsersKnowledgeStatsRoutes = typeof silverUsersKnowledgeStatsApiRoutes
 export type SilverUsersKnowledgeInteractionsRoutes = typeof silverUsersKnowledgeInteractionsApiRoutes
 export type SilverUsersKnowledgeRankingsRoutes = typeof silverUsersKnowledgeRankingsApiRoutes
+export type SilverUserProfileRoutes = typeof silverUserProfileApiRoutes
 
 export default api

+ 169 - 0
src/server/api/silver-jobs/companies/my/index.ts

@@ -0,0 +1,169 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from '@hono/zod-openapi';
+import { Company } from '@/server/modules/silver-jobs/company.entity';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AppDataSource } from '@/server/data-source';
+import { AuthContext } from '@/server/types/context';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+import { CompanyService } from '@/server/modules/silver-jobs/company.service';
+
+// 获取当前用户的企业信息
+const getMyCompanyRoute = createRoute({
+  method: 'get',
+  path: '/',
+  middleware: [authMiddleware],
+  responses: {
+    200: {
+      description: '成功获取企业信息',
+      content: { 
+        'application/json': { 
+          schema: z.object({
+            data: z.any().nullable(),
+            hasCompany: z.boolean()
+          })
+        } 
+      }
+    },
+    401: {
+      description: '未授权',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+// 创建或更新企业信息
+const upsertMyCompanyRoute = createRoute({
+  method: 'post',
+  path: '/',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': { schema: z.any() }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '成功创建或更新企业信息',
+      content: { 'application/json': { schema: z.any() } }
+    },
+    400: {
+      description: '请求参数错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '未授权',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+// 更新企业信息
+const updateMyCompanyRoute = createRoute({
+  method: 'put',
+  path: '/',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': { schema: z.any() }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '成功更新企业信息',
+      content: { 'application/json': { schema: z.any() } }
+    },
+    400: {
+      description: '请求参数错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '未授权',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>();
+
+// 获取当前用户的企业信息
+app.openapi(getMyCompanyRoute, async (c) => {
+  try {
+    const user = c.get('user');
+    if (!user) {
+      return c.json({ code: 401, message: '未授权访问' }, 401);
+    }
+
+    const service = new CompanyService(AppDataSource);
+    const company = await service.findByUserId(user.id);
+
+    return c.json({
+      data: company,
+      hasCompany: !!company
+    }, 200);
+  } catch (error) {
+    console.error('获取企业信息失败:', error);
+    return c.json({ code: 500, message: '获取企业信息失败' }, 500);
+  }
+});
+
+// 创建或更新企业信息
+app.openapi(upsertMyCompanyRoute, async (c) => {
+  try {
+    const user = c.get('user');
+    if (!user) {
+      return c.json({ code: 401, message: '未授权访问' }, 401);
+    }
+
+    const data = await c.req.json();
+    const service = new CompanyService(AppDataSource);
+    
+    const company = await service.upsertByUserId(user.id, data);
+    
+    return c.json(company, 200);
+  } catch (error) {
+    console.error('创建或更新企业信息失败:', error);
+    return c.json({ code: 500, message: '操作失败' }, 500);
+  }
+});
+
+// 更新企业信息
+app.openapi(updateMyCompanyRoute, async (c) => {
+  try {
+    const user = c.get('user');
+    if (!user) {
+      return c.json({ code: 401, message: '未授权访问' }, 401);
+    }
+
+    const data = await c.req.json();
+    const service = new CompanyService(AppDataSource);
+    
+    const company = await service.updateByUserId(user.id, data);
+    
+    if (!company) {
+      return c.json({ code: 404, message: '企业信息不存在' }, 404);
+    }
+    
+    return c.json(company, 200);
+  } catch (error) {
+    console.error('更新企业信息失败:', error);
+    return c.json({ code: 500, message: '更新失败' }, 500);
+  }
+});
+
+export default app;

+ 5 - 5
src/server/api/silver-users/profiles/index.ts

@@ -1,13 +1,13 @@
 import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
-import { SilverUserProfile } from '@/server/modules/silver-users/silver-user-profile.entity';
+import { SilverUserProfile, SilverUserProfileSchema, CreateSilverUserProfileDto, UpdateSilverUserProfileDto } from '@/server/modules/silver-users/silver-user-profile.entity';
 import { authMiddleware } from '@/server/middleware/auth.middleware';
 
 const silverUserProfileRoutes = createCrudRoutes({
   entity: SilverUserProfile,
-  createSchema: {} as any, // 简化处理
-  updateSchema: {} as any, // 简化处理
-  getSchema: {} as any, // 简化处理
-  listSchema: {} as any, // 简化处理
+  createSchema: CreateSilverUserProfileDto,
+  updateSchema: UpdateSilverUserProfileDto,
+  getSchema: SilverUserProfileSchema,
+  listSchema: SilverUserProfileSchema,
   searchFields: ['realName', 'nickname', 'organization', 'personalSkills', 'personalExperience'],
   middleware: [authMiddleware],
   userTracking: {

+ 13 - 1
src/server/modules/silver-jobs/company.entity.ts

@@ -1,7 +1,8 @@
-import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn } from 'typeorm';
+import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn, OneToOne, JoinColumn } from 'typeorm';
 import { Job } from './job.entity';
 import { CompanyImage } from './company-image.entity';
 import { z } from '@hono/zod-openapi';
+import { UserEntity } from '../users/user.entity';
 
 export const CompanySchema = z.object({
   id: z.number().int().positive().openapi({
@@ -60,6 +61,10 @@ export const CompanySchema = z.object({
     description: '收藏人数',
     example: 23
   }),
+  userId: z.number().int().positive().openapi({
+    description: '用户ID',
+    example: 1
+  }),
   createdAt: z.date().openapi({
     description: '创建时间',
     example: '2024-01-01T00:00:00Z'
@@ -159,6 +164,13 @@ export class Company {
   @Column({ name: 'favorite_count', type: 'int', default: 0 })
   favoriteCount!: number;
 
+  @Column({ name: 'user_id', type: 'int', unsigned: true, unique: true })
+  userId!: number;
+
+  @OneToOne(() => UserEntity, user => user.id)
+  @JoinColumn({ name: 'user_id' })
+  user!: UserEntity;
+
   @CreateDateColumn({ name: 'created_at' })
   createdAt!: Date;
 

+ 142 - 4
src/server/modules/silver-jobs/company.service.ts

@@ -1,9 +1,147 @@
-import { GenericCrudService } from '@/server/utils/generic-crud.service';
-import { DataSource } from 'typeorm';
+import { DataSource, Repository } from 'typeorm';
 import { Company } from './company.entity';
+import { UserEntity } from '../users/user.entity';
+
+export class CompanyService {
+  private companyRepository: Repository<Company>;
+  private userRepository: Repository<UserEntity>;
 
-export class CompanyService extends GenericCrudService<Company> {
   constructor(dataSource: DataSource) {
-    super(dataSource, Company);
+    this.companyRepository = dataSource.getRepository(Company);
+    this.userRepository = dataSource.getRepository(UserEntity);
+  }
+
+  /**
+   * 根据用户ID查找企业信息
+   */
+  async findByUserId(userId: number): Promise<Company | null> {
+    return await this.companyRepository.findOne({
+      where: { userId }
+    });
+  }
+
+  /**
+   * 根据用户ID创建或更新企业信息
+   */
+  async upsertByUserId(userId: number, data: any): Promise<Company> {
+    // 检查是否已存在企业信息
+    const existing = await this.findByUserId(userId);
+    
+    if (existing) {
+      // 更新现有企业信息
+      Object.assign(existing, data);
+      return await this.companyRepository.save(existing);
+    } else {
+      // 创建新企业信息
+      const company = this.companyRepository.create({
+        ...data,
+        userId
+      });
+      return await this.companyRepository.save(company);
+    }
+  }
+
+  /**
+   * 根据用户ID更新企业信息
+   */
+  async updateByUserId(userId: number, data: any): Promise<Company | null> {
+    const company = await this.findByUserId(userId);
+    if (!company) {
+      return null;
+    }
+
+    Object.assign(company, data);
+    return await this.companyRepository.save(company);
+  }
+
+  /**
+   * 创建企业信息
+   */
+  async create(data: any): Promise<Company> {
+    const company = this.companyRepository.create(data);
+    return await this.companyRepository.save(company);
+  }
+
+  /**
+   * 根据ID查找企业信息
+   */
+  async findById(id: number): Promise<Company | null> {
+    return await this.companyRepository.findOne({
+      where: { id },
+      relations: ['jobs', 'companyImages']
+    });
+  }
+
+  /**
+   * 获取企业列表
+   */
+  async findAll(options?: {
+    page?: number;
+    pageSize?: number;
+    keyword?: string;
+    isCertified?: number;
+  }): Promise<[Company[], number]> {
+    const query = this.companyRepository.createQueryBuilder('company');
+
+    if (options?.keyword) {
+      query.andWhere('company.name LIKE :keyword OR company.shortName LIKE :keyword', {
+        keyword: `%${options.keyword}%`
+      });
+    }
+
+    if (options?.isCertified !== undefined) {
+      query.andWhere('company.isCertified = :isCertified', {
+        isCertified: options.isCertified
+      });
+    }
+
+    if (options?.page && options?.pageSize) {
+      query.skip((options.page - 1) * options.pageSize);
+      query.take(options.pageSize);
+    }
+
+    return await query.getManyAndCount();
+  }
+
+  /**
+   * 更新企业信息
+   */
+  async update(id: number, data: any): Promise<Company | null> {
+    const company = await this.findById(id);
+    if (!company) {
+      return null;
+    }
+
+    Object.assign(company, data);
+    return await this.companyRepository.save(company);
+  }
+
+  /**
+   * 删除企业信息
+   */
+  async delete(id: number): Promise<boolean> {
+    const result = await this.companyRepository.delete(id);
+    return result.affected !== undefined && result.affected! > 0;
+  }
+
+  /**
+   * 增加查看次数
+   */
+  async incrementViewCount(id: number): Promise<void> {
+    await this.companyRepository.increment({ id }, 'viewCount', 1);
+  }
+
+  /**
+   * 增加收藏次数
+   */
+  async incrementFavoriteCount(id: number): Promise<void> {
+    await this.companyRepository.increment({ id }, 'favoriteCount', 1);
+  }
+
+  /**
+   * 减少收藏次数
+   */
+  async decrementFavoriteCount(id: number): Promise<void> {
+    await this.companyRepository.decrement({ id }, 'favoriteCount', 1);
   }
 }

+ 72 - 1
src/server/modules/silver-users/silver-user-profile.entity.ts

@@ -1,5 +1,6 @@
 import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
 import { UserEntity } from '../users/user.entity';
+import { z } from '@hono/zod-openapi';
 
 export enum Gender {
   MALE = 1,
@@ -132,4 +133,74 @@ export class SilverUserProfile {
 
   @Column({ name: 'updated_by', type: 'int', nullable: true })
   updatedBy!: number | null;
-}
+}
+
+// Zod Schemas for API
+export const SilverUserProfileSchema = z.object({
+  id: z.number().int().positive().openapi({ description: '用户资料ID', example: 1 }),
+  userId: z.number().int().positive().openapi({ description: '关联用户ID', example: 1 }),
+  realName: z.string().max(50).openapi({ description: '真实姓名', example: '张三' }),
+  nickname: z.string().max(50).nullable().optional().openapi({ description: '昵称', example: '张大爷' }),
+  organization: z.string().max(255).nullable().optional().openapi({ description: '所属组织/机构', example: '社区服务中心' }),
+  age: z.number().int().min(1).max(120).openapi({ description: '年龄', example: 65 }),
+  gender: z.number().int().min(1).max(3).openapi({ description: '性别:1-男,2-女,3-其他', example: 1 }),
+  phone: z.string().max(20).openapi({ description: '联系电话', example: '13800138000' }),
+  email: z.string().max(255).email().nullable().optional().openapi({ description: '电子邮箱', example: 'example@email.com' }),
+  avatarUrl: z.string().max(500).url().nullable().optional().openapi({ description: '头像URL', example: 'https://example.com/avatar.jpg' }),
+  personalIntro: z.string().nullable().optional().openapi({ description: '个人简介', example: '退休教师,热爱教育事业' }),
+  personalSkills: z.string().nullable().optional().openapi({ description: '个人技能', example: '书法、绘画、音乐教学' }),
+  personalExperience: z.string().nullable().optional().openapi({ description: '个人经历', example: '从事教育工作40年,经验丰富' }),
+  certificationStatus: z.number().int().min(0).max(3).openapi({ description: '认证状态:0-未认证,1-认证中,2-已认证,3-已拒绝', example: 0 }),
+  certificationInfo: z.string().nullable().optional().openapi({ description: '认证信息', example: '教师资格证、工作经验证明' }),
+  jobSeekingStatus: z.number().int().min(0).max(2).openapi({ description: '求职状态:0-未求职,1-积极求职,2-观望机会', example: 0 }),
+  jobSeekingRequirements: z.string().nullable().optional().openapi({ description: '求职需求', example: '希望找兼职教师工作,每周工作3天' }),
+  totalPoints: z.number().int().min(0).openapi({ description: '总积分', example: 100 }),
+  resumeCount: z.number().int().min(0).openapi({ description: '简历数量', example: 1 }),
+  applicationCount: z.number().int().min(0).openapi({ description: '投递数量', example: 5 }),
+  timeBankHours: z.number().openapi({ description: '时间银行小时数', example: 10.5 }),
+  knowledgeContributions: z.number().int().min(0).openapi({ description: '知识贡献数', example: 3 }),
+  knowledgeShareCount: z.number().int().min(0).openapi({ description: '知识分享数', example: 3 }),
+  knowledgeDownloadCount: z.number().int().min(0).openapi({ description: '知识下载数', example: 15 }),
+  knowledgeLikeCount: z.number().int().min(0).openapi({ description: '知识点赞数', example: 25 }),
+  knowledgeReadCount: z.number().int().min(0).openapi({ description: '知识阅读数', example: 100 }),
+  knowledgeFavoriteCount: z.number().int().min(0).openapi({ description: '知识收藏数', example: 8 }),
+  knowledgeCommentCount: z.number().int().min(0).openapi({ description: '知识评论数', example: 5 }),
+  knowledgeRanking: z.number().int().min(0).openapi({ description: '知识排名', example: 1 }),
+  knowledgeRankingScore: z.number().openapi({ description: '知识排名分数', example: 95.5 }),
+  createdAt: z.date().openapi({ description: '创建时间', example: '2024-01-01T00:00:00Z' }),
+  updatedAt: z.date().openapi({ description: '更新时间', example: '2024-01-01T00:00:00Z' }),
+  createdBy: z.number().int().positive().nullable().optional().openapi({ description: '创建人ID', example: 1 }),
+  updatedBy: z.number().int().positive().nullable().optional().openapi({ description: '更新人ID', example: 1 })
+});
+
+export const CreateSilverUserProfileDto = z.object({
+  realName: z.string().max(50).openapi({ description: '真实姓名', example: '张三' }),
+  age: z.coerce.number().int().min(1).max(120).openapi({ description: '年龄', example: 65 }),
+  gender: z.coerce.number().int().min(1).max(3).openapi({ description: '性别:1-男,2-女,3-其他', example: 1 }),
+  phone: z.string().max(20).openapi({ description: '联系电话', example: '13800138000' }),
+  nickname: z.string().max(50).optional().openapi({ description: '昵称', example: '张大爷' }),
+  organization: z.string().max(255).optional().openapi({ description: '所属组织/机构', example: '社区服务中心' }),
+  email: z.string().max(255).email().optional().openapi({ description: '电子邮箱', example: 'example@email.com' }),
+  avatarUrl: z.string().max(500).url().optional().openapi({ description: '头像URL', example: 'https://example.com/avatar.jpg' }),
+  personalIntro: z.string().optional().openapi({ description: '个人简介', example: '退休教师,热爱教育事业' }),
+  personalSkills: z.string().optional().openapi({ description: '个人技能', example: '书法、绘画、音乐教学' }),
+  personalExperience: z.string().optional().openapi({ description: '个人经历', example: '从事教育工作40年,经验丰富' }),
+  certificationInfo: z.string().optional().openapi({ description: '认证信息', example: '教师资格证、工作经验证明' }),
+  jobSeekingRequirements: z.string().optional().openapi({ description: '求职需求', example: '希望找兼职教师工作,每周工作3天' })
+});
+
+export const UpdateSilverUserProfileDto = z.object({
+  realName: z.string().max(50).optional().openapi({ description: '真实姓名', example: '张三' }),
+  age: z.coerce.number().int().min(1).max(120).optional().openapi({ description: '年龄', example: 65 }),
+  gender: z.coerce.number().int().min(1).max(3).optional().openapi({ description: '性别:1-男,2-女,3-其他', example: 1 }),
+  phone: z.string().max(20).optional().openapi({ description: '联系电话', example: '13800138000' }),
+  nickname: z.string().max(50).optional().openapi({ description: '昵称', example: '张大爷' }),
+  organization: z.string().max(255).optional().openapi({ description: '所属组织/机构', example: '社区服务中心' }),
+  email: z.string().max(255).email().optional().openapi({ description: '电子邮箱', example: 'example@email.com' }),
+  avatarUrl: z.string().max(500).url().optional().openapi({ description: '头像URL', example: 'https://example.com/avatar.jpg' }),
+  personalIntro: z.string().optional().openapi({ description: '个人简介', example: '退休教师,热爱教育事业' }),
+  personalSkills: z.string().optional().openapi({ description: '个人技能', example: '书法、绘画、音乐教学' }),
+  personalExperience: z.string().optional().openapi({ description: '个人经历', example: '从事教育工作40年,经验丰富' }),
+  certificationInfo: z.string().optional().openapi({ description: '认证信息', example: '教师资格证、工作经验证明' }),
+  jobSeekingRequirements: z.string().optional().openapi({ description: '求职需求', example: '希望找兼职教师工作,每周工作3天' })
+});

+ 6 - 2
src/server/modules/users/user.entity.ts

@@ -1,4 +1,4 @@
-import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
+import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable, CreateDateColumn, UpdateDateColumn, OneToMany, OneToOne } from 'typeorm';
 import { Role, RoleSchema } from './role.entity';
 import { SilverUserProfile } from '../silver-users/silver-user-profile.entity';
 import { UserPreference } from '../silver-users/user-preference.entity';
@@ -8,6 +8,7 @@ import { SilverKnowledgeInteraction } from '../silver-users/silver-knowledge-int
 import { Application } from '../silver-jobs/application.entity';
 import { Favorite } from '../silver-jobs/favorite.entity';
 import { ViewRecord } from '../silver-jobs/view-record.entity';
+import { Company } from '../silver-jobs/company.entity';
 import { z } from '@hono/zod-openapi';
 import { DeleteStatus, DisabledStatus } from '@/share/types';
 
@@ -47,7 +48,7 @@ export class UserEntity {
   @JoinTable()
   roles!: Role[];
 
-  @OneToMany(() => SilverUserProfile, profile => profile.user)
+  @OneToOne(() => SilverUserProfile, profile => profile.user)
   silverUserProfile!: SilverUserProfile;
 
   @OneToMany(() => UserPreference, preference => preference.user)
@@ -71,6 +72,9 @@ export class UserEntity {
   @OneToMany(() => ViewRecord, viewRecord => viewRecord.user)
   viewRecords!: ViewRecord[];
 
+  @OneToOne(() => Company, company => company.user)
+  company!: Company | null;
+
   @CreateDateColumn({ name: 'created_at', type: 'timestamp' })
   createdAt!: Date;