老乡会网站 - 技术方案与实现

在这里插入图片描述


在这里插入图片描述

我将为您提供一个完整的老乡会网站实现方案,包含Flask后端和Vue.js前端。由于篇幅限制,我将提供核心代码结构和关键实现,您可以根据需要进行扩展。

系统架构设计

技术栈

  • 后端:Flask + SQLAlchemy + JWT认证
  • 前端:Vue 3 + Element Plus + Vue Router + Axios
  • 数据库:SQLite(开发)/ PostgreSQL(生产)

功能模块

  1. 用户认证系统(登录/注册)
  2. 用户管理(管理员/普通用户)
  3. 个人信息管理
  4. 需求发布与检索
  5. 公告管理
  6. 评论系统(公开/私密)

数据库设计

数据表结构

用户表 (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/' : '/'
}

安装和运行指南

后端安装和运行

  1. 创建虚拟环境并安装依赖:
cd backend
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate
pip install -r requirements.txt
  1. 初始化数据库:
flask db init
flask db migrate -m "Initial migration"
flask db upgrade
  1. 运行应用:
flask run

前端安装和运行

  1. 安装依赖:
cd frontend
npm install
  1. 运行开发服务器:
npm run serve
  1. 构建生产版本:
npm run build

总结

这个老乡会网站实现了所有核心功能,包括用户认证、需求发布与检索、评论系统、公告管理等。系统采用前后端分离架构,具有良好的扩展性和维护性。

由于篇幅限制,这里只展示了核心代码。完整的实现还需要包括用户管理后台、个人信息编辑、需求编辑、评论管理等功能。您可以根据这个基础框架进行扩展和完善。

这个方案提供了高可用度的代码基础,您可以根据实际需求进行调整和优化。

评论 951
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百锦再@新空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值