在本节我想与大家与分享一下,我所将要做的权限系统的架构和数据库的表的设计。请各位大神们对我项目中设计的不足之处进行指导,让我得以更好的写完它,留给需要它的人。

我的项目架构如下图所示:

如上图所示,在数据访问层,我采用了抽象工厂的方式,来对数据访问层和业务逻辑层解耦,当然如果你想更高大上一些,可以用第三方的框架,比如Spring.Net ,Autofac来实现。解耦的好处在于可以方便的切换数据库,当数据库变更时,只需换一下对应的数据访问DAL就行,本系列中,我所采用的是SQLServer。写到这我想在如下这个大数据时代,MongoDB其实也是不错的。后面有机会,可以开一个MongoDB的专题和大家一起来使用一下MongoDB学习一下它。对于抽象工厂来实现业务逻辑层与数据访问层的解耦实现代码如下,主要用到了反射,面向接口编程。

配置:

<appSettings>

  <add key="DAL" value="MCFramework.SQLDAL"/>
  
  <!--页容量-->
  <add key="PageSize" value="20"/>

</appSettings>
View Code

抽象工厂:

namespace MCFramework.RepositoryFactory
{
    public class RepositoryFactory
    {
        public RepositoryFactory()
        { }

        private static readonly string AssemblyPath =ConfigurationSettings.AppSettings["DAL"];

        #region CreateObject

        //不使用缓存
        private static object CreateObjectNoCache(string AssemblyPath, string classNamespace)
        {
            try
            {
                object objType = Assembly.Load(AssemblyPath).CreateInstance(classNamespace);
                return objType;
            }
            catch(System.Exception ex)
            {
                string str=ex.StackTrace;// 记录错误日志
                return null;
            }

        }
        //使用缓存
        //private static object CreateObject(string AssemblyPath, string classNamespace)
        //{
        //    object objType = DataCache.GetCache(classNamespace);
        //    if (objType == null)
        //    {
        //        try
        //        {
        //            objType = Assembly.Load(AssemblyPath).CreateInstance(classNamespace);
        //            DataCache.SetCache(classNamespace, objType);// 写入缓存
        //        }
        //        catch//(System.Exception ex)
        //        {
        //            //string str=ex.Message;// 记录错误日志
        //        }
        //    }
        //    return objType;
        //}
        #endregion


        /// <summary>
        /// 用户仓储
        /// </summary>
        public static ISystem_EmployeeRepository System_EmployeeRepository { get { return (ISystem_EmployeeRepository)CreateObjectNoCache(AssemblyPath, AssemblyPath + ".System_EmployeeRepository"); } }

        /// <summary>
        ///菜单仓储
        /// </summary>
        public static ISystem_MenuRepository  System_MenuRepository { get { return (ISystem_MenuRepository)CreateObjectNoCache(AssemblyPath, AssemblyPath + ".System_MenuRepository"); } }

        /// <summary>
        ///角色仓储
        /// </summary>
        public static ISystem_RoleRepository System_RoleRepository { get { return (ISystem_RoleRepository)CreateObjectNoCache(AssemblyPath, AssemblyPath + ".System_RoleRepository"); } }

        /// <summary>
        ///按钮仓储
        /// </summary>
        public static ISystem_ButtonRepository System_ButtonRepository { get { return (ISystem_ButtonRepository)CreateObjectNoCache(AssemblyPath, AssemblyPath + ".System_ButtonRepository"); } }

    }
}
View Code

所有的访问数据库的操作都用接口来约束:

namespace MCFramework.IDAL
{
    public interface IBaseRepository<T> where T:class
    {
        int Add(T model);

        int UpdateDel(string ids, bool isDel);

        int Del(string ids);

        int Update(T model);

        DataSet GetListByProc(string procName, System.Data.SqlClient.SqlParameter[] paras);

        DataSet GetModel(string Id);

        DataSet GetList(string strWhere);

    }
}
View Code
namespace MCFramework.IDAL
{
   public interface ISystem_ButtonRepository:IBaseRepository<System_ButtonModel>
    {
        bool IsExit(string ButtonCode);
    }
}
View Code

接口的实现:

