22、企业内部网工作组协作应用详解

企业内部网工作组协作应用详解

1. 应用概述

该应用是一款适用于小团队的工作组工具,提供了丰富的办公通信和协作功能,如项目管理、为项目添加评论和文档等。它不像 Google Maps 那样炫酷,但实用且必要。Ajax 的运用相对低调,增强了应用的功能和可用性,却不引人注目,同时也展示了 Ajax 的适用场景。

2. 下载与配置

要下载该应用的源代码,可访问 相关链接 。若文件未列出,则与 Rails 默认框架相同。文件就位后,需通过编辑 config/database.yml 来配置数据库。默认配置期望一个名为 intranet_development 的 MySQL 数据库,可在本地主机使用用户名 root 且无密码访问。具体操作步骤如下:
1. 为应用创建数据库。
2. 根据需要修改 database.yml
3. 运行 rake db:schema:load 以根据 schema.rb 创建应用的数据库结构。

3. 数据库表结构

应用使用三张表: users posts attachments
| 表名 | 用途 |
| ---- | ---- |
| users | 管理用户信息 |
| posts | 存储各种类型的帖子,如文档、项目计划、消息、评论或联系人信息 |
| attachments | 管理二进制文件上传,如照片、电子表格、文档等 |

以下是创建表的代码:

ActiveRecord::Schema.define(  ) do
  create_table "users", :force => true do |t|
    t.column "email",        :string,   :limit => 100, 
                             :default => "",    :null => false
    t.column "password",     :string,   :limit => 100, 
                             :default => "",    :null => false
    t.column "name",         :string,   :limit => 40,  
                             :default => "",    :null => false
    t.column "phone",        :string,   :limit => 50,  
                             :default => "",    :null => false
    t.column "address",      :string,   :limit => 50,  
                             :default => "",    :null => false
    t.column "city",         :string,   :limit => 50,  
                             :default => "",    :null => false
    t.column "state",        :string,   :limit => 50,  
                             :default => "",    :null => false
    t.column "zip",          :string,   :limit => 50,  
                             :default => "",    :null => false
    t.column "picture_id",   :integer
    t.column "created_at",   :datetime
    t.column "updated_at",   :datetime
    t.column "status",       :string,   :limit => 50,  
                             :default => "",    :null => false
    t.column "last_active",  :datetime
    t.column "admin",        :boolean,                 
                             :default => false, :null => false
  end
  add_index "users", ["email"], :name => "email", :unique => true
  add_index "users", ["password"], :name => "password"
  create_table "posts", :force => true do |t|
    t.column "type",         :string,   :limit => 20
    t.column "post_id",      :integer
    t.column "created_at",   :datetime
    t.column "updated_at",   :datetime
    t.column "created_by",   :integer
    t.column "updated_by",   :integer
    t.column "name",         :string,   :limit => 128, 
                             :default => "Untitled", :null => false
    t.column "body",         :text, :default => "",  :null => false
    t.column "email",        :string,   :limit => 50,  
                             :default => "",         :null => false
    t.column "phone",        :string,   :limit => 50,  
                             :default => "",        :null => false
    t.column "start_date",   :date
    t.column "end_date",     :date
    t.column "attachment_id",           :integer
    t.column "attachment_filename",     :string
    t.column "attachment_content_type", :string,   :limit => 128
    t.column "attachment_size",         :integer
  end
  add_index "posts", ["type"], :name => "type"
  add_index "posts", ["created_at"], :name => "created_at"
  add_index "posts", ["updated_at"], :name => "updated_at"
  add_index "posts", ["post_id"], :name => "post_id"
  create_table "attachments", :force => true do |t|
    t.column "content",    :binary
    t.column "updated_at", :datetime
  end
end
4. 模型定义
  • User 模型 :用于记录系统用户,每个用户与他创建的帖子相关联,且每个用户可以有一个用户图片,存储在 Attachment 模型中。
class User < ActiveRecord::Base
  has_many   :posts, :foreign_key => "created_by", 
    :dependent => :destroy
  belongs_to :picture, :class_name =>'Attachment', 
    :foreign_key =>'picture_id', :dependent => :destroy
  validates_length_of     :name, :password, :email, :within => 4..100
  validates_uniqueness_of :email
  validates_format_of     :email, 
    :with => /^(([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,}))?$/
  def self.authenticate(email, password)
    find_by_email_and_password(email, password)
  end
  def first_name; name.split.first; end
  def last_name;  name.split.last; end
  def short_name
    name.blank? ? "" : "#{first_name} #{last_name[0,1]}."
  end
  def file= file
    unless file.size == 0
      picture = Attachment.new :content => file.read
      picture.save
      write_attribute'picture_id', picture.id
    end
  end
  def inactive?
    last_active < 1.minute.ago rescue true
  end
end
  • Post 模型 :是 Plan Message Document Comment 的超类,一个帖子有一个创建者(必须是用户),可以有附件和评论。
class Post < ActiveRecord::Base
  has_many   :comments,  :order =>'id', :dependent => :destroy
  belongs_to :creator, :class_name =>'User', 
    :foreign_key => "created_by"
  belongs_to :attachment, :dependent => :destroy
  validates_presence_of :name
  def file=(file)
    unless file.size == 0
      attachment=Attachment.new :content => file.read
      attachment.save
      write_attribute('attachment_id', attachment.id)
      write_attribute('attachment_filename', file.original_filename)
      write_attribute('attachment_content_type', file.content_type)
      write_attribute('attachment_size', file.size)
    end
  end
