在数字化时代,自由职业者与企业需求之间的桥梁变得越来越重要。悬赏任务APP和威客平台正是连接两者的理想解决方案,让雇主能够发布任务并悬赏,而自由职业者则可以接受任务并获得报酬。本文将介绍一个支持iOS和Android双端的悬赏任务APP与威客平台的核心实现方案,采用React Native作为跨平台框架,确保一套代码能够在两个系统上运行。
系统架构
源码及演示:casgams.top/xs
前端:React Native+Redux
后端:Node.js+Express
数据库:MongoDB
实时通讯:Socket.io
核心功能模块
用户认证系统(注册、登录、身份验证)
任务发布与管理
任务投标与接单
消息通知系统
支付与交易系统
评价与信誉体系
核心代码实现
下面是平台的核心代码实现,包含主要功能模块:
taskActions.js
import axios from 'axios';
import {
FETCH_TASKS_REQUEST,
FETCH_TASKS_SUCCESS,
FETCH_TASKS_FAILURE,
FETCH_TASK_DETAIL_REQUEST,
FETCH_TASK_DETAIL_SUCCESS,
FETCH_TASK_DETAIL_FAILURE,
CREATE_TASK_REQUEST,
CREATE_TASK_SUCCESS,
CREATE_TASK_FAILURE,
SUBMIT_BID_REQUEST,
SUBMIT_BID_SUCCESS,
SUBMIT_BID_FAILURE,
ACCEPT_BID_REQUEST,
ACCEPT_BID_SUCCESS,
ACCEPT_BID_FAILURE,
COMPLETE_TASK_REQUEST,
COMPLETE_TASK_SUCCESS,
COMPLETE_TASK_FAILURE
} from './types';
const API_URL = 'https://api.yourplatform.com';
// 获取任务列表
export const fetchTasks = () => {
return async (dispatch) => {
try {
dispatch({ type: FETCH_TASKS_REQUEST });
const response = await axios.get(`${API_URL}/tasks`);
dispatch({
type: FETCH_TASKS_SUCCESS,
payload: response.data
});
} catch (error) {
dispatch({
type: FETCH_TASKS_FAILURE,
payload: error.response?.data?.message || '获取任务列表失败'
});
}
};
};
// 获取任务详情
export const fetchTaskDetail = (taskId) => {
return async (dispatch) => {
try {
dispatch({ type: FETCH_TASK_DETAIL_REQUEST });
const response = await axios.get(`${API_URL}/tasks/${taskId}`);
dispatch({
type: FETCH_TASK_DETAIL_SUCCESS,
payload: response.data
});
} catch (error) {
dispatch({
type: FETCH_TASK_DETAIL_FAILURE,
payload: error.response?.data?.message || '获取任务详情失败'
});
}
};
};
// 创建任务
export const createTask = (taskData, navigation) => {
return async (dispatch, getState) => {
try {
dispatch({ type: CREATE_TASK_REQUEST });
const { auth } = getState();
const response = await axios.post(
`${API_URL}/tasks`,
taskData,
{
headers: {
Authorization: `Bearer ${auth.token}`
}
}
);
dispatch({
type: CREATE_TASK_SUCCESS,
payload: response.data
});
// 创建成功后返回任务列表
navigation.navigate('Home');
} catch (error) {
dispatch({
type: CREATE_TASK_FAILURE,
payload: error.response?.data?.message || '创建任务失败'
});
}
};
};
// 提交投标
export const submitBid = (taskId, bidData) => {
return async (dispatch, getState) => {
try {
dispatch({ type: SUBMIT_BID_REQUEST });
const { auth } = getState();
const response = await axios.post(
`${API_URL}/tasks/${taskId}/bids`,
bidData,
{
headers: {
Authorization: `Bearer ${auth.token}`
}
}
);
dispatch({
type: SUBMIT_BID_SUCCESS,
payload: response.data
});
// 刷新任务详情
dispatch(fetchTaskDetail(taskId));
} catch (error) {
dispatch({
type: SUBMIT_BID_FAILURE,
payload: error.response?.data?.message || '提交投标失败'
});
}
};
};
// 接受投标
export const acceptBid = (taskId, bidId) => {
return async (dispatch, getState) => {
try {
dispatch({ type: ACCEPT_BID_REQUEST });
const { auth } = getState();
const response = await axios.put(
`${API_URL}/tasks/${taskId}/bids/${bidId}/accept`,
{},
{
headers: {
Authorization: `Bearer ${auth.token}`
}
}
);
dispatch({
type: ACCEPT_BID_SUCCESS,
payload: response.data
});
// 刷新任务详情
dispatch(fetchTaskDetail(taskId));
} catch (error) {
dispatch({
type: ACCEPT_BID_FAILURE,
payload: error.response?.data?.message || '接受投标失败'
});
}
};
};
// 完成任务
export const completeTask = (taskId) => {
return async (dispatch, getState) => {
try {
dispatch({ type: COMPLETE_TASK_REQUEST });
const { auth } = getState();
const response = await axios.put(
`${API_URL}/tasks/${taskId}/complete`,
{},
{
headers: {
Authorization: `Bearer ${auth.token}`
}
}
);
dispatch({
type: COMPLETE_TASK_SUCCESS,
payload: response.data
});
// 刷新任务详情
dispatch(fetchTaskDetail(taskId));
} catch (error) {
dispatch({
type: COMPLETE_TASK_FAILURE,
payload: error.response?.data?.message || '完成任务失败'
});
}
};
};
HomeScreen.js
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, TouchableOpacity, TextInput, StyleSheet, ActivityIndicator } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { fetchTasks } from '../redux/actions/taskActions';
import TaskCard from '../components/TaskCard';
import { Icon } from 'react-native-elements';
const HomeScreen = ({ navigation }) => {
const dispatch = useDispatch();
const { tasks, loading, error } = useSelector(state => state.tasks);
const [searchTerm, setSearchTerm] = useState('');
const [filteredTasks, setFilteredTasks] = useState([]);
useEffect(() => {
dispatch(fetchTasks());
}, [dispatch]);
useEffect(() => {
if (tasks.length > 0) {
const filtered = tasks.filter(task =>
task.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
task.description.toLowerCase().includes(searchTerm.toLowerCase())
);
setFilteredTasks(filtered);
}
}, [tasks, searchTerm]);
const handleTaskPress = (taskId) => {
navigation.navigate('TaskDetail', { taskId });
};
const handleCreateTask = () => {
navigation.navigate('CreateTask');
};
if (loading) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#0066cc" />
</View>
);
}
if (error) {
return (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>{error}</Text>
<TouchableOpacity
style={styles.retryButton}
onPress={() => dispatch(fetchTasks())}
>
<Text style={styles.retryText}>重试</Text>
</TouchableOpacity>
</View>
);
}
return (
<View style={styles.container}>
<View style={styles.searchContainer}>
<TextInput
style={styles.searchInput}
placeholder="搜索任务..."
value={searchTerm}
onChangeText={setSearchTerm}
/>
<TouchableOpacity
style={styles.createButton}
onPress={handleCreateTask}
>
<Icon name="add" type="material" color="white" />
</TouchableOpacity>
</View>
<FlatList
data={filteredTasks}
keyExtractor={item => item._id}
renderItem={({ item }) => (
<TaskCard
task={item}
onPress={() => handleTaskPress(item._id)}
/>
)}
ListEmptyComponent={
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>没有找到匹配的任务</Text>
</View>
}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
searchContainer: {
flexDirection: 'row',
padding: 10,
backgroundColor: 'white',
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
searchInput: {
flex: 1,
height: 40,
borderColor: '#ddd',
borderWidth: 1,
borderRadius: 20,
paddingHorizontal: 15,
},
createButton: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#0066cc',
justifyContent: 'center',
alignItems: 'center',
marginLeft: 10,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
errorText: {
color: 'red',
fontSize: 16,
marginBottom: 20,
textAlign: 'center',
},
retryButton: {
backgroundColor: '#0066cc',
padding: 10,
borderRadius: 5,
},
retryText: {
color: 'white',
fontSize: 16,
},
emptyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
emptyText: {
color: '#666',
fontSize: 16,
},
});
export default HomeScreen;
taskController.js
const Task = require('../models/Task');
const Bid = require('../models/Bid');
const User = require('../models/User');
const Notification = require('../models/Notification');
const mongoose = require('mongoose');
// 获取所有任务
exports.getTasks = async (req, res) => {
try {
const {
page = 1,
limit = 10,
category,
status = 'open',
minBudget,
maxBudget,
search
} = req.query;
// 构建查询条件
const query = {};
if (status) {
query.status = status;
}
if (category) {
query.category = category;
}
if (minBudget || maxBudget) {
query.budget = {};
if (minBudget) query.budget.$gte = Number(minBudget);
if (maxBudget) query.budget.$lte = Number(maxBudget);
}
if (search) {
query.$text = { $search: search };
}
// 执行查询
const tasks = await Task.find(query)
.populate('employer', 'name avatar rating')
.sort({ createdAt: -1 })
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
// 获取总数
const count = await Task.countDocuments(query);
res.status(200).json({
tasks,
totalPages: Math.ceil(count / limit),
currentPage: page,
totalTasks: count
});
} catch (error) {
console.error('Error fetching tasks:', error);
res.status(500).json({ message: '获取任务失败', error: error.message });
}
};
// 获取单个任务详情
exports.getTaskById = async (req, res) => {
try {
const task = await Task.findById(req.params.id)
.populate('employer', 'name avatar rating reviews')
.populate('freelancer', 'name avatar rating reviews')
.populate({
path: 'bids',
populate: {
path: 'freelancer',
select: 'name avatar rating'
}
})
.populate('acceptedBid');
if (!task) {
return res.status(404).json({ message: '任务不存在' });
}
res.status(200).json(task);
} catch (error) {
console.error('Error fetching task:', error);
res.status(500).json({ message: '获取任务详情失败', error: error.message });
}
};
// 创建任务
exports.createTask = async (req, res) => {
const session = await mongoose.startSession();
session.startTransaction();
try {
const {
title,
description,
category,
budget,
deadline,
attachments
} = req.body;
// 创建新任务
const newTask = new Task({
title,
description,
category,
budget,
deadline,
employer: req.user.id,
attachments: attachments || []
});
const savedTask = await newTask.save({ session });
// 更新雇主的任务计数
await User.findByIdAndUpdate(
req.user.id,
{ $inc: { tasksPosted: 1 } },
{ session }
);
await session.commitTransaction();
session.endSession();
res.status(201).json({
message: '任务创建成功',
task: savedTask
});
} catch (error) {
await session.abortTransaction();
session.endSession();
console.error('Error creating task:', error);
res.status(500).json({ message: '创建任务失败', error: error.message });
}
};
// 提交投标
exports.submitBid = async (req, res) => {
const session = await mongoose.startSession();
session.startTransaction();
try {
const { taskId } = req.params;
const { amount, proposal, deliveryTime } = req.body;
// 检查任务是否存在且状态为开放
const task = await Task.findById(taskId).session(session);
if (!task) {
await session.abortTransaction();
session.endSession();
return res.status(404).json({ message: '任务不存在' });
}
if (task.status !== 'open') {
await session.abortTransaction();
session.endSession();
return res.status(400).json({ message: '该任务不接受投标' });
}
// 检查是否是自己发布的任务
if (task.employer.toString() === req.user.id) {
await session.abortTransaction();
session.endSession();
return res.status(400).json({ message: '不能对自己发布的任务投标' });
}
// 检查是否已经投标
const existingBid = await Bid.findOne({
task: taskId,
freelancer: req.user.id
}).session(session);
if (existingBid) {
await session.abortTransaction();
session.endSession();
return res.status(400).json({ message: '您已经对该任务投过标' });
}
// 创建新投标
const newBid = new Bid({
task: taskId,
freelancer: req.user.id,
amount,
proposal,
deliveryTime
});
const savedBid = await newBid.save({ session });
// 将投标添加到任务中
task.bids.push(savedBid._id);
await task.save({ session });
// 更新 freelancer 的投标计数
await User.findByIdAndUpdate(
req.user.id,
{ $inc: { bidsSubmitted: 1 } },
{ session }
);
// 创建通知
const notification = new Notification({
user: task.employer,
type: 'new_bid',
message: `您的任务"${task.title}"收到了新的投标`,
relatedId: savedBid._id
});
await notification.save({ session });
await session.commitTransaction();
session.endSession();
// 发送实时通知
req.io.to(`user_${task.employer}`).emit('notification', {
type: 'new_bid',
message: `您的任务"${task.title}"收到了新的投标`,
relatedId: savedBid._id,
createdAt: new Date()
});
res.status(201).json({
message: '投标提交成功',
bid: savedBid
});
} catch (error) {
await session.abortTransaction();
session.endSession();
console.error('Error submitting bid:', error);
res.status(500).json({ message: '提交投标失败', error: error.message });
}
};
// 接受投标
exports.acceptBid = async (req, res) => {
const session = await mongoose.startSession();
session.startTransaction();
try {
const { taskId, bidId } = req.params;
// 查找任务和投标
const task = await Task.findById(taskId)
.populate('bids')
.session(session);
if (!task) {
await session.abortTransaction();
session.endSession();
return res.status(404).json({ message: '任务不存在' });
}
// 检查是否是任务发布者
if (task.employer.toString() !== req.user.id) {
await session.abortTransaction();
session.endSession();
return res.status(403).json({ message: '没有权限操作此任务' });
}
// 检查任务状态
if (task.status !== 'open') {
await session.abortTransaction();
session.endSession();
return res.status(400).json({ message: '该任务已不再接受投标' });
}
// 查找投标
const bid = await Bid.findById(bidId).session(session);
if (!bid) {
await session.abortTransaction();
session.endSession();
return res.status(404).json({ message: '投标不存在' });
}
// 检查投标是否属于该任务
if (bid.task.toString() !== taskId) {
await session.abortTransaction();
session.endSession();
return res.status(400).json({ message: '该投标不属于此任务' });
}
// 更新任务状态和接受的投标
task.status = 'in_progress';
task.acceptedBid = bidId;
task.freelancer = bid.freelancer;
await task.save({ session });
// 更新投标状态
bid.status = 'accepted';
await bid.save({ session });
// 更新用户信息
await User.findByIdAndUpdate(
bid.freelancer,
{ $inc: { tasksAccepted: 1 } },
{ session }
);
// 创建通知
const notification = new Notification({
user: bid.freelancer,
type: 'bid_accepted',
message: `您对任务"${task.title}"的投标已被接受`,
relatedId: taskId
});
await notification.save({ session });
await session.commitTransaction();
session.endSession();
// 发送实时通知
req.io.to(`user_${bid.freelancer}`).emit('notification', {
type: 'bid_accepted',
message: `您对任务"${task.title}"的投标已被接受`,
relatedId: taskId,
createdAt: new Date()
});
res.status(200).json({
message: '投标接受成功',
task
});
} catch (error) {
await session.abortTransaction();
session.endSession();
console.error('Error accepting bid:', error);
res.status(500).json({ message: '接受投标失败', error: error.message });
}
};
// 完成任务
exports.completeTask = async (req, res) => {
const session = await mongoose.startSession();
session.startTransaction();
try {
const { taskId } = req.params;
// 查找任务
const task = await Task.findById(taskId)
.populate('employer')
.populate('freelancer')
.session(session);
if (!task) {
await session.abortTransaction();
session.endSession();
return res.status(404).json({ message: '任务不存在' });
}
// 检查是否是任务发布者
if (task.employer._id.toString() !== req.user.id) {
await session.abortTransaction();
session.endSession();
return res.status(403).json({ message: '没有权限完成此任务' });
}
// 检查任务状态
if (task.status !== 'in_progress') {
await session.abortTransaction();
session.endSession();
return res.status(400).json({ message: '该任务不能被标记为完成' });
}
// 更新任务状态
task.status = 'completed';
await task.save({ session });
// 更新用户信息
await User.findByIdAndUpdate(
task.freelancer._id,
{
$inc: {
tasksCompleted: 1,
earnings: task.acceptedBid.amount
}
},
{ session }
);
// 创建通知
const notification = new Notification({
user: task.freelancer._id,
type: 'task_completed',
message: `任务"${task.title}"已被标记为完成`,
relatedId: taskId
});
await notification.save({ session });
await session.commitTransaction();
session.endSession();
// 发送实时通知
req.io.to(`user_${task.freelancer._id}`).emit('notification', {
type: 'task_completed',
message: `任务"${task.title}"已被标记为完成`,
relatedId: taskId,
createdAt: new Date()
});
res.status(200).json({
message: '任务已标记为完成',
task
});
} catch (error) {
await session.abortTransaction();
session.endSession();
console.error('Error completing task:', error);
res.status(500).json({ message: '标记任务完成失败', error: error.message });
}
};
App.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { Provider } from 'react-redux';
import store from './src/store';
import SocketProvider from './src/contexts/SocketContext';
// 页面导入
import SplashScreen from './src/screens/SplashScreen';
import LoginScreen from './src/screens/LoginScreen';
import RegisterScreen from './src/screens/RegisterScreen';
import HomeScreen from './src/screens/HomeScreen';
import TaskDetailScreen from './src/screens/TaskDetailScreen';
import CreateTaskScreen from './src/screens/CreateTaskScreen';
import MyTasksScreen from './src/screens/MyTasksScreen';
import MessagesScreen from './src/screens/MessagesScreen';
import ProfileScreen from './src/screens/ProfileScreen';
import PaymentScreen from './src/screens/PaymentScreen';
const Stack = createStackNavigator();
const App = () => {
return (
<Provider store={store}>
<SocketProvider>
<NavigationContainer>
<Stack.Navigator initialRouteName="Splash">
<Stack.Screen name="Splash" component={SplashScreen} options={{ headerShown: false }} />
<Stack.Screen name="Login" component={LoginScreen} options={{ headerShown: false }} />
<Stack.Screen name="Register" component={RegisterScreen} options={{ headerShown: false }} />
<Stack.Screen name="Home" component={HomeScreen} options={{ title: '任务广场' }} />
<Stack.Screen name="TaskDetail" component={TaskDetailScreen} options={{ title: '任务详情' }} />
<Stack.Screen name="CreateTask" component={CreateTaskScreen} options={{ title: '发布任务' }} />
<Stack.Screen name="MyTasks" component={MyTasksScreen} options={{ title: '我的任务' }} />
<Stack.Screen name="Messages" component={MessagesScreen} options={{ title: '消息中心' }} />
<Stack.Screen name="Profile" component={ProfileScreen} options={{ title: '个人中心' }} />
<Stack.Screen name="Payment" component={PaymentScreen} options={{ title: '支付中心' }} />
</Stack.Navigator>
</NavigationContainer>
</SocketProvider>
</Provider>
);
};
export default App;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。