namespace MCFramework.SQLDAL
{
    /// <summary>
    /// Author: MaChun
    /// Description: DALTier -- the DAL class of System_Button.
    /// Datetime:2015/6/8 13:00:35
    /// </summary>
    public class  BaseSystem_ButtonRepository: IBaseRepository<System_ButtonModel>
    {
    
        //创建企业库连接
     public  SqlDataAccess db = SqlDataAccess.CreateDataAccess();
    
        #region 新增一条记录 Add(System_ButtonModel model)
        /// <summary>
        /// 新增一条记录
        /// </summary>
        public int Add(System_ButtonModel  model)
        {
            int result = 0;
            try
            {

                StringBuilder strSql = new StringBuilder();
                strSql.Append("insert into System_Button(");
                strSql.Append("SBT_Guid,SBT_ButtonCode,SBT_ButtonName,SBT_IconUrl,SBT_IconCss,SBT_CreateBy,SBT_CreatedDate)");
                strSql.Append(" values (");
                strSql.Append("@SBT_Guid,@SBT_ButtonCode,@SBT_ButtonName,@SBT_IconUrl,@SBT_IconCss,@SBT_CreateBy,@SBT_CreatedDate)");
                strSql.Append(";select @@IDENTITY");
                SqlParameter[] parameters = {
                    new SqlParameter("@SBT_Guid", SqlDbType.VarChar,36),
                    new SqlParameter("@SBT_ButtonCode", SqlDbType.VarChar,200),
                    new SqlParameter("@SBT_ButtonName", SqlDbType.VarChar,100),
                    new SqlParameter("@SBT_IconUrl", SqlDbType.VarChar,100),
                    new SqlParameter("@SBT_IconCss", SqlDbType.VarChar,100),
                    new SqlParameter("@SBT_CreateBy", SqlDbType.VarChar,100),
                    new SqlParameter("@SBT_CreatedDate", SqlDbType.DateTime,8)};
                                parameters[0].Value = model.SBTGuid;
                parameters[1].Value = model.SBTButtonCode;
                parameters[2].Value = model.SBTButtonName;
                parameters[3].Value = model.SBTIconUrl;
                parameters[4].Value = model.SBTIconCss;
                parameters[5].Value = model.SBTCreateBy;
                parameters[6].Value = model.SBTCreatedDate;
                
                result =   db.ExecuteNonQuery(strSql.ToString(), parameters);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return result;
        }
        #endregion 

        #region 逻辑删除 UpdateDel(string ids, bool isDel)
        /// <summary>
        /// 逻辑删除
        /// </summary>
        public int UpdateDel(string ids, bool isDel)
        {
          
           
            StringBuilder strSql = new StringBuilder();
            strSql.Append("update System_Button set noDelKey='" + isDel.ToString() + "' where SBT_Guid in (" + ids + ")");
         
            return   db.ExecuteNonQuery(strSql.ToString());
        }
        #endregion
        
        #region 物理删除 Del(string id)
        /// <summary>
        /// 物理删除
        /// </summary>
        public int Del(string id)
        {
   
            StringBuilder strSql = new StringBuilder();
            strSql.Append("delete System_Button where SBT_Guid ='"+id+"' ");
            return  db.ExecuteNonQuery(strSql.ToString());
        }
        #endregion 

        #region 修改记录 Update(System_ButtonModel model)
        /// <summary>
        /// 修改记录
        /// </summary>
        public int Update(System_ButtonModel model)
        {
            int res = -2;
            StringBuilder strSql = new StringBuilder();
            strSql.Append("update System_Button set ");
                        strSql.Append("SBT_ButtonCode=@SBT_ButtonCode,");
            strSql.Append("SBT_ButtonName=@SBT_ButtonName,");
            strSql.Append("SBT_IconUrl=@SBT_IconUrl,");
            strSql.Append("SBT_IconCss=@SBT_IconCss,");
            strSql.Append("SBT_CreateBy=@SBT_CreateBy,");
            strSql.Append("SBT_CreatedDate=@SBT_CreatedDate");
            strSql.Append(" where SBT_Guid=@SBT_Guid ");
            SqlParameter[] parameters = {
                    new SqlParameter("@SBT_Guid", SqlDbType.VarChar,36),
                    new SqlParameter("@SBT_ButtonCode", SqlDbType.VarChar,200),
                    new SqlParameter("@SBT_ButtonName", SqlDbType.VarChar,100),
                    new SqlParameter("@SBT_IconUrl", SqlDbType.VarChar,100),
                    new SqlParameter("@SBT_IconCss", SqlDbType.VarChar,100),
                    new SqlParameter("@SBT_CreateBy", SqlDbType.VarChar,100),
                    new SqlParameter("@SBT_CreatedDate", SqlDbType.DateTime,8)};
                            parameters[0].Value = model.SBTGuid;
                parameters[1].Value = model.SBTButtonCode;
                parameters[2].Value = model.SBTButtonName;
                parameters[3].Value = model.SBTIconUrl;
                parameters[4].Value = model.SBTIconCss;
                parameters[5].Value = model.SBTCreateBy;
                parameters[6].Value = model.SBTCreatedDate;

            try
            {
                res = db.ExecuteNonQuery(strSql.ToString(), parameters);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally {  }
            return res;
        }
        #endregion 
        
        #region  根据主键查询出DataSet GetModel(string Id)
        /// <summary>
        /// 根据主键查询出DataSet
        /// </summary>
        public DataSet GetModel(string Id)
        {
           
           
            StringBuilder strSql = new StringBuilder();
            strSql.Append("select SBT_Guid,SBT_ButtonCode,SBT_ButtonName,SBT_IconUrl,SBT_IconCss,SBT_CreateBy,SBT_CreatedDate from System_Button ");
            strSql.Append(" where SBT_Guid=@SBT_Guid ");
            SqlParameter[] parameters = {
                    new SqlParameter("@SBT_Guid", SqlDbType.Char,36)};
            parameters[0].Value = Id;
         
            DataSet ds =  db.ExecuteDataSet(strSql.ToString(), parameters);
         
            return ds;
        }

        #endregion
        
        #region 根据where 条件查询出DataSet  GetList(string strWhere)
        /// <summary>
        /// 根据where 条件查询出DataSet
        /// </summary>
        public DataSet GetList(string strWhere)
        {
           
           
            StringBuilder strSql = new StringBuilder();
            strSql.Append("select SBT_Guid,SBT_ButtonCode,SBT_ButtonName,SBT_IconUrl,SBT_IconCss,SBT_CreateBy,SBT_CreatedDate ");
            strSql.Append(" FROM System_Button ");
            if (strWhere.Trim() != "")
            {
                strSql.Append(" where " + strWhere);
            }
            
            DataSet ds = db.ExecuteDataSet(strSql.ToString());
            
            return ds;
        }
        #endregion

        #region 根据存储过程查询出DataSet GetListByProc(string procName,SqlParameter[] paras)
        /// <summary>
        ///根据存储过程查询出DataSet
        /// </summary>
        /// <param name="procName">procName</param>
        /// <param name="paras">paras</param>
        public DataSet GetListByProc(string procName,SqlParameter[] paras)
        {

            DataSet ds =  db.ExecuteDataSet(procName, paras);
           
            return ds;
        }
        #endregion


        
    }
}
View Code
namespace MCFramework.SQLDAL
{
    /// <summary>
    /// Author: MaChun
    /// Description: DALTier -- the DAL class of System_Button.
    /// Datetime:2015/4/20 16:01:42
    /// </summary>
    public class System_ButtonRepository:BaseSystem_ButtonRepository,ISystem_ButtonRepository
    {