end
  • Contact 模型 :是 Post 的一种类型,用于存储人员信息,如销售代表、公关人员或客户。
class Contact < Post
  validates_format_of :name, :with => /^.+ .+$/, 
    :message => "must include full name"
  validates_format_of :email, 
    :with => /^(([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,}))?$/
  def self.letter letter
    Contact.find :all, 
      :conditions => [ "name like ?", '% '+letter+'%' ]
  end
  def reversed_name
    names = name.split
    "#{names.pop}, #{names.join ' '}"
  end
end
  • Document 模型 :是 Post 的子类,可表示几乎任何类型的内容,如电子表格、PDF、Word 文档等。
class Document < Post
end
  • Message 模型 :是 Post 的另一个子类,代表几乎任何类型的简单文本消息。
class Message < Post
end
  • Plan 模型 :代表特定类型的事件,提供按特定日期范围获取计划的方法。
class Plan < Post
  def self.this_week
    Plan.find :all, :conditions => "start_date >= now(  ) and 
        start_date < '#{Date.today + 7}'", 
      :order => "start_date asc"
  end
  def self.next_three_weeks
    Plan.find :all, :conditions => "start_date >= 
        '#{Date.today + 7}' and start_date < '#{Date.today + 28}'", 
      :order => "start_date asc"
  end
  def self.beyond
    Plan.find :all, 
      :conditions => "start_date >= '#{Date.today + 28}'", 
      :order => "start_date asc"
  end
end
  • Project 模型 :是 Post 的另一种类型。
class Project < Post
end
  • Comment 模型 :是一种简单的 Post ,可以附加到另一个帖子上。
class Comment < Post
  belongs_to :post
  validates_presence_of :body
end
  • Attachment 模型 :用于管理与文档相关的二进制数据以及系统用户的附加图像。
class Attachment < ActiveRecord::Base
end
5. 路由配置

应用的路由较为简单, map.resources 调用设置了对应用的 RESTful 访问。

ActionController::Routing::Routes.draw do |map|
  map.resources :messages, :plans, :documents, :projects, :contacts, 
    :member => { :download => :get }
  map.resources :comments, :path_prefix => "/:post_type/:post_id"
  map.resources :sessions
  map.resources :users, :collection => { :statuses => :get }, 
                        :member => { :status => :any }
  map.home '', :controller =>'messages', :action =>'home'
  map.connect ':controller/:action/:id'
end
6. 环境与辅助工具

环境文件要求应用加载 lib/labeling_form_helper.rb authentication.rb 提供简单的身份验证服务。

RAILS_GEM_VERSION ='1.1.2'
require File.join(File.dirname(__FILE__  _),'boot')
Rails::Initializer.run do |config|
end
require'labeling_form_helper'
7. 身份验证模块
module Authentication
  protected
    def logged_in?
      return false unless session[:user_id]
      begin
        @current_user ||= User.find(session[:user_id])
      rescue ActiveRecord::RecordNotFound
        reset_session
      end
    end
    def current_user
      @current_user if logged_in?
    end
    def require_login
      username, passwd = get_auth_data
      if username && passwd
        self.current_user ||= 
          User.authenticate(username, passwd) || :false
      end
      return true if logged_in?
      respond_to do |format|
        format.html do
          session[:return_to] = request.request_uri
          redirect_to new_session_url
        end
        format.xml do
          headers["Status"]           = "Unauthorized"
          headers["WWW-Authenticate"] = %(Basic realm="Web Password")
          render :text => "Could't authenticate you", 
            :status =>'401 Unauthorized'
        end
      end
      false
    end
    def access_denied
      redirect_to new_session_url
    end
    def store_location
      session[:return_to] = request.request_uri
    end
    def redirect_back_or_default(default)
      session[:return_to] ? 
        redirect_to_url(session[:return_to]) : 
        redirect_to(default)
      session[:return_to] = nil
    end
    def self.included(base)
      base.send :helper_method, :current_user, :logged_in?
    end
  private
    def get_auth_data
      user, pass = nil, nil
      if request.env.has_key?'X-HTTP_AUTHORIZATION' 
        authdata = request.env['X-HTTP_AUTHORIZATION'].to_s.split 
      elsif request.env.has_key?'HTTP_AUTHORIZATION' 
        authdata = request.env['HTTP_AUTHORIZATION'].to_s.split  
      end 
      if authdata && authdata[0] =='Basic' 
        user, pass = Base64.decode64(authdata[1]).split(':')[0..1] 
      end 
      return [user, pass] 
    end
end
8. 表单构建器

LabelingFormBuilder 重写了 form_for remote_form_for 中的一些方法,扩展它们以自动处理字段名称。

class LabelingFormBuilder < ActionView::Helpers::FormBuilder
  (%w(text_field password_field text_area 
      date_select file_field)).each do |selector|
    src = <<-end_src
      def #{selector}(method, options = {})
        text = options.delete(:label) || method.to_s.humanize
        errors = @object.errors.on(method.to_s)
        errors = errors.is_a?(Array) ? errors.first : errors.to_s
        html = '<label for="' + @object_name.to_s +'_' + 
                method.to_s + '">'
        html << text
        unless errors.blank?
          html << ' <span class="error">' + errors + '</span>'
        end
        html << '</label> '
        #{selector=='date_select' ? "html << '<span id=\"' + 
                     @object_name.to_s +'_' + method.to_s + 
                     '\"></span>'" : ""}
        html << super
        html
      end
    end_src
    class_eval src, __FILE__, __LINE_  _
  end
