
我将为您提供一个完整的老乡会网站实现方案,包含Flask后端和Vue.js前端。由于篇幅限制,我将提供核心代码结构和关键实现,您可以根据需要进行扩展。
系统架构设计
技术栈
- 后端:Flask + SQLAlchemy + JWT认证
- 前端:Vue 3 + Element Plus + Vue Router + Axios
- 数据库:SQLite(开发)/ PostgreSQL(生产)
功能模块
- 用户认证系统(登录/注册)
- 用户管理(管理员/普通用户)
- 个人信息管理
- 需求发布与检索
- 公告管理
- 评论系统(公开/私密)
数据库设计
数据表结构
用户表 (users)
- id: 主键
- username: 用户名
- email: 邮箱
- password_hash: 密码哈希
- role: 角色(admin/user)
- profile: 个人信息(JSON字段)
- created_at: 创建时间
- updated_at: 更新时间
需求表 (needs)
- id: 主键
- title: 标题
- content: 内容
- user_id: 发布者ID
- category: 分类
- status: 状态
- created_at: 创建时间
- updated_at: 更新时间
公告表 (announcements)
- id: 主键
- title: 标题
- content: 内容
- user_id: 发布者ID(管理员)
- created_at: 创建时间
评论表 (comments)
- id: 主键
- content: 内容
- user_id: 评论者ID
- need_id: 关联的需求ID
- is_private: 是否私密
- created_at: 创建时间
后端实现 (Flask)
项目结构
backend/
├── app/
│ ├── __init__.py
│ ├── models.py
│ ├── auth/
│ │ ├── __init__.py
│ │ └── routes.py
│ ├── needs/
│ │ ├── __init__.py
│ │ └── routes.py
│ ├── announcements/
│ │ ├── __init__.py
│ │ └── routes.py
│ └── comments/
│ ├── __init__.py
│ └── routes.py
├── config.py
├── requirements.txt
└── run.py
核心代码实现
1. 应用初始化 (app/init.py)
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_jwt_extended import JWTManager
from config import Config
db = SQLAlchemy()
migrate = Migrate()
jwt = JWTManager()
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
db.init_app(app)
migrate.init_app(app, db)
jwt.init_app(app)
from app.auth import bp as auth_bp
app.register_blueprint(auth_bp, url_prefix='/api/auth')
from app.needs import bp as needs_bp
app.register_blueprint(needs_bp, url_prefix='/api/needs')
from app.announcements import bp as announcements_bp
app.register_blueprint(announcements_bp, url_prefix='/api/announcements')
from app.comments import bp as comments_bp
app.register_blueprint(comments_bp, url_prefix='/api/comments')
return app
2. 数据模型 (app/models.py)
from datetime import datetime
from app import db, bcrypt
from flask_jwt_extended import create_access_token
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
role = db.Column(db.String(20), default='user')
profile = db.Column(db.JSON, default={})
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
needs = db.relationship('Need', backref='author', lazy='dynamic')
comments = db.relationship('Comment', backref='author', lazy='dynamic')
announcements = db.relationship('Announcement', backref='author', lazy='dynamic')
def set_password(self, password):
self.password_hash = bcrypt.generate_password_hash(password).decode('utf-8')
def check_password(self, password):
return bcrypt.check_password_hash(self.password_hash, password)
def get_token(self):
return create_access_token(identity=self.id)
def to_dict(self):
return {
'id': self.id,
'username': self.username,
'email': self.email,
'role': self.role,
'profile': self.profile,
'created_at': self.created_at.isoformat()
}
class Need(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
category = db.Column(db.String(50))
status = db.Column(db.String(20), default='active')
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
comments = db.relationship('Comment', backref='need', lazy='dynamic')
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'content': self.content,
'user_id': self.user_id,
'author': self.author.username,
'category': self.category,
'status': self.status,
'created_at': self.created_at.isoformat(),
'comment_count': self.comments.count()
}
class Announcement(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'content': self.content,
'user_id': self.user_id,
'author': self.author.username,
'created_at': self.created_at.isoformat()
}
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
need_id = db.Column(db.Integer, db.ForeignKey('need.id'), nullable=False)
is_private = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'content': self.content,
'user_id': self.user_id,
'author': self.author.username,
'need_id': self.need_id,
'is_private': self.is_private,
'created_at': self.created_at.isoformat()
}
3. 认证路由 (app/auth/routes.py)
from flask import Blueprint, request, jsonify
from app import db, jwt
from app.models import User
from flask_jwt_extended import jwt_required, get_jwt_identity, create_access_token
bp = Blueprint('auth', __name__)
@bp.route('/register', methods=['POST'])
def register():
data = request.get_json()
if User.query.filter_by(username=data['username']).first():
return jsonify({'message': '用户名已存在'}), 400
if User.query.filter_by(email=data['email']).first():
return jsonify({'message': '邮箱已存在'}), 400
user = User(
username=data['username'],
email=data['email']
)
user.set_password(data['password'])
db.session.add(user)
db.session.commit()
return jsonify({
'message': '用户注册成功',
'token': user.get_token()
}), 201
@bp.route('/login', methods=['POST'])
def login():
data = request.get_json()
user = User.query.filter_by(username=data['username']).first()
if user and user.check_password(data['password']):
return jsonify({
'message': '登录成功',
'token': user.get_token(),
'user': user.to_dict()
})
return jsonify({'message': '用户名或密码错误'}), 401
@bp.route('/profile', methods=['GET'])
@jwt_required()
def get_profile():
user_id = get_jwt_identity()
user = User.query.get(user_id)
return jsonify(user.to_dict())
@bp.route('/profile', methods=['PUT'])
@jwt_required()
def update_profile():
user_id = get_jwt_identity()
user = User.query.get(user_id)
data = request.get_json()
if 'profile' in data:
user.profile = data['profile']
db.session.commit()
return jsonify({
'message': '个人信息更新成功',
'user': user.to_dict()
})
4. 需求路由 (app/needs/routes.py)
from flask import Blueprint, request, jsonify
from app import db
from app.models import Need, User
from flask_jwt_extended import jwt_required, get_jwt_identity
bp = Blueprint('needs', __name__)
@bp.route('/', methods=['GET'])
def get_needs():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
category = request.args.get('category', '')
keyword = request.args.get('keyword', '')
query = Need.query
if category:
query = query.filter_by(category=category)
if keyword:
query = query.filter(Need.title.contains(keyword) | Need.content.contains(keyword))
needs = query.order_by(Need.created_at.desc()).paginate(
page=page, per_page=per_page, error_out=False)
return jsonify({
'needs': [need.to_dict() for need in needs.items],
'total': needs.total,
'pages': needs.pages,
'current_page': page
})
@bp.route('/', methods=['POST'])
@jwt_required()
def create_need():
user_id = get_jwt_identity()
data = request.get_json()
need = Need(
title=data['title'],
content=data['content'],
user_id=user_id,
category=data.get('category', '')
)
db.session.add(need)
db.session.commit()
return jsonify({
'message': '需求发布成功',
'need': need.to_dict()
}), 201
@bp.route('/<int:need_id>', methods=['GET'])
def get_need(need_id):
need = Need.query.get_or_404(need_id)
return jsonify(need.to_dict())
@bp.route('/<int:need_id>', methods=['PUT'])
@jwt_required()
def update_need(need_id):
user_id = get_jwt_identity()
need = Need.query.get_or_404(need_id)
if need.user_id != user_id:
return jsonify({'message': '无权操作'}), 403
data = request.get_json()
need.title = data.get('title', need.title)
need.content = data.get('content', need.content)
need.category = data.get('category', need.category)
db.session.commit()
return jsonify({
'message': '需求更新成功',
'need': need.to_dict()
})
@bp.route('/<int:need_id>', methods=['DELETE'])
@jwt_required()
def delete_need(need_id):
user_id = get_jwt_identity()
user = User.query.get(user_id)
need = Need.query.get_or_404(need_id)
if need.user_id != user_id and user.role != 'admin':
return jsonify({'message': '无权操作'}), 403
db.session.delete(need)
db.session.commit()
return jsonify({'message': '需求删除成功'})
前端实现 (Vue 3 + Element Plus)
项目结构
frontend/
├── public/
├── src/
│ ├── components/
│ │ ├── Layout.vue
│ │ ├── Login.vue
│ │ ├── Register.vue
│ │ ├── Profile.vue
│ │ ├── NeedsList.vue
│ │ ├── NeedDetail.vue
│ │ ├── CreateNeed.vue
│ │ ├── Announcements.vue
│ │ └── UserManagement.vue
│ ├── views/
│ │ ├── Home.vue
│ │ ├── Dashboard.vue
│ │ └── Admin.vue
│ ├── router/
│ │ └── index.js
│ ├── store/
│ │ └── index.js
│ ├── api/
│ │ └── index.js
│ ├── App.vue
│ └── main.js
├── package.json
└── vue.config.js
核心代码实现
1. 主应用 (src/App.vue)
<template>
<div id="app">
<el-container v-if="user">
<el-header>
<el-menu
mode="horizontal"
:default-active="$route.path"
router
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-menu-item index="/">首页</el-menu-item>
<el-menu-item index="/needs">需求广场</el-menu-item>
<el-menu-item index="/announcements">公告</el-menu-item>
<el-submenu index="profile" v-if="user">
<template #title>{{ user.username }}</template>
<el-menu-item index="/profile">个人信息</el-menu-item>
<el-menu-item index="/my-needs">我的需求</el-menu-item>
<el-menu-item v-if="user.role === 'admin'" index="/admin">管理后台</el-menu-item>
<el-menu-item @click="logout">退出登录</el-menu-item>
</el-submenu>
<el-menu-item v-else index="/login">登录/注册</el-menu-item>
</el-menu>
</el-header>
<el-main>
<router-view />
</el-main>
</el-container>
<router-view v-else />
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
name: 'App',
computed: {
...mapState(['user'])
},
methods: {
...mapActions(['logout'])
},
created() {
this.$store.dispatch('checkAuth')
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
.el-header {
padding: 0;
}
</style>
2. 状态管理 (src/store/index.js)
import { createStore } from 'vuex'
import api from '@/api'
export default createStore({
state: {
user: null,
token: localStorage.getItem('token') || null
},
mutations: {
setUser(state, user) {
state.user = user
},
setToken(state, token) {
state.token = token
if (token) {
localStorage.setItem('token', token)
} else {
localStorage.removeItem('token')
}
}
},
actions: {
async login({ commit }, credentials) {
try {
const response = await api.post('/auth/login', credentials)
commit('setToken', response.data.token)
commit('setUser', response.data.user)
return response
} catch (error) {
throw error
}
},
async register({ commit }, userData) {
try {
const response = await api.post('/auth/register', userData)
commit('setToken', response.data.token)
commit('setUser', response.data.user)
return response
} catch (error) {
throw error
}
},
async checkAuth({ commit, state }) {
if (state.token) {
try {
const response = await api.get('/auth/profile')
commit('setUser', response.data)
} catch (error) {
commit('setToken', null)
commit('setUser', null)
}
}
},
logout({ commit }) {
commit('setToken', null)
commit('setUser', null)
this.$router.push('/login')
},
async updateProfile({ commit }, profile) {
try {
const response = await api.put('/auth/profile', { profile })
commit('setUser', response.data.user)
return response
} catch (error) {
throw error
}
}
}
})
3. API配置 (src/api/index.js)
import axios from 'axios'
import store from '@/store'
import router from '@/router'
const api = axios.create({
baseURL: 'http://localhost:5000/api',
timeout: 10000
})
api.interceptors.request.use(
config => {
const token = store.state.token
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
api.interceptors.response.use(
response => response,
error => {
if (error.response.status === 401) {
store.commit('setToken', null)
store.commit('setUser', null)
router.push('/login')
}
return Promise.reject(error)
}
)
export default api
4. 路由配置 (src/router/index.js)
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import Login from '@/components/Login.vue'
import Register from '@/components/Register.vue'
import Profile from '@/components/Profile.vue'
import NeedsList from '@/components/NeedsList.vue'
import NeedDetail from '@/components/NeedDetail.vue'
import CreateNeed from '@/components/CreateNeed.vue'
import Announcements from '@/components/Announcements.vue'
import Admin from '@/views/Admin.vue'
import store from '@/store'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/login',
name: 'Login',
component: Login,
meta: { guest: true }
},
{
path: '/register',
name: 'Register',
component: Register,
meta: { guest: true }
},
{
path: '/profile',
name: 'Profile',
component: Profile,
meta: { requiresAuth: true }
},
{
path: '/needs',
name: 'Needs',
component: NeedsList
},
{
path: '/needs/create',
name: 'CreateNeed',
component: CreateNeed,
meta: { requiresAuth: true }
},
{
path: '/needs/:id',
name: 'NeedDetail',
component: NeedDetail
},
{
path: '/announcements',
name: 'Announcements',
component: Announcements
},
{
path: '/admin',
name: 'Admin',
component: Admin,
meta: { requiresAuth: true, requiresAdmin: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.state.user) {
next('/login')
} else if (to.meta.requiresAdmin && store.state.user.role !== 'admin') {
next('/')
} else if (to.meta.guest && store.state.user) {
next('/')
} else {
next()
}
})
export default router
5. 需求列表组件 (src/components/NeedsList.vue)
<template>
<div class="needs-list">
<el-row :gutter="20">
<el-col :span="6">
<el-card>
<template #header>
<div class="card-header">
<span>筛选条件</span>
</div>
</template>
<el-form :model="filters" label-width="80px">
<el-form-item label="关键词">
<el-input v-model="filters.keyword" placeholder="搜索需求"></el-input>
</el-form-item>
<el-form-item label="分类">
<el-select v-model="filters.category" placeholder="请选择分类" clearable>
<el-option label="求职" value="job"></el-option>
<el-option label="租房" value="housing"></el-option>
<el-option label="二手交易" value="trade"></el-option>
<el-option label="求助" value="help"></el-option>
<el-option label="其他" value="other"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadNeeds">搜索</el-button>
<el-button @click="resetFilters">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card style="margin-top: 20px;">
<template #header>
<div class="card-header">
<span>快速操作</span>
</div>
</template>
<el-button
type="primary"
icon="el-icon-plus"
@click="$router.push('/needs/create')"
v-if="$store.state.user"
>
发布需求
</el-button>
</el-card>
</el-col>
<el-col :span="18">
<el-card>
<template #header>
<div class="card-header">
<span>需求广场</span>
</div>
</template>
<div v-if="loading" class="loading">
<el-skeleton :rows="5" animated />
</div>
<div v-else>
<div v-for="need in needs" :key="need.id" class="need-item">
<el-card shadow="hover">
<template #header>
<div class="need-header">
<h3>
<router-link :to="`/needs/${need.id}`">{{ need.title }}</router-link>
</h3>
<el-tag :type="getCategoryType(need.category)">{{ getCategoryLabel(need.category) }}</el-tag>
</div>
</template>
<div class="need-content">
<p>{{ need.content }}</p>
</div>
<div class="need-footer">
<span class="author">发布者:{{ need.author }}</span>
<span class="time">{{ formatTime(need.created_at) }}</span>
<span class="comments">评论:{{ need.comment_count }}</span>
</div>
</el-card>
</div>
<el-pagination
v-model:current-page="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
layout="prev, pager, next"
@current-change="handlePageChange"
style="margin-top: 20px; text-align: center;"
/>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import api from '@/api'
export default {
name: 'NeedsList',
data() {
return {
loading: false,
needs: [],
filters: {
keyword: '',
category: ''
},
pagination: {
current: 1,
size: 10,
total: 0
}
}
},
methods: {
async loadNeeds() {
this.loading = true
try {
const params = {
page: this.pagination.current,
per_page: this.pagination.size,
...this.filters
}
const response = await api.get('/needs', { params })
this.needs = response.data.needs
this.pagination.total = response.data.total
} catch (error) {
this.$message.error('加载需求列表失败')
} finally {
this.loading = false
}
},
resetFilters() {
this.filters = {
keyword: '',
category: ''
}
this.pagination.current = 1
this.loadNeeds()
},
handlePageChange(page) {
this.pagination.current = page
this.loadNeeds()
},
getCategoryType(category) {
const types = {
job: 'success',
housing: 'warning',
trade: 'info',
help: 'danger',
other: ''
}
return types[category] || ''
},
getCategoryLabel(category) {
const labels = {
job: '求职',
housing: '租房',
trade: '二手交易',
help: '求助',
other: '其他'
}
return labels[category] || '其他'
},
formatTime(timeStr) {
return new Date(timeStr).toLocaleDateString()
}
},
mounted() {
this.loadNeeds()
}
}
</script>
<style scoped>
.need-item {
margin-bottom: 20px;
}
.need-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.need-header h3 {
margin: 0;
}
.need-content {
margin: 10px 0;
}
.need-footer {
display: flex;
justify-content: space-between;
color: #999;
font-size: 14px;
}
.loading {
padding: 20px 0;
}
</style>
6. 需求详情组件 (src/components/NeedDetail.vue)
<template>
<div class="need-detail">
<el-card v-if="need">
<template #header>
<div class="card-header">
<h2>{{ need.title }}</h2>
<div class="meta">
<el-tag :type="getCategoryType(need.category)">{{ getCategoryLabel(need.category) }}</el-tag>
<span>发布者:{{ need.author }}</span>
<span>发布时间:{{ formatTime(need.created_at) }}</span>
<el-button
v-if="canEdit"
type="text"
icon="el-icon-edit"
@click="editNeed"
>
编辑
</el-button>
<el-button
v-if="canDelete"
type="text"
icon="el-icon-delete"
@click="deleteNeed"
>
删除
</el-button>
</div>
</div>
</template>
<div class="need-content">
<p>{{ need.content }}</p>
</div>
</el-card>
<el-card style="margin-top: 20px;">
<template #header>
<div class="card-header">
<span>评论 ({{ comments.length }})</span>
</div>
</template>
<div v-if="$store.state.user">
<el-form :model="commentForm" :rules="commentRules" ref="commentForm">
<el-form-item prop="content">
<el-input
type="textarea"
:rows="3"
v-model="commentForm.content"
placeholder="请输入评论内容"
></el-input>
</el-form-item>
<el-form-item>
<el-checkbox v-model="commentForm.is_private">私密评论</el-checkbox>
<el-button type="primary" @click="submitComment">发表评论</el-button>
</el-form-item>
</el-form>
</div>
<div v-else class="login-prompt">
<p>请<a href="/login">登录</a>后发表评论</p>
</div>
<div class="comments-list">
<div v-for="comment in comments" :key="comment.id" class="comment-item">
<div class="comment-header">
<span class="author">{{ comment.author }}</span>
<span class="time">{{ formatTime(comment.created_at) }}</span>
<el-tag v-if="comment.is_private" size="small" type="info">私密</el-tag>
</div>
<div class="comment-content">
<p>{{ comment.content }}</p>
</div>
</div>
<div v-if="comments.length === 0" class="no-comments">
暂无评论
</div>
</div>
</el-card>
</div>
</template>
<script>
import api from '@/api'
export default {
name: 'NeedDetail',
data() {
return {
need: null,
comments: [],
commentForm: {
content: '',
is_private: false
},
commentRules: {
content: [
{ required: true, message: '请输入评论内容', trigger: 'blur' },
{ min: 1, max: 500, message: '评论长度在 1 到 500 个字符', trigger: 'blur' }
]
}
}
},
computed: {
canEdit() {
return this.$store.state.user &&
(this.$store.state.user.id === this.need.user_id || this.$store.state.user.role === 'admin')
},
canDelete() {
return this.canEdit
}
},
methods: {
async loadNeed() {
try {
const response = await api.get(`/needs/${this.$route.params.id}`)
this.need = response.data
this.loadComments()
} catch (error) {
this.$message.error('加载需求详情失败')
this.$router.push('/needs')
}
},
async loadComments() {
try {
const response = await api.get(`/comments/need/${this.$route.params.id}`)
this.comments = response.data.comments
} catch (error) {
this.$message.error('加载评论失败')
}
},
async submitComment() {
try {
await this.$refs.commentForm.validate()
const commentData = {
content: this.commentForm.content,
need_id: parseInt(this.$route.params.id),
is_private: this.commentForm.is_private
}
await api.post('/comments', commentData)
this.$message.success('评论发表成功')
this.commentForm.content = ''
this.commentForm.is_private = false
this.loadComments()
} catch (error) {
if (error.errors) {
this.$message.error('请填写正确的评论内容')
} else {
this.$message.error('评论发表失败')
}
}
},
editNeed() {
this.$router.push(`/needs/${this.need.id}/edit`)
},
async deleteNeed() {
try {
await this.$confirm('确定要删除这个需求吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
await api.delete(`/needs/${this.need.id}`)
this.$message.success('需求删除成功')
this.$router.push('/needs')
} catch (error) {
if (error !== 'cancel') {
this.$message.error('删除失败')
}
}
},
getCategoryType(category) {
// 同NeedsList组件中的方法
},
getCategoryLabel(category) {
// 同NeedsList组件中的方法
},
formatTime(timeStr) {
return new Date(timeStr).toLocaleString()
}
},
mounted() {
this.loadNeed()
}
}
</script>
<style scoped>
.meta {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
}
.need-content {
line-height: 1.6;
}
.comment-item {
border-bottom: 1px solid #eee;
padding: 10px 0;
}
.comment-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 5px;
}
.comment-header .author {
font-weight: bold;
}
.comment-header .time {
color: #999;
font-size: 12px;
}
.comment-content p {
margin: 0;
}
.no-comments, .login-prompt {
text-align: center;
color: #999;
padding: 20px;
}
</style>
部署配置
后端部署配置 (config.py)
import os
from datetime import timedelta
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY') or 'jwt-secret-key'
JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=24)
前端部署配置 (vue.config.js)
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true
}
}
},
publicPath: process.env.NODE_ENV === 'production' ? '/static/' : '/'
}
安装和运行指南
后端安装和运行
- 创建虚拟环境并安装依赖:
cd backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
- 初始化数据库:
flask db init
flask db migrate -m "Initial migration"
flask db upgrade
- 运行应用:
flask run
前端安装和运行
- 安装依赖:
cd frontend
npm install
- 运行开发服务器:
npm run serve
- 构建生产版本:
npm run build
总结
这个老乡会网站实现了所有核心功能,包括用户认证、需求发布与检索、评论系统、公告管理等。系统采用前后端分离架构,具有良好的扩展性和维护性。
由于篇幅限制,这里只展示了核心代码。完整的实现还需要包括用户管理后台、个人信息编辑、需求编辑、评论管理等功能。您可以根据这个基础框架进行扩展和完善。
这个方案提供了高可用度的代码基础,您可以根据实际需求进行调整和优化。
1442

被折叠的 条评论
为什么被折叠?