        public bool IsExit(string ButtonCode)
        {
            bool flag = false;
            string sql = string.Format(@"SELECT * FROM dbo.System_Button WHERE SBT_ButtonCode='{0}'", ButtonCode);
            DataSet ds = db.ExecuteDataSet(sql);
            if (ds.Tables.Count > 0)
            {
                flag = ds.Tables[0].Rows.Count > 0;
            }

            return flag;
        }
    }
}
View Code

数据访问层操作数据库我没有用ORM框架,用的是传统的ADO.Net,我个人觉得这样可控性会更好,也利于以后的优化。

业务逻辑层的调用:

namespace MCFramework.BLL
{
    /// <summary>
    /// Author: MaChun
    /// Description: BLLTier -- the BLL class of System_Button.
    /// Datetime:2015/5/6 17:00:35
    /// </summary>
    public  class  BaseSystem_ButtonService
    {
        protected  static readonly ISystem_ButtonRepository  dal = RepositoryFactory.RepositoryFactory.System_ButtonRepository;
      
        
            #region 新增一条记录 Add(System_ButtonModel model)
        /// <summary>
        /// 新增一条记录
        /// </summary>
        public int Add(System_ButtonModel model)
        {
            return dal.Add(model);
        }
        #endregion
        
            #region 根据ID 逻辑删除一条记录 UpdateDel(string ids, bool isDel)
        /// <summary>
        /// 根据ID 逻辑删除一条记录
        /// </summary>
        public int UpdateDel(string ids, bool isDel)
        {
            return dal.UpdateDel(ids, isDel);
        }
        #endregion
        