end
9. 应用控制器

ApplicationController 提供了一些 before_filter 来确保用户已登录、用户有效并显示每日消息,还提供了访问控制的辅助方法。

class ApplicationController < ActionController::Base
  include Authentication
  before_filter :require_login
  before_filter :set_system_announcement
  before_filter :check_for_valid_user
  private
    def set_system_announcement
      flash.now[:system_announcement] = 
        "This is the <strong>Ajax on Rails Intranet</strong>, <br/>
         released as part of <a href=\"http://scottraymond.net/\">
         <em>Ajax on Rails</em></a> from O&rsquo;Reilly Media."
    end
    def can_edit? record
      return true if current_user.admin?
      case record.class.to_s
        when'User'
          record.id == current_user.id
        when'Message'
          record.created_by == current_user.id
        else true
      end
    end
    helper_method :can_edit?
    def admin?; current_user.admin?; end
    helper_method :admin?
    def require_admin
      unless admin?
        flash[:warning] = "Sorry, only administrators can do that."
        redirect_to messages_url
      end
    end
    def check_for_valid_user
      if logged_in? and !current_user.valid?
        flash[:warning] = "Please create your administrator account"
        redirect_to edit_user_url(:id => current_user)
        return false
      end
    end
end
10. 辅助方法模块

application_helper.rb 定义了更多辅助方法,用于返回有关内容类型的信息, page_title 尝试在未明确给出标题时推断页面标题, standard_form 使用 labeling_form_helper 来简化视图模板。

module ApplicationHelper
  def icon_for content_type
    case content_type.to_s.strip
      when "image/jpeg"
        "JPG"
      when "application/vnd.ms-excel"
        "XLS"
      when "application/msword"
        "DOC"
      when "application/pdf"
        "PDF"
      else "Generic"
    end
  end
  def description_of content_type
    case content_type.to_s.strip
      when "image/jpeg"
        "JPEG graphic"
      when "application/vnd.ms-excel"
        "Excel worksheet"
      when "application/msword"
        "Word document"
      when "application/pdf"
        "PDF file"
      else ""
    end
  end
  def site_title
   'Intranet'
  end
  def page_title
    return @page_title if @page_title
    return @post.name if @post and !@post.new_record?
    return @user.name if @user and !@user.new_record?
    ''
  end
  def flash_div *keys
    divs = keys.select { |k| flash[k] }.collect do |k|
      content_tag :div, flash[k], :class => "flash #{k}"
    end
    divs.join
  end
  def user_thumb user
    img = tag("img", 
      :src => formatted_user_url(:id => user, :format =>'jpg'), 
      :class =>'user_picture', :alt => user.name)
    img_link = link_to img, user_url(:id => user)
    text_link = link_to user.short_name, user_url(:id => user)
    content_tag :div, "#{img_link}<br/>#{text_link}", 
      :class =>'user'
  end
  def clear_div
    '<div class="clear"></div>'
  end
  def standard_form name, object, &block
    url  = { :action    => object.new_record? ? "index" : "show" }
    html = { :class     => "standard",
             :style     => (@edit_on ? '' : "display: none;"),
             :multipart => true }
    concat form_tag(url, html) + "<fieldset>", block.binding
    unless object.new_record?
      concat '<input name="_method" type="hidden" value="put" />', 
        block.binding
    end
    yield LabelingFormBuilder.new(name, object, self, {}, block)
    concat "</fieldset>" + end_form_tag, block.binding
  end
  def standard_submit name=nil, object=nil
    name = post_type unless name
    object = @post unless object
    delete_link = link_to("Delete", { :action =>'show' }, 
      :method => :delete, 
      :confirm => "Are you sure?",
      :class => "delete")
    submit_tag("Save #{name}") + 
      (object.new_record? ? "" : (" or " + delete_link))
  end
end
11. 布局文件

