Jelajahi Sumber

✨ feat(publish): implement silver wisdom publish feature

- add new documentation for silver wisdom publish implementation plan
- create PublishKnowledgeForm component for wisdom publishing
- modify PublishPage to replace "发布服务" tab with "发布智库"
- update PublishTalentForm button text from "发布人才信息" to "发布"
- add knowledge publishing API integration and form validation

📝 docs: add silver wisdom publish implementation plan

- document entity structure, API interface and implementation steps
- define UI design specifications and responsive adaptation requirements
- create deployment checklist and future expansion plan

♻️ refactor(publish): update publish page tab configuration

- replace service tab with wisdom tab in PublishPage
- update tab icon to BookOpenIcon for better representation
- adjust tab description to "分享您的知识和经验"
- update form display logic for wisdom tab
yourname 8 bulan lalu
induk
melakukan
0970bc3485

+ 171 - 0
docs/silver-wisdom-publish-implementation-plan.md

@@ -0,0 +1,171 @@
+# 银龄智库发布功能实施计划
+
+## 项目概述
+将移动端发布页面的"发布服务"标签改为"发布智库",并创建相应的银龄智库发布表单,确保数据能够正确写入银龄智库实体。
+
+## 技术架构
+
+### 1. 实体结构分析
+- **实体名称**: SilverUserProfile (银龄智库)
+- **核心字段**:
+  - realName: 真实姓名
+  - age: 年龄 (50-100岁)
+  - gender: 性别 (MALE/FEMALE/OTHER)
+  - phone: 联系电话
+  - personalIntro: 个人简介 (≤1000字)
+  - personalSkills: 个人技能 (≤2000字)
+  - personalExperience: 个人经历 (≤3000字)
+  - jobSeekingRequirements: 求职需求 (≤1000字)
+  - avatarUrl: 个人头像
+  - organization: 所属机构
+
+### 2. API接口
+- **创建智库**: POST /api/v1/silver-users/profiles
+- **数据验证**: 使用CreateSilverTalentDto进行验证
+- **认证要求**: 需要登录用户权限
+
+## 实施步骤
+
+### 步骤1:修改发布页面标签
+**文件**: `src/client/mobile/pages/PublishPage.tsx`
+**变更内容**:
+```typescript
+// 修改前
+{
+  id: 'service',
+  name: '发布服务',
+  icon: WrenchScrewdriverIcon,
+  description: '发布专业服务信息',
+  color: INK_COLORS.accent.red
+}
+
+// 修改后
+{
+  id: 'wisdom',
+  name: '发布智库',
+  icon: LightBulbIcon, // 使用更适合的图标
+  description: '发布银龄智库专家信息',
+  color: INK_COLORS.accent.red
+}
+```
+
+### 步骤2:更新表单显示逻辑
+**文件**: `src/client/mobile/pages/PublishPage.tsx`
+**变更内容**:
+```typescript
+// 修改表单显示条件
+{activeTab === 'wisdom' && (
+  <PublishWisdomForm
+    onSuccess={handleFormSuccess}
+    onCancel={handleFormCancel}
+  />
+)}
+```
+
+### 步骤3:创建智库发布表单组件
+**文件**: `src/client/mobile/components/PublishWisdomForm.tsx`
+**功能特性**:
+- 基于现有PublishTalentForm组件改造
+- 移除求职相关字段(jobSeekingRequirements)
+- 突出智库专家特色字段
+- 保持水墨风格UI设计
+- 支持头像上传功能
+
+### 步骤4:UI设计规范
+**遵循移动端水墨风格**:
+- 主背景色: #f5f3f0 (宣纸色)
+- 卡片背景: rgba(255,255,255,0.8)
+- 边框色: var(--ink-medium) (#d4c4a8)
+- 文字色: var(--text-primary) (#2f1f0f)
+- 按钮样式: 圆润边角,淡墨边框
+
+### 步骤5:表单字段优化
+**智库专家特色字段**:
+1. 基本信息区域
+   - 个人头像(带预览)
+   - 真实姓名*
+   - 年龄*(50-100岁选择)
+   - 性别*
+   - 联系电话*
+
+2. 专业展示区域
+   - 所属机构
+   - 个人简介(突出专业背景)
+   - 专业技能(专家领域)
+   - 个人经历(突出权威性)
+
+3. 视觉设计
+   - 使用卡片式布局
+   - 添加适当的图标装饰
+   - 支持实时字数统计
+   - 自动保存草稿功能
+
+### 步骤6:数据提交验证
+**验证规则**:
+- 必填字段:姓名、年龄、性别、电话
+- 字符限制:按后端DTO要求
+- 手机号格式:中国大陆手机号
+- 图片大小:≤2MB
+- 图片格式:JPG/PNG/WebP
+
+### 步骤7:测试验证
+**测试场景**:
+1. 页面加载正常,标签显示正确
+2. 表单字段验证功能正常
+3. 图片上传功能正常
+4. 数据提交成功,写入数据库
+5. 水墨风格UI显示正常
+6. 移动端适配良好
+
+## 文件结构变更
+
+```
+src/client/mobile/
+├── pages/
+│   └── PublishPage.tsx (修改)
+├── components/
+│   ├── PublishWisdomForm.tsx (新增)
+│   └── PublishTalentForm.tsx (已有)
+└── styles/
+    └── colors.ts (已有)
+```
+
+## 技术实现要点
+
+### 1. 组件复用策略
+- 基础表单结构复用PublishTalentForm
+- 移除不必要的字段
+- 调整文案和提示信息
+
+### 2. 图标选择
+- 使用LightBulbIcon(灯泡图标)代表智库
+- 保持图标风格一致性
+- Heroicons图标库集成
+
+### 3. 用户体验优化
+- 添加"银龄智库专家"身份提示
+- 突出专业性和权威性
+- 简化填写流程
+
+### 4. 响应式适配
+- 支持320px以上屏幕
+- 触摸目标≥44×44px
+- 字体大小适配系统设置
+
+## 部署检查清单
+
+- [ ] 标签文案修改为"发布智库"
+- [ ] 图标更换为合适图标
+- [ ] 表单组件创建完成
+- [ ] 字段验证规则正确
+- [ ] 水墨风格UI应用正确
+- [ ] 移动端测试通过
+- [ ] 数据写入验证成功
+- [ ] 代码审查完成
+
+## 后续扩展
+
+1. **认证功能**: 支持专家认证申请
+2. **标签系统**: 支持专业领域标签选择
+3. **推荐算法**: 基于专业领域推荐匹配
+4. **统计分析**: 智库专家数据统计展示

+ 172 - 0
src/client/mobile/components/PublishKnowledgeForm.tsx

@@ -0,0 +1,172 @@
+import React, { useState, useEffect } from 'react';
+import { Form, Input, Select, Button, message } from 'antd';
+
+const { TextArea } = Input;
+const { Option } = Select;
+
+const KNOWLEDGE_TYPES = [
+  { value: 1, label: '文章' },
+  { value: 2, label: '视频' },
+  { value: 3, label: '文档' },
+  { value: 4, label: '课程' },
+  { value: 5, label: '经验分享' },
+  { value: 6, label: '案例分享' },
+  { value: 7, label: '研究报告' }
+];
+
+interface PublishKnowledgeFormProps {
+  onSuccess: () => void;
+  onCancel: () => void;
+}
+
+const PublishKnowledgeForm: React.FC<PublishKnowledgeFormProps> = ({ onSuccess, onCancel }) => {
+  const [form] = Form.useForm();
+  const [loading, setLoading] = useState(false);
+  const [categories, setCategories] = useState<Array<{id: number; name: string}>>([]);
+
+  useEffect(() => {
+    const fetchCategories = async () => {
+      try {
+        const response = await fetch('/api/v1/silver-users/knowledge-categories');
+        const data = await response.json();
+        setCategories(data.data || []);
+      } catch (error) {
+        console.error('获取分类失败:', error);
+      }
+    };
+    fetchCategories();
+  }, []);
+
+  const handleSubmit = async (values: any) => {
+    setLoading(true);
+    try {
+      // 获取当前用户信息
+      const userResponse = await fetch('/api/v1/auth/me');
+      const userData = await userResponse.json();
+      
+      if (!userData.data?.id) {
+        message.error('请先登录');
+        return;
+      }
+
+      const formData = {
+        userId: userData.data.id,
+        title: values.title,
+        content: values.content,
+        type: values.type,
+        categoryId: values.categoryId || null,
+        summary: values.summary || null,
+        keywords: values.keywords || null,
+        source: values.source || null,
+        author: values.author || null,
+        tags: values.tags ? JSON.stringify(values.tags.split(',').map((tag: string) => tag.trim())) : null,
+      };
+
+      const response = await fetch('/api/v1/silver-users/knowledges', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify(formData),
+      });
+
+      const result = await response.json();
+      
+      if (response.ok) {
+        message.success('智库发布成功!');
+        form.resetFields();
+        onSuccess();
+      } else {
+        message.error(result.message || '发布失败');
+      }
+    } catch (error) {
+      message.error('发布失败,请重试');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <Form
+      form={form}
+      layout="vertical"
+      onFinish={handleSubmit}
+    >
+      <Form.Item
+        label="知识标题"
+        name="title"
+        rules={[{ required: true, message: '请输入知识标题' }]}
+      >
+        <Input placeholder="请输入知识标题" />
+      </Form.Item>
+
+      <Form.Item
+        label="知识类型"
+        name="type"
+        rules={[{ required: true, message: '请选择知识类型' }]}
+      >
+        <Select placeholder="请选择知识类型">
+          {KNOWLEDGE_TYPES.map(type => (
+            <Option key={type.value} value={type.value}>
+              {type.label}
+            </Option>
+          ))}
+        </Select>
+      </Form.Item>
+
+      <Form.Item label="知识分类" name="categoryId">
+        <Select placeholder="请选择知识分类" allowClear>
+          {categories.map(category => (
+            <Option key={category.id} value={category.id}>
+              {category.name}
+            </Option>
+          ))}
+        </Select>
+      </Form.Item>
+
+      <Form.Item
+        label="知识内容"
+        name="content"
+        rules={[{ required: true, message: '请输入知识内容' }]}
+      >
+        <TextArea rows={6} placeholder="请详细描述您的知识内容..." />
+      </Form.Item>
+
+      <Form.Item label="知识摘要" name="summary">
+        <TextArea rows={3} placeholder="请输入知识摘要(可选)" maxLength={200} />
+      </Form.Item>
+
+      <Form.Item label="关键词" name="keywords">
+        <Input placeholder="健康,养生,老年人" />
+      </Form.Item>
+
+      <Form.Item label="标签" name="tags">
+        <Input placeholder="用逗号分隔多个标签,如:健康养生,老年人" />
+      </Form.Item>
+
+      <Form.Item label="知识来源" name="source">
+        <Input placeholder="如:中国老年学会" />
+      </Form.Item>
+
+      <Form.Item label="原作者" name="author">
+        <Input placeholder="请输入原作者" />
+      </Form.Item>
+
+      <div style={{ display: 'flex', gap: '16px', marginTop: '24px' }}>
+        <Button onClick={onCancel} style={{ flex: 1 }}>
+          取消
+        </Button>
+        <Button 
+          type="primary" 
+          htmlType="submit" 
+          loading={loading}
+          style={{ flex: 1 }}
+        >
+          发布智库
+        </Button>
+      </div>
+    </Form>
+  );
+};
+
+export default PublishKnowledgeForm;

+ 1 - 1
src/client/mobile/components/PublishTalentForm.tsx

@@ -550,7 +550,7 @@ export default function PublishTalentForm({ onSuccess, onCancel, initialData }:
               disabled={formState.submitting || formState.loading}
               className="flex-1 bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
             >
-              {formState.submitting ? '提交中...' : '发布人才信息'}
+              {formState.submitting ? '提交中...' : '发布'}
             </button>
             <button
               type="button"

+ 12 - 14
src/client/mobile/pages/PublishPage.tsx

@@ -1,7 +1,8 @@
 import React, { useState } from 'react';
 import { PublishJobForm } from '@/client/mobile/components/PublishJobForm';
 import PublishTalentForm from '@/client/mobile/components/PublishTalentForm';
-import { UserIcon, BriefcaseIcon, WrenchScrewdriverIcon } from '@heroicons/react/24/outline';
+import PublishKnowledgeForm from '@/client/mobile/components/PublishKnowledgeForm';
+import { UserIcon, BriefcaseIcon, BookOpenIcon } from '@heroicons/react/24/outline';
 import { message } from 'antd';
 
 const INK_COLORS = {
@@ -32,14 +33,13 @@ const FONT_STYLES = {
 };
 
 const PublishPage: React.FC = () => {
-  const [activeTab, setActiveTab] = useState<'job' | 'talent' | 'service'>('job');
+  const [activeTab, setActiveTab] = useState<'job' | 'talent' | 'knowledge'>('job');
 
   const handleFormSuccess = () => {
     message.success('发布成功!');
   };
 
   const handleFormCancel = () => {
-    // 可以添加返回上一页的逻辑
     window.history.back();
   };
 
@@ -59,10 +59,10 @@ const PublishPage: React.FC = () => {
       color: INK_COLORS.accent.green
     },
     {
-      id: 'service',
-      name: '发布服务',
-      icon: WrenchScrewdriverIcon,
-      description: '发布专业服务信息',
+      id: 'knowledge',
+      name: '发布智库',
+      icon: BookOpenIcon,
+      description: '分享您的知识和经验',
       color: INK_COLORS.accent.red
     }
   ];
@@ -129,13 +129,11 @@ const PublishPage: React.FC = () => {
             />
           )}
           
-          {activeTab === 'service' && (
-            <div className="text-center py-12">
-              <WrenchScrewdriverIcon className="w-16 h-16 mx-auto mb-4" style={{ color: INK_COLORS.text.light }} />
-              <p className={FONT_STYLES.body} style={{ color: INK_COLORS.text.secondary }}>
-                服务发布功能开发中...
-              </p>
-            </div>
+          {activeTab === 'knowledge' && (
+            <PublishKnowledgeForm
+              onSuccess={handleFormSuccess}
+              onCancel={handleFormCancel}
+            />
           )}
         </div>