          #region 根据ID物理删除一条记录 Del(string id)
        /// <summary>
        /// 根据ID物理删除一条记录
        /// </summary>
        public int Del(string id)
        {
            return dal.Del(id);
        }
        #endregion

            #region 更新一条记录 Update(System_ButtonModel model)
            /// <summary>
            /// 更新一条记录
            /// </summary>
            public int Update(System_ButtonModel model)
            {
                return dal.Update(model);
            }
            #endregion
            
            #region 根据存储过程来查询返回一个list 集合 GetListByProc
            /// <summary>
            ///根据存储过程来查询返回一个list 集合
            /// </summary>
            /// <param name="procName">procName</param>
            /// <param name="paras">paras</param>
            public List<System_ButtonModel> GetListByProc(string procName,System.Data.SqlClient.SqlParameter[] paras)
            {
                     DataTable dt=  dal.GetListByProc(procName,paras).Tables[0];
                     
                     List<System_ButtonModel> list = null;
                     list = new List<System_ButtonModel>();
                     LoadListData(ref list, dt);
                    if (list.Count > 0)
                        {
                          return list;
                        }
                        else
                        { 
                          return null;
                        }
            }
            #endregion
            
            #region  根据主键来查询得到一个实体 GetModel(string Id)
            /// <summary>
            /// 根据主键来查询得到一个实体
            /// </summary>
            public System_ButtonModel GetModel(string Id)
            {
                 
                    DataTable dt= dal.GetModel(Id).Tables[0];
                    
                   System_ButtonModel model = new System_ButtonModel();
                  if (dt.Rows.Count > 0)
                    {
                        LoadEntityData(ref model, dt.Rows[0]);
                        return model;
                    }
                    else
                    {
                        return null;
                    }
            }
            #endregion
            
            #region  根据 where 条件来查询得到一个list集合 GetList(string strWhere)
            /// <summary>
            /// 根据 where 条件来查询得到一个list集合
            /// </summary>
            public List<System_ButtonModel> GetList(string strWhere)
            {
                    DataTable dt= dal.GetList(strWhere).Tables[0];
                    
                   List<System_ButtonModel> list = null;
                   list = new List<System_ButtonModel>();
                   LoadListData(ref list, dt);   
                    if (list.Count > 0)
                        {
                          return list;
                        }
                        else
                        {
                          return null;
                        }
                
            }
            #endregion
        
       
       
       
            #region 将DataTable转换为 List 对象集合  LoadListData
        /// <summary>
        ///  将DataTable转换为 List 对象集合
        /// </summary>
        /// <param name="list">GenericList</param>
        /// <param name="dt">DataTable</param>
        public void LoadListData(ref List<System_ButtonModel> list, DataTable dt)
        {
            if (dt.Rows.Count > 0)
            {
                System_ButtonModel model;
                foreach (DataRow dr in dt.Rows)
                {
                    model = new System_ButtonModel();
                    LoadEntityData(ref model, dr);
                    list.Add(model);
                }
            }
        }
        #endregion
        
            #region 将Datatable中的Dr 转换成 Model 实体  LoadEntityData
        /// <summary>
        /// 将Datatable中的Dr 转换成 Model 实体
        /// </summary>
        /// <param name="model">Entity</param>
        /// <param name="dr">DataRow</param>
        public void LoadEntityData(ref System_ButtonModel model, DataRow dr)
        {
                        model.SBTGuid = dr["SBT_Guid"].ToString();
            model.SBTButtonCode = dr["SBT_ButtonCode"].ToString();
            model.SBTButtonName = dr["SBT_ButtonName"].ToString();
            model.SBTIconUrl = dr["SBT_IconUrl"].ToString();
            model.SBTIconCss = dr["SBT_IconCss"].ToString();
            model.SBTCreateBy = dr["SBT_CreateBy"].ToString();
            if (dr["SBT_CreatedDate"].ToString() != "")
            {
                model.SBTCreatedDate = DateTime.Parse(dr["SBT_CreatedDate"].ToString());
            }

        }
        #endregion
        
       
    }
}
View Code
 public  class System_ButtonService:BaseSystem_ButtonService
    {
      