application.rhtml 是一个基本布局,包含用于浏览应用的链接,包括登录和注销链接,以及用于创建一些标签式导航的 CSS。 utility DIV 是一个 Ajax 侧边栏,列出了登录和未登录的用户。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml
<html xmlns="http://www.w3.org/1999/xhtml" 
      xml:lang="en" lang="en">
  <head>
    <title>
      <%= site_title + 
          (page_title.blank? ? '' : " - #{page_title}") %>
    </title>
    <%= stylesheet_link_tag "application" %>
    <%= javascript_include_tag :defaults %>
  </head>
  <body class="<%= params[:controller] %>">
    <%= flash_div :system_announcement %>
    <div id="header">
      <h1><%= link_to site_title, home_url %></h1>
      <% if logged_in? and current_user.valid? %>
        <div id="account">
          Signed in as 
          <%= link_to current_user.first_name, 
                user_url(:id => current_user), 
                :class =>'strong stealth' %><br/>
          <%= link_to'Settings', 
                      edit_user_url(:id => current_user), 
                      :class =>'small subtle' %> 
          <%= link_to'Sign Out', 
                      session_url(:id => session.session_id), 
                      :method => :delete, 
                      :class =>'small delete' %>
        </div>
        <ul id="nav">
          <li id="messages">
            <%= link_to "Messages", messages_url %>
          </li>
          <li id="plans">
            <%= link_to "Event Plans", plans_url %>
          </li>
          <li id="documents">
            <%= link_to "Documents", documents_url %>
          </li>
          <li id="projects">
            <%= link_to "Projects", projects_url %>
          </li>
          <li id="contacts">
            <%= link_to "Contacts", contacts_url %>
          </li>
        </ul>
      <% end %>
    </div>
    <div id="utility">
      <%= flash_div :notice %>
      <% if logged_in? and current_user.valid? %>
        <div id="status">
          <ul>
            <li>
              <%= link_to current_user.short_name, 
                          user_url(:id => current_user) %>
              <span id="my_status">
                <%= current_user.status.blank? ? 
                      "(Click to set status)" : 
                      current_user.status %>
              </span>
            </li>
          </ul>
          <%= javascript_tag "new Ajax.InPlaceEditor('my_status', 
                '#{user_url(current_user)}', 
                {loadTextURL:'#{status_user_url(current_user)}', 
                 ajaxOptions:{method:'put'}, 
                 callback:function(form, value){ 
                    return'user[status]='+escape(value); 
                 }});" %>
          <%= render :partial => "users/statuses" %>
          <%= javascript_tag "new PeriodicalExecuter(function(  ){ 
                new Ajax.Updater('statuses', '#{statuses_users_url}', 
                   {method:'get'}); }, 30)" %>
        </div>
      <% end %>
    </div>
    <div id="main">
      <%= flash_div :warning %>
      <%= content_tag :h2, page_title %>
      <%= yield %>
    </div>
  </body>
</html>
12. 控制器
  • PostsController :是所有其他控制器的超类,实现了帖子的所有基本 CRUD 操作。
class PostsController < ApplicationController
  before_filter :find_post, 
    :only => [ :show, :download, :edit, :update, :destroy ]
  before_filter :check_permissions, :only => [ :update, :destroy ]
  def index
    @page_title = post_type.pluralize
    @post = model.new
    @posts = model.find :all
  end
  def new
    @page_title = "New #{post_type}"
    @edit_on = true
    @post = model.new
  end
  def create
    @post = model.new params[:post]
    @post.creator = current_user
    @post.updated_by = @post.created_by
    if @post.save 
      flash[:notice] ='Post successfully created.'
      redirect_to :action =>'index'
    else
      @page_title = "New #{post_type}"
      @edit_on = true
      render :action =>'new'
    end
  end
  def show
  end
  def download
    filename = @post.attachment_filename.split(/\\/).last
    send_data @post.attachment.content, :filename => filename, 
      :type => @post.attachment_content_type, 
      :disposition =>'attachment'
  end
  def edit
    @edit_on = true
    render :action =>'show'
  end
  def update
    post = params[:post].merge(:updated_by => current_user)
    if @post.update_attributes post
      flash[:notice] ='Your changes were saved.'
      redirect_to :action =>'show'
    else
      @edit_on = true
      render :action =>'show'
    end
  end
  def destroy
    @post.destroy
    flash[:notice] = "The post was deleted."
    redirect_to :action =>'index'
  end
  private
    def model_name;'Post'; end
    def post_type; model_name; end
    helper_method :post_type
    def model; eval model_name; end
    def find_post
      @post = model.find params[:id]
    end
    def check_permissions
      unless can_edit? @post
        flash[:warning] = "You can't edit that post."
        redirect_to :action =>'show'
        return false
      end
    end
end
  • CommentsController :是 Posts 控制器的子类,用于处理评论的创建。
class CommentsController < ApplicationController
  before_filter :find_post
  def index
  end
  def create
    @comment = Comment.new params[:comment]
    @comment.post_id = @post.id
    @comment.name = "Re: #{@post.name}"
    @comment.creator = current_user
    @comment.save
    respond_to do |format|
      format.html {
        flash[:notice] = "Comment saved."
        redirect_to :back
      }
      format.js {
        render :update do |page|
          page[:comments].reload
        end
      }
    end
  end
  def show
    @comment = @post.comments.find params[:id]
  end
  private
    def find_post
      @post = Post.find params[:post_id]
    end
end
  • ContactsController :用于管理联系人记录,继承了 PostsController 的大部分行为。
class ContactsController < PostsController
  def index
    @page_title = post_type.pluralize
    @post = model.new
    @posts = params[:letter] ? 
      Contact.letter(params[:letter]) : 
      Contact.find(:all)
  end
  private
    def model_name;'Contact'; end
end
  • DocumentsController :用于管理文档,继承了 PostsController 的行为。
class DocumentsController < PostsController
  private
    def model_name;'Document'; end
end
  • MessagesController :用于管理消息,继承了 PostsController 的行为。
class MessagesController < PostsController
  def home
    flash.keep
    redirect_to messages_url
  end
  def index
    super
    @post_pages, @posts = paginate :messages, 
      :order_by =>'created_at desc', :per_page => 30
  end
  private
    def model_name;'Message'; end
end
  • PlansController :用于管理计划,继承了 PostsController 的行为。
class PlansController < PostsController
  def index
    super
    @page_title = "Upcoming Event Plans"
    @this_week = Plan.this_week
    @next_three_weeks = Plan.next_three_weeks
    @beyond = Plan.beyond
  end
  private
    def model_name;'Plan'; end
    def post_type;'Event Plan'; end
end
  • ProjectsController :用于管理项目,继承了 PostsController 的行为。
class ProjectsController < PostsController
  private
    def model_name;'Project'; end
end
  • SessionsController :处理登录和注销。
class SessionsController < ApplicationController
  before_filter :create_first_user, :only => :new
  skip_before_filter :require_login
  filter_parameter_logging :password
  def new
    redirect_to home_url if logged_in?
    @user = User.new
  end
  def create
    if user = User.authenticate(params[:session][:email], 
                                params[:session][:password])
      reset_session
      session[:user_id] = user.id
      redirect_back_or_default home_url
      flash[:notice] = "Signed in successfully"
    else
      flash[:warning] = "There was a problem signing you in. 
                         Please try again."
      @user = User.new
      render :action =>'new'
    end
  end
  def destroy
    reset_session
    flash[:notice] = "You have been signed out."
    redirect_to new_session_url
  end
  private
    def create_first_user
      return true unless User.count == 0
      user = User.new :admin => 1
      user.save_with_validation false
      session[:user_id] = user.id
      redirect_to home_url
    end
end
  • UsersController :支持用户注册和编辑用户资料, statuses 操作是应用存在指示器的关键。
class UsersController < ApplicationController
  before_filter :require_admin, :only => [ :new, :create ]
  before_filter :find_user, 
    :only => [ :show, :status, :edit, :update, :destroy ]
  before_filter :check_permissions, 
    :only => [ :edit, :update, :destroy ]
  skip_before_filter :check_for_valid_user, 
    :only => [ :edit, :update ]
  filter_parameter_logging :password
  def index
    @users = User.find :all
    @page_title = "Users"
    @user = User.new
  end
  def new
    @page_title = "New User"
    @user = User.new
    @edit_on = true
  end
  def statuses
    current_user.update_attributes :last_active => Time.now
    render :partial =>'statuses'
  end
  def create
    if @user = User.create(params[:user])
      flash[:notice] ='User was successfully saved.'
      redirect_to user_url(:id => @user)
    else
      render :action =>'index'
    end
  end
  def show
    if params[:format]=='jpg'
      if @user.has_picture?
        send_data @user.picture.content, 
          :filename    => "#{@user.id}.jpg", 
          :type        =>'image/jpeg', 
          :disposition =>'inline'
      else
        send_file RAILS_ROOT+'/public/images/default_user.jpg', 
          :filename    => "#{@user.id}.jpg", 
          :type        =>'image/jpeg', 
          :disposition =>'inline'
      end
      return
    end
  end
  def status
    render :text => @user.status
  end
  def edit
    @edit_on = true
    render :action =>'show'
  end
  def update
    success = @user.update_attributes params[:user]
    respond_to do |format|
      format.html {
        if success
          flash[:notice] ='User was successfully updated.'
          redirect_to user_url
        else
          @edit_on = true
          render :action =>'show'
        end
      }
      format.js {
        render :text => @user.status.blank? ?
                           "(none)" :
                           @user.status
      }
    end
  end
  def destroy
    @user.destroy
    flash[:notice] = "User deleted."
    redirect_to users_url
  end
  private
    def post_type; "User"; end
    helper_method :post_type
    def find_user
      @user = User.find params[:id]
    end
    def check_permissions
      return false unless can_edit? @user
    end
end
13. 视图与脚本
  • PostForm 类 :提供用户点击编辑链接时的视觉效果,表单会滑过内容。