        #region 单例模式

        private System_ButtonService()
        {
        }

        private static readonly System_ButtonService instance = new System_ButtonService();

        public static System_ButtonService Instance
        {
            get { return instance; }
        }

        #endregion





        public bool IsExit(string ButtonCode)
        {

            return dal.IsExit(ButtonCode);
        }
    }
}
View Code

对于,业务层到UI层之间的解耦,我没有按架构图中的去实现,我觉在一般中的项目中没有这个必要,为了简化 ,我所以省去了,如果你想加上,完全可以按抽象工厂的方式加上。以上就是我对整个项目的架构上做的一个说明。接下来我们再看看我数据库的设计。

 

   权限的细化颗粒度到各页面的按钮。对于以上的架构和表设计,如果有什么好的建议可以给我留言。下一节,我们将完成主页面的主休框架和登录验证。

posted @ 2015-06-08 14:11 孤星缀月 阅读(3626) 评论(7) 推荐(9) 编辑
摘要: 前言: 当今是互联网的时代,我们己经阻止不了它的发展了,只有跟上脚步,才不会被抛弃,松散了这么久,该紧紧了。背景: 我之所以说以一个权限应用系统来告别我的WebForm内部系统的生涯,是缘于我自从步入码农这个圈子,我就一直做的是各个企业的内部办公系统。几年下来发送互联网脱轨了,新生的技术,知道的... 阅读全文
posted @ 2015-06-04 16:38 孤星缀月 阅读(1878) 评论(8) 推荐(1) 编辑
摘要: 一.什么是Asp.Net页面生命周期当我们在浏览器地址栏中输入网址,回车查看页面时,这时会向服务器端(IIS)发送一个request请求,服务器就会判断发送过来的请求页面, 完全识别 HTTP 页面处理程序类后,ASP.NET 运行时将调用处理程序的 ProcessRequest 方法来处理请求,来创建页面对象。通常情况下,无需更改此方法的实现,因为它是由 Page 类提供的。接下来被创建页面对象的ProcessRequest方法使页面经历了各个阶段:初始化、加载视图状态信息和回发数据、加载页面的用户代码以及执行回发服务器端事件。之后,页面进入显示模式:收集更新的视图状态,生成 HTML 代码 阅读全文
posted @ 2012-05-20 14:38 孤星缀月 阅读(58635) 评论(38) 推荐(93) 编辑
摘要: CP HTTP UDP:都是通信协议,也就是通信时所遵守的规则,只有双方按照这个规则“说话”,对方才能理解或为之服务。TCP HTTP UDP三者的关系:TCP/IP是个协议组,可分为四个层次:网络接口层、网络层、传输层和应用层。在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。在传输层中有TCP协议与UDP协议。在应用层有FTP、HTTP、TELNET、SMTP、DNS等协议。因此,HTTP本身就是一个协议,是从Web服务器传输超文本到本地浏览器的传送协议。socket:这是为了实现以上的通信过程而建立成来的通信管道,其真实的代表是客户端和服务器端的一个通信... 阅读全文
posted @ 2012-03-03 14:21 孤星缀月 阅读(31918) 评论(11) 推荐(7) 编辑
摘要: 学习任何东西,我们只要搞清楚其原理,就会触类旁通。现在结和我所学,我想总结一下客户端到服务器端的通信过程。只有明白了原理,我们才会明白当我们程序开发过程中错误的问题会出现在那,才会更好的解决问题。 我们首先要了解一个概念性的词汇:Socket socket的英文原义是“孔”或“插座”。作为进程通信机制,取后一种意思。通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄。(其实就是两个程序通信用的。)socket非常类似于电话的插座。以一个电话网为例。电话的通话双方相当于相互通信的2个程序,电话号码可以当作是IP地址。任何用户在通话之前,首先要占有一部电话机,相当于申请一个sock. 阅读全文
posted @ 2012-02-11 16:50 孤星缀月 阅读(10506) 评论(4) 推荐(8) 编辑
摘要: webFrom页是由前台面和后台面组成的也就是(codebehind)代码分离,前台文件(.aspx) 和后台文件(.cs)是不相干的两个类.它们是子父类继承的关系.在前台可以通过<% %>调用后台任何非私有的属性和方法等成员,前台控件注有runat=server的后台都可以调用.前台页面中的<% %>来调用后台c#代码。<%=uesrName %>相当于Response.write(userName).Aspx \.CS\.dll 三者之间的关系webForm前台页面文件(.aspx)在被问时会被编译为dll文件,在dll文件中包含了两个类文件:前台页面类 阅读全文
posted @ 2012-02-04 16:37 孤星缀月 阅读(801) 评论(0) 推荐(2) 编辑
摘要: 文本(字符串)在数据库中的存储可以有那几种数据类型,各类型的区别是什么?数据据库的两个关系表有那几种连接方式?每种方式的区别是什么?,前几日一个面试者来到公司面试,他说数据库比较熟,老大直接抛出了这几个问题。我想这问题也太基础了吧,对于一个熟悉数据库的来说简直小菜一碟,然而结果确出乎我的想像,前一个问题他答错了,后一个问题答的不全。现在我们一起来看一下这两个问题,希望那些概念不清的同学友,抓紧时间打牢基础了,不要在面试的时候,连基本的都答不上来,那就不好玩了。第一个问题:文本(字符串)在数据库中的存储可以有那几种数据类型?各类型的区别是什么? 1、 varchar: 可变长度的非 Unicod 阅读全文
posted @ 2011-12-15 14:57 孤星缀月 阅读(890) 评论(2) 推荐(1) 编辑
摘要: 在上篇《万能报表之数据篇》中我们己经展示了数据是如何写入Excel生成报表的,在本篇中,我们再来看一下,如何在报表中指定位置,插入图片。效果如下图所示:插入图片的方法写到了一个类中,代码如下:View Code 1 public class InsertImage : IDisposable 2 { 3 SpreadsheetDocument spreadSheet; 4 public WorksheetPart CurrentWorksheetPart { get; set; } 5 SharedStringTablePar... 阅读全文
posted @ 2011-12-10 11:07 孤星缀月 阅读(2305) 评论(3) 推荐(6) 编辑
摘要: 对于上篇中提到的报表我们以如下的事例,来说明如何做:报表事例图:模板图:来源数据图:(即为上图中Data2中的数据)生成报表图:现在相信大家己经看出来了,我的报表是在Excel中完成的,我们的报表在项目中都是借助excel来实现的,将生成的报表传到我们的MOSS上 Excel服务器上供用户查看以用下载。借助于excel中的透视表来做,基本上能够满足各种需求。当然对于特别特别复杂的我们可以自己定义想要的模板。完成如上图的报表我们要做的准备工作如下:报表的数据来源: 根据我们报表的展示需求,我们先来确定下呈现报表所需要的数据,写好数据的返回格式,以便于写存储过程从项目中提取我们所需要的数据。如我们 阅读全文
posted @ 2011-12-09 11:32 孤星缀月 阅读(2900) 评论(7) 推荐(2) 编辑
摘要: 进入项目组己经一个多月了,我主要做的是报表这一块,现在报表这一块己步入正轨。在开发报表的过程中,我遇到了很多问题。现在讲讲我遇到的主要技术问题,让大家和我一起来学习一下,以后遇到类似的可以少走一些弯路,早点回家,不毕再奋战到凌晨,还没有进入被窝。 可能有的人一看说报表还不好做,其实不然。对于目前大多数战友来说,我们大部分接触到的报表是非常规距,结构不复杂的,用一般的插件就能直接导出Excel生成报表。而我今天所要讲的是结构复杂,并且加入图片,和图例的综合型报表。首先让我们来看一下我项目中的几个生成的报表。为了保护隐私,我把图中的单位名称涂抹了,照片我也替换了。不过不影响效果。报表事例图:报表. 阅读全文
posted @ 2011-12-08 17:43 孤星缀月 阅读(4799) 评论(22) 推荐(15) 编辑
点击右上角即可分享
微信分享提示