var PostForm = {
  toggle: function(  ) {
    var container = $('form_container');
    var form = $$('#form_container form').first(  );
    if(container.hasClassName('active')) {
      form.visualEffect('blind_up', { 
        duration: 0.25, 
        afterFinish: function(  ){
          container.removeClassName('active');
        }
      });
    } else {
      form.visualEffect('blind_down', { 
        duration: 0.5, 
        beforeStart: function(  ){
          container.addClassName('active');
        }
      });
    }
  }
}
14. 样式表
/* Basics */
/* --------------------------------------------------------- */
*/* */ {
  color: inherit;
  font: inherit;
  margin: 0;
  list-style: none;
  padding: 0;
  text-decoration: none;
}
body {
    background-color: #fff;
    background-repeat: repeat-y;
    color: #333;
}
body, p, ol, ul, td {
    font-family: verdana, arial, helvetica, sans-serif;
    font-size:   11px;
    line-height: 14px;
}
p { margin-bottom: 8px; }
ul li { list-style-type: disc; }
ul, ol { margin: .5em 0 .5em 2em; }
ol li { list-style-type: decimal; }
fieldset { border: none; }
strong, b { font-weight: bold; }
em { font-style: italic; }
.strong { font-weight: bold; }
.small { font-size: 10px; }
#main {
    float: left;
    position: relative;
    left: -2px;
    top: 24px;
    padding-right: 30px;
    width: 575px;
    padding-bottom: 50px;
}
#utility {
    width: 170px;
    padding: 45px 10px 20px 18px;
    float: left;
    height: 100%;
}
div.clear {
    clear: both;
    margin-top: 1px;
    display: block;
}
/* Links */
/* ------------------------------------------------------------ */
a { color: #264764; text-decoration: underline; }
a:visited { color: #264764; }
a:hover { color: #fff; background-color: #264764; 
          text-decoration: none; }
a.stealth { color: #000; text-decoration: none; }
a:hover.stealth { background-color: #000; color: #fff; }
a.subtle { color: #666; text-decoration: underline; }
a:hover.subtle { background-color: #666; color: #fff; }
a.delete { color: #c00; text-decoration: underline; }
a:hover.delete { background-color: #c00; color: #fff; }
a.create { color: #009900; text-decoration: underline; }
a:hover.create { background-color: #009900; color: #fff; }
/* Headers */
/* ---------------------------------------------------------- */
#header {
    height: 92px;
    background-color: #E0E6EF;
    border-bottom: 1px solid #888;
}
#header h1 {
    font-family: futura;
    font-size: 30px;
    float: left;
    height: 92px;
    width: 181px;
    xbackground-image: url('/images/logo.gif');
    xtext-indent: -1000px;
    /* or */
    height: 37px;
    padding-top: 55px;
    width: 136px;
    padding-left: 45px;
}
#header h1 a { text-decoration: none; }
#header #account {
    float: right;
    text-align: right;
    font-family: verdana;
    font-size: 11px;
    color: #333;
    margin-right: 8px;
    margin-top: 15px;
    line-height: 14px;
}
#main h2 {
    font-family: trebuchet ms;
    font-size: 18px;
    font-weight: normal;
    color: #264764;
    margin-bottom: 0px;
    border-bottom: 1px solid #B8B8B8;
    width: 569px;
    padding-bottom: 8px;
    clear: both;
}
h3 {
    font-size: 12px;
    font-weight: bold;
    margin-top: 10px;
    margin-bottom: 0;
    background-color: #eee;
    padding: 3px 0 3px 5px;
    border-bottom: 1px solid #ddd;
}
h4 {
    font-size: 11px;
    font-weight: bold;
    margin-top: 10px;
    margin-bottom: 2px;
}
/* Warnings and notices */
/* ----------------------------------------------------------- */
.flash.notice {
  background-color: #ffc;
  padding: .5em;
  border-top: 1px solid #dda;
  border-bottom: 1px solid #dda;
  margin: 0 30px 1.5em 0;
}
.flash.warning {
  background-color: #c22;
  padding: .5em;
  border-top: 1px solid #600;
  border-bottom: 1px solid #600;
  margin: 0em 0 2em 0em;
  color: #fff;
  font-weight: bold;
}
.flash.warning a { color: #fff; }
.flash.system_announcement {
  padding: 5px;
  background-color: #EFF3AB;
  border-bottom: 1px solid #898989;
  color: #444;
  text-align: center;
  height: 30px;
}
/* Navigation */
/* ------------------------------------------------------------- */
ul#nav { margin: 0; position: relative; left: 15px; top: 67px; }
html>body ul#nav { top: 68px; } /* non-iewin */
ul#nav li {
  display: inline;
  height: 30px;
  font-size: 12px;
  line-height: 26px;
  font-family: helvetica, arial;
  margin-right: 5px;
  padding: 3px 4px 5px 7px;
}
html>body ul#nav li { padding: 3px 7px 4px 7px; } /* non-iewin */
body.messages li#messages, body.plans li#plans, 
body.documents li#documents, body.projects li#projects, 
body.contacts li#contacts {
    background-color: #fff; border: 1px solid #888; 
    border-bottom: 1px solid #fff;
}
ul#nav li a { text-decoration: none; color: #555; }
ul#nav li a:hover { background-color: transparent; 
                    text-decoration: none; }
ul#nav li:hover a { text-decoration: none; color: #000; }
ul#nav li:hover { text-decoration: underline; }
/* Statuses */
/* ------------------------------------------------------------ */
#status ul li {
    list-style-type: none;
    margin-bottom: 5px;
    font-weight: bold;
}
#status ul li span {
    font-weight: normal;
    display: block;
  font-style: italic;
}
#status ul { margin-left: 0; }
#status ul li a { text-decoration: none; }
#status ul li.inactive, #status ul li.inactive a { color: #777; }
/* Post container */
/* ------------------------------------------------------------- */
#form_container {
    padding: 6px 10px 15px 12px;
    width: 545px;
    margin-bottom: 0px;
}
#form_container.active {
  background: #EEF8ED;
  border-left: 1px solid #89B989;
  border-right: 1px solid #89B989;
  border-bottom: 1px solid #89B989;
}
#form_container #new_link {
    float: left;
    color: #009900;
    font-weight: bold;
    margin-left: -12px;
}
#form_container.active #new_link span { visibility: hidden; }
#form_container #cancel_link { visibility: hidden; float: right; }
#form_container.active #cancel_link { visibility: visible; }
#form_container.active #new_link a { text-decoration: none; }
#form_container #meta { float: left; margin-left: 10px; }
#form_container #detail { clear: left; padding-top: 20px; }
#form_container.active #detail { display: none; }
/* Standard form */
/* ------------------------------------------------------------- */
form.standard {
    clear: left;
    margin-top: 10px;
  margin-left: 15px;
  padding-top: 10px;
  width: 510px;
}
form.standard label {
    font-weight: bold;
    display: block;
    margin-bottom: 3px;
    font-size: 12px;
    font-family: verdana;
}
form.standard input, form.standard textarea {
    width: 100%;
    display: block;
    margin-bottom: 10px;
}
form.standard input#post_name, form.standard input#user_name { 
  font-size: 18px; font-weight: bold; 
}
form.standard .fieldWithErrors { 
  border-left: 4px solid #c00; padding-left: 3px; 
}
form.standard label span.error { color: #c00; }
form.standard input[type='submit'] { width: 100px; display: inline; }
form.standard textarea { height: 150px; }
form.standard select { margin-bottom: 10px; }
/* Body details */
/* ------------------------------------------------------------ */
.post_detail {
  background-color: #eee;
  border: 1px solid #ccc;
  padding: 12px;
  width: 508px;
}
#main div.post {
    margin-top: 11px;
    margin-bottom: 25px;
    margin-left: 1px;
}
#main div.post div.user {
    width: 60px;
    float: left;
    text-align: center;
    margin-right: 10px;
    margin-top: 4px;
    font-size: 10px;
}
img.user_picture {
    text-decoration: none;
    background-color: #fff;
    margin-bottom: -2px;
    width: 60px;
    height: 60px;
    border: 1px solid #666;
    padding: 2px;
}
#main div.post h3 {
    font-weight: bold;
    font-size: 11px;
    padding-top: 2px;
    padding-left: 1px;
}
#main div.post h3 span {
    color: #666;
    font-weight: normal;
    margin-left: 8px;
}
#main div.post p.meta {
    color: #666;
    font-size: 10px;
    margin-bottom: 5px;
}
#main img.icon {
  float: left;
  width: 32px;
  height: 32px;
  padding-right: 8px;
}
#main div.post h3 span a { color: #666; font-weight: normal; }
#main div.post h3 span a:hover { background-color: #666; 
                                 color: #fff; }
#main div.post div.body { margin-left: 70px; }
#main div.post div.no_user { margin-left: 0px; }
#main div.post p.meta span.comments { 
  float: right; font-size: 11px; 
}
#main p.meta { color: #666; margin-bottom: 10px; }
#letter_links {
    margin-top: 20px;
    margin-bottom: 20px;
}
#letter_links a {
    background-color: #ffa;
    padding: 3px 4px;
    margin: 0px 1px;
    border: 1px solid #dd9;
    text-decoration: none;
}
#letter_links a:hover, #letter_links a.active {
    color: #000;
    background-color: #dd9;
    border: 1px solid #cc7;
}
/* Comments */
/* ------------------------------------------------------------- */
#comments {
    background-color: #eee;
    width: 500px;
    margin: 3em 0 1em 0;
    padding: 0 0 1em 0;
    width: 100%;
}
#comments h2 {
  background-color: #777;
  width: 100%;
  color: #fff;
  font-size: 1em;
  font-weight: bold;
  padding: 3px 0px 3px 3px;
  line-height: 1em;
  border-bottom: 1px solid #555;
}
#comments form, #comments div.post { 
  margin: 1em 0 1em 1em; background-color: #eee; 
}
#comments form textarea { width: 90%; height: 80px; }
#comments h3 { font-size: 1em; }
#comments input { float: left; }
#comments p img { margin-top: 1px; margin-left: 10px; }
/* User list */
/* ----------------------------------------------------------- */
table#posts { width: 100%; }
table#posts td { margin: 0; padding: 4px; 
                 border-bottom: 1px solid #ccc; }
/* User#show */
/* ----------------------------------------------------------- */
body.user_show #main img {
  float: left;
  margin-right: 20px;
  border: 1px solid #ccc;
  padding: 3px;
  width: 80px;
  height: 80px;
  margin-bottom: 100px;
}
#change_picture {
    float: right;
    width: 120px;
    padding: 15px;
    text-align: center;
    border: 1px solid #ccc
}
body.user_show #main { width: 600px; }
body.user_show #main h3 { clear: left; margin-bottom: 10px; }
body.user_show #main ul#lookuplinks li { 
  display: inline; margin-right: 10px; 
}
#change_picture img { border: 1px solid black; } 

通过以上详细的介绍,我们可以全面了解这个企业内部网工作组协作应用的各个方面,包括其功能、数据库结构、模型定义、控制器实现、视图设计以及样式表设置等。这些组件相互协作,为小团队提供了一个高效的办公通信和协作平台。

企业内部网工作组协作应用详解

15. 各部分功能总结与关联

为了更清晰地理解整个应用的架构和各部分之间的关系,我们可以通过以下表格进行总结:
| 部分 | 功能 | 关联 |
| ---- | ---- | ---- |
| 数据库表 | 存储应用的数据,如用户信息、帖子信息、附件信息等 | 模型通过 ActiveRecord 与数据库表交互 |
| 模型 | 定义数据结构和业务逻辑,如用户、帖子、附件等 | 控制器通过模型进行数据的增删改查操作 |
| 控制器 | 处理用户请求,实现 CRUD 操作 | 视图通过控制器获取数据并展示 |
| 视图 | 展示数据给用户,提供用户交互界面 | 控制器将处理结果传递给视图 |
| 路由 | 映射 URL 到相应的控制器动作 | 连接用户请求和控制器 |
| 辅助方法 | 提供额外的功能,如表单构建、内容类型处理等 | 视图和控制器可以调用辅助方法 |
| 样式表 | 定义应用的外观和布局 | 影响视图的显示效果 |

下面是一个 mermaid 流程图,展示了用户从登录到操作帖子的基本流程:

graph LR
    A[用户登录] --> B{登录成功?}
    B -- 是 --> C[进入应用主页]
    B -- 否 --> A
    C --> D[选择帖子类型]
    D --> E[查看帖子列表]
    E --> F{选择操作}
    F -- 创建 --> G[填写帖子信息]
    F -- 查看 --> H[查看帖子详情]
    F -- 编辑 --> I[修改帖子信息]
    F -- 删除 --> J[确认删除]
    G --> K[保存帖子]
    I --> K
    K --> E
    J --> E
16. 具体操作流程分析
  • 用户登录流程

    1. 用户访问登录页面,输入邮箱和密码。
    2. 表单提交到 SessionsController create 动作。
    3. create 动作调用 User.authenticate 方法验证用户信息。
    4. 如果验证成功,将用户 ID 存储在会话中,并重定向到主页;否则,显示错误信息并重新渲染登录页面。
  • 帖子创建流程

    1. 用户在主页选择创建新帖子。
    2. 进入创建页面,填写帖子信息。
    3. 表单提交到相应控制器的 create 动作。
    4. create 动作创建新的帖子对象,设置创建者和更新者信息。
    5. 尝试保存帖子,如果保存成功,显示成功信息并重定向到帖子列表页面;否则,显示错误信息并重新渲染创建页面。
  • 帖子编辑流程

    1. 用户在帖子列表中选择要编辑的帖子。
    2. 进入编辑页面,显示帖子的当前信息。
    3. 用户修改帖子信息后提交表单。
    4. 表单提交到相应控制器的 update 动作。
    5. update 动作更新帖子信息,如果更新成功,显示成功信息并重定向到帖子详情页面;否则,显示错误信息并重新渲染编辑页面。
17. 代码优化建议
  • 性能优化

    • 对于频繁查询的操作,可以考虑使用缓存技术,减少数据库查询次数。例如,在 UsersController statuses 动作中,可以缓存用户状态信息,避免每次都查询数据库。
    • 对于大数据量的查询,可以使用分页技术,减少一次性加载的数据量。例如,在 MessagesController index 动作中,使用 paginate 方法进行分页查询。
  • 代码复用

    • 可以将一些通用的方法提取到辅助方法模块中,提高代码的复用性。例如,将文件上传的逻辑提取到一个单独的辅助方法中,在多个控制器中复用。
    • 对于一些重复的表单字段,可以创建表单组件,减少代码的重复编写。
  • 安全性优化

    • 对用户输入进行严格的验证和过滤,防止 SQL 注入和 XSS 攻击。例如,在模型中使用 validates 方法对用户输入进行验证。
    • 对敏感信息进行加密存储,如用户密码。可以使用 Rails 提供的加密机制对密码进行加密。
18. 总结

这个企业内部网工作组协作应用为小团队提供了一个功能丰富的办公通信和协作平台。通过合理的数据库设计、模型定义、控制器实现和视图设计,实现了用户管理、帖子管理、评论管理等功能。同时,通过路由配置、辅助方法和样式表的设置,提高了应用的易用性和美观性。

在实际应用中,可以根据具体需求对应用进行扩展和优化。例如,可以添加更多的帖子类型、增加权限管理功能、优化性能等。通过不断地改进和完善,这个应用可以更好地满足团队的协作需求。

希望以上内容能帮助你更好地理解和使用这个企业内部网工作组协作应用。如果你有任何问题或建议,欢迎随时交流。

通过以上对企业内部网工作组协作应用的深入分析,我们可以看到它在功能实现、架构设计和用户体验等方面都有很多值得学习和借鉴的地方。无论是对于开发者还是团队管理者,都可以从中获取有价值的信息,以提升团队的协作效率和工作质量。

【EI复现】基于深度强化学习的微能源网能量管理与优化策略研究(Python代码实现)内容概要:本文围绕“基于深度强化学习的微能源网能量管理与优化策略”展开研究,重点利用深度Q网络(DQN)等深度强化学习算法对微能源网中的能量调度进行建模与优化,旨在应对可再生能源出力波动、负荷变化及运行成本等问题。文中结合Python代码实现,构建了包含光伏、储能、负荷等元素的微能源网模型,通过强化学习智能体动态决策能量分配策略,实现经济性、稳定性和能效的多重优化目标,并可能与其他优化算法进行对比分析以验证有效性。研究属于电力系统与人工智能交叉领域,具有较强的工程应用背景和学术参考价值。; 适合人群:具备一定Python编程基础和机器学习基础知识,从事电力系统、能源互联网、智能优化等相关方向的研究生、科研人员及工程技术人员。; 使用场景及目标:①学习如何将深度强化学习应用于微能源网的能量管理;②掌握DQN等算法在实际能源系统调度中的建模与实现方法;③为相关课题研究或项目开发提供代码参考和技术思路。; 阅读建议:建议读者结合提供的Python代码进行实践操作,理解环境建模、状态空间、动作空间及奖励函数的设计逻辑,同时可扩展学习其他强化学习算法在能源系统中的应用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值