记录一下自己的C#实训课的成果
需要github gitee链接的在最底下.
目标1:理解复杂软件系统的分层架构思想,能运用两层/三层架构设计软件。
目标2:在明确软件功能需求下,能按两层架/三层架构设计特定的软件模块。
目标3:了解复杂软件系统,通过设计实现不同的组件、模块及其关系完成整个软件的开发,并体现创新意识。
目标4:熟练掌握一种集成开发环境,并能运用各类技术、资源解决软件开发中的问题。
目标5:能够撰写报告,清晰表达软件开发中的问题和方案。
ide 我使用的Visual Studio 2015
sql 就是使用vs自带的sql server 2014
登录
添加用户界面
日志查询
日志迁移
员工列表
编辑员工信息
生成工资单
打印工资单
如果你有打印机的话就可以直接打印了.
首先在自己的D盘新建一个文件夹,我这叫做SqlData~~(当然你也可以叫别的,甚至不在d盘,但是需要你在sql文件里重写修改储存地址)~~ 文件夹地址就是D:\SqlData
创建完后具体使用方式就是把sql文件拖到Visual Studio中再点击左上角的运行按键就可以了
脚本和项目文件我会一起放在github上和gitee上,有需要的自取.
首先用windows身份登录数据库,点击安全性,再右键登录名选择添加新登录名
输入,后运行就创建成功了.(可以自己修改用户名和密码,我这里用户名为hrmtest,密码为test)
CREATE LOGIN [hrmtest] WITH PASSWORD = 'test'
进入刚刚创建好的数据库HRMDB数据库中,选择安全性,之后右键用户,添加用户
我们把第二行的WITHOUT LOGIN给删掉,将两处[]内改成我们的用户名hrmtest后,运行
CREATE USER [hrmtest]
WITH DEFAULT_SCHEMA = dbo
GO
GRANT CONNECT TO [hrmtest]
我们右键HRMDB数据库后新建查询
输入后运行,其中把最后一个引号内改成自己的用户名
exec sp_addrolemember 'db_owner', 'hrmtest'
首先右键原有是由windows连接的服务器,断开连接.
点击添加Sql server
填写身份验证
连接完成之后进去HRMDB中看看,如果可以看到表就说明之前的操作都成功了.
右键解决方案,点击添加,点击新建项目,选择类库,创建我们的模型层HRMSystem.Model
在这个类库中,我们添加一个Operator类,作为登录的操作员的信息模型
其中Id的数据类型设置为Guid,这样可以使用Guid来生成一个独一无二的辨识编号
public class Operator
{
public Guid Id { get; set; } //Guid为全局统一标识符,可以生成一个独一无二的id
public string UserName { get; set; }
public string Password { get; set; }
public bool IsDeleted { get; set; }//有无被删除
public string RealName { get; set; }
public bool IsLocked { get; set; }//有无被锁定
}
用来存放操作日志
public class OperationLog
{
public Guid Id { get; set; }
public string ActionDesc { get; set; }
public Guid OperatorId { get; set; }
public DateTime ActionDate { get; set; }
}
用来存放员工的信息
其中Photo的数据类型需要设置为Byte[],要不然不能存储图片,必须用数据流来存储
public class Employee
{
public Guid Id { get; set; }
public string Number { get; set; }
public string Name { get; set; }
public DateTime BirthDay { get; set; }
public DateTime InDay { get; set; }
public Guid MarriageId { get; set; }
public Guid PartyId { get; set; }
public Guid EducationId { get; set; }
public Guid GenderId { get; set; }
public Guid DepartmentId { get; set; }
public string Telephone { get; set; }
public string Address { get; set; }
public string Email { get; set; }
public string Remarks { get; set; }
public string Resume { get; set; }
public Byte[] Photo { get; set; }
public string Nation { get; set; }
public string NativePlace { get; set; }
}
用来存放员工查询时的查询条件信息
public class EmployeeSelect
{
public string Name { get; set; }
public DateTime InDay1 { get; set; }
public DateTime InDay2 { get; set; }
public Guid DepartmentId { get; set; }
public bool TimeCheck { get; set; }
}
用来存放部门的编号和名字信息
public class Department
{
public Guid Id { get; set; }
public string Name { get; set; }
}
存储工资表的信息
public class SalarySheet
{
public Guid Id { get; set; }
public int Year { get; set; }
public int Month { get; set; }
public Guid DepartmentId { get; set; }
}
存储工资单细节信息
public class SalarySheetItem
{
public Guid Id { get; set; }
public Guid SheetId { get; set; }
public Guid EmployeeId { get; set; }
public decimal BaseSalary { get; set; }
public decimal Bonus { get; set; }
public decimal Fine { get; set; }
public decimal Other { get; set; }
}
创建一个类库叫做HRMSystem.DAL,当作数据层,用来对于数据库进行操作.
为了简化connstr,所以我们使用引用来添加.
在数据层中右键引用,添加引用Configuration
然后在HRMSystem2022类库中加上
<connectionStrings>
<add name="connStr" connectionString="Data Source=(localdb)\MSSQLLocalDB;Initial Catalog = HRMDB;Uid =hrmtest;Pwd=test"/>
connectionStrings>
其中用户名和密码要对应
这个类被设置成抽象类用来存放对sql server的操作.这样可以在代码中重复多次的sql语句等进行化简.需要用到对sql访问的时候调用这个类中的静态方法就可以了.
其中包括存放connstr信息的字段,以及返回SqlDataReader的方法,返回Object对象的ExecuteScalar方法,返回受影响记录数的ExecuteNonQuery方法,返回DataTable数据类型的GetDataTable方法,事务处理的Execute方法.
注意,参数里的SqlParameter[]前面要加一个params,表示可以有多个也可以没有,这样可以符合所有查询的条件或者是没有条件.
public class SqlHelper
{
public static string connstr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;
public static SqlDataReader ExecuteReader(string sql, params SqlParameter[] parameters)
{
SqlConnection conn = new SqlConnection(connstr);
SqlCommand comm = new SqlCommand(sql, conn);
comm.Parameters.AddRange(parameters);
conn.Open();
return comm.ExecuteReader(CommandBehavior.CloseConnection);
}
public static Object ExecuteScalar(string sql, params SqlParameter[] parameters)
{
using (SqlConnection conn = new SqlConnection(connstr))
{
SqlCommand comm = new SqlCommand(sql, conn);
comm.Parameters.AddRange(parameters);
conn.Open();
return comm.ExecuteScalar();
}
}
public static int ExecuteNonQuery(string sql, params SqlParameter[] parameters)
{
using (SqlConnection conn = new SqlConnection(connstr))
{
SqlCommand comm = new SqlCommand(sql, conn);
comm.Parameters.AddRange(parameters);
conn.Open();
return comm.ExecuteNonQuery();
}
}
public static DataTable GetDataTable(string sql, params SqlParameter[] parameters)
{
using (SqlConnection conn = new SqlConnection(connstr))
{
DataTable dt = new DataTable();
SqlDataAdapter sda = new SqlDataAdapter(sql, conn);
sda.SelectCommand.Parameters.AddRange(parameters);
sda.Fill(dt);
return dt;
}
}
public static bool Execute(string sql1, string sql2, params SqlParameter[] parameters)
{
using (SqlConnection conn = new SqlConnection(connstr))
{
conn.Open();
SqlTransaction st = null;
SqlCommand comm = null;
try
{
st = conn.BeginTransaction();
comm = new SqlCommand(sql1, conn, st);
comm.CommandText += sql2;
comm.Parameters.AddRange(parameters);
comm.ExecuteNonQuery();
st.Commit();
}
catch (Exception ex)
{
st.Rollback();
return false;
throw ex;
}
return true;
}
}
}
这个类用来写对于操作员的一些对数据库的操作的一些方法.例如添加用户,删除用户等
删除用户的方法,将传入的信息放入SqlParameter中来调用SqlHelper类中的ExecuteNonQuery方法实现删除用户.(此处的删除不是真的在数据库中删除,而是给该用户一个标记,表面这个用户已经被注销了)
public bool DeleteOperator(string UserName)
{
string sql = "UPDATE Operator SET IsDeleted = 1 WHERE @UserName = UserName";
SqlParameter paramUserName = new SqlParameter("@UserName", UserName);
int n = SqlHelper.ExecuteNonQuery(sql, paramUserName);
return n > 0;
}
添加新用户的方法,将传入的信息放入SqlParameter[]中来调用SqlHelper类中的ExecuteNonQuery方法实现插入用户.
public bool InsertOperator(string RealName, string Password, string UserName)
{
string sql = "INSERT INTO Operator(Id, UserName, Password, IsDeleted, RealName, IsLocked) VALUES(NEWID(), @UserName, @Password, 0, @RealName, 0)";
SqlParameter[] parames = {
new SqlParameter("@UserName", UserName),
new SqlParameter("@Password", Password),
new SqlParameter("@RealName", RealName)
};
int n = SqlHelper.ExecuteNonQuery(sql, parames);
return n > 0;
}
锁定用户的方法,将传入的信息放入SqlParameter中来调用SqlHelper类中的ExecuteNonQuery方法实现锁定用户.
public bool LockOperator(string UserName)
{
string sql = "UPDATE Operator SET IsLocked = 1 WHERE @UserName = UserName";
SqlParameter paramUserName = new SqlParameter("@UserName", UserName);
int n = SqlHelper.ExecuteNonQuery(sql, paramUserName);
return n > 0;
}
修改用户的密码的方法,将传入的信息放入SqlParameter中来调用SqlHelper类中的ExecuteNonQuery方法实现修改用户密码.
public void PwdModify(string un, string pwdAfter)
{
string sql = "update Operator set Password = @PasswordAfter where UserName = @UserName";
SqlParameter[] parames = {
new SqlParameter("@PasswordAfter", pwdAfter),
new SqlParameter("@UserName", un)
};
int n = SqlHelper.ExecuteNonQuery(sql, parames);
}
用来通过用户名来调用SqlHelper中的ExecuteReader方法返回当前登录着的用户的信息,存储在op对象中
public Operator GetOperator(string un)
{
Operator op = null;
string sql = "select * from Operator where UserName = @UserName";
SqlParameter paramUserName = new SqlParameter("@UserName", un);
SqlDataReader sdr = SqlHelper.ExecuteReader(sql, paramUserName);
if (sdr.Read())
{
op = new Operator();
op.Id = (Guid)sdr["Id"];
op.UserName = sdr["UserName"].ToString();
op.Password = sdr["Password"].ToString();
op.IsDeleted = (bool)sdr["IsDeleted"];
op.RealName = sdr["RealName"].ToString();
op.IsLocked = (bool)sdr["IsLocked"];
}
sdr.Close();
return op;
}
创建该类用来存放一些对于操作日志进行操作的方法,比如添加日志,获取日志数以及获取日志列表
将操作员的信息通过SqlHelper中的ExecuteNonQuery方法插入到OperationLog数据表中
public bool LogAdd(OperationLog ol)
{
string sql = "insert into OperationLog values(@Id, @OperatorId, @ActionDate, @ActionDesc)";
SqlParameter[] Parameters = {
new SqlParameter("@Id", ol.Id),
new SqlParameter("@OperatorId", ol.OperatorId),
new SqlParameter("@ActionDate", ol.ActionDate),
new SqlParameter("@ActionDesc", ol.ActionDesc)
};
int n = SqlHelper.ExecuteNonQuery(sql, Parameters);
return n > 0;
}
获取日志的数目
public int GetLogCount()
{
string sql = "select count(*) from OperationLog";
int n = SqlHelper.ExecuteNonQuery(sql);
return n;
}
获取日志所有列表的DataTable,然后分页显示,所以这里面的sql语句有点长,大家仔细看看.
public DataTable GetLogList(int pageNo, int numPerPage)
{
string sql = "SELECT Temp.Id AS 编号,Operator.RealName AS 操作员, Temp.ActionDate AS 操作时间, Temp.ActionDesc AS 描述 FROM (SELECT TOP(@LogNum) * FROM OperationLog WHERE Id NOT IN(SELECT TOP(@BeforeNum) Id FROM OperationLog)) AS Temp, Operator WHERE Temp.OperatorId = Operator.Id";
SqlParameter[] parameters = {
new SqlParameter("@LogNum", numPerPage),
new SqlParameter("@BeforeNum", numPerPage * (pageNo - 1))
};
return SqlHelper.GetDataTable(sql, parameters);
}
用来存放对于员工的一些操作,包括获取员工列表,删除员工,插入员工,编辑员工,通过Id查找员工等功能
这个方法有两个重载,一个是无参数的,直接查询所有的员工信息然后输出DataTable
另一个是有一个EmployeeSelect类型的参数,将员工查询的信息输入,然后仅仅查询出符合条件的员工返回DataTable
无参的:
public DataTable GetEmployeeList()
{
string sql = "select Id as 编号, Number as 工号, Name as 姓名, InDay as 入职时间, Nation as 民族, NativePlace as 籍贯 from Employee";
return SqlHelper.GetDataTable(sql);
}
有参的:
这里面需要使用List
最后将条件使用and连接起来.
但要注意!!使用sql语句中参数的时候如果进行模糊查询,那么就不能直接将%加在@Name中,因为这样就识别不出来了,所以需要使用+进行字符串的拼接.例如"Name like ‘%’ + @Name + ‘%’"
这也是我好久都提示错误的原因,哎!
public DataTable GetEmployeeList(EmployeeSelect es)
{
string sql = "select Id as 编号, Number as 工号, Name as 姓名, InDay as 入职时间, Nation as 民族, NativePlace as 籍贯 from Employee";
List<SqlParameter> whereParameters = new List<SqlParameter>();
if(es != null)
{
List<string> whereList = new List<string>();
if (!string.IsNullOrEmpty(es.Name))
{
whereList.Add(string.Format("Name like '%' + @Name + '%'"));
whereParameters.Add(new SqlParameter("@Name", es.Name));
}
if (es.TimeCheck)
{
whereList.Add(string.Format("InDay between @Time1 and @Time2"));
whereParameters.Add(new SqlParameter("@Time1", es.InDay1));
whereParameters.Add(new SqlParameter("@Time2", es.InDay2));
}
if(es.DepartmentId != Guid.Empty)
{
whereList.Add(string.Format("DepartmentId = @Dept"));
whereParameters.Add(new SqlParameter("@Dept", es.DepartmentId));
}
string whereStr = string.Join(" and ", whereList);
if(whereStr != null && whereStr.Length > 0)
{
sql += " where " + whereStr;
}
}
return SqlHelper.GetDataTable(sql, whereParameters.ToArray());
}
添加新员工的方法,将传入的emp对象中的信息都作为参数传入SqlHelper中调用ExecuteNonQuery方法来实现往数据库中添加新记录.
public bool InsertEmployee(Employee emp)
{
string sql = "insert into Employee values(@Id, @Number, @Name, @BirthDay, @InDay, @MarriageId, @PartyId, @EducationId, @GenderId, @DepartmentId, @Telephone, @Address, @Email, @Remarks, @Resume, @Photo, @Nation, @NativaPalce)";
SqlParameter[] Parameter = {
new SqlParameter("@Id", emp.Id),
new SqlParameter("@Number", emp.Number),
new SqlParameter("@Name", emp.Name),
new SqlParameter("@BirthDay", emp.BirthDay),
new SqlParameter("@InDay", emp.InDay),
new SqlParameter("@MarriageId", emp.MarriageId),
new SqlParameter("@PartyId", emp.PartyId),
new SqlParameter("@EducationId", emp.EducationId),
new SqlParameter("@GenderId", emp.GenderId),
new SqlParameter("@DepartmentId", emp.DepartmentId),
new SqlParameter("@Telephone", emp.Telephone),
new SqlParameter("@Address", emp.Address),
new SqlParameter("@Email", emp.Email),
new SqlParameter("@Remarks", emp.Remarks),
new SqlParameter("@Resume", emp.Resume),
new SqlParameter("@Photo", emp.Photo),
new SqlParameter("@Nation", emp.Nation),
new SqlParameter("@NativaPalce", emp.NativePlace),
};
return SqlHelper.ExecuteNonQuery(sql, Parameter) > 0;
}
通过传入的Id来查找到对应的员工信息,存到emp对象中然后返回该对象.
public Employee GetEmpById(Guid id)
{
string sql = "select * from Employee where Id = @id";
SqlParameter para = new SqlParameter("@id", id);
Employee emp = new Employee();
SqlDataReader sdr = SqlHelper.ExecuteReader(sql, para);
while (sdr.Read())
{
emp.Name = sdr["Name"].ToString();
emp.Address = sdr["Address"].ToString();
emp.Telephone = sdr["Telephone"].ToString();
emp.Resume = sdr["Resume"].ToString();
emp.Remarks = sdr["Remarks"].ToString();
emp.Photo = (byte[])sdr["Photo"];
emp.PartyId = (Guid)sdr["PartyId"];
emp.Number = sdr["Number"].ToString();
emp.NativePlace = sdr["NativePlace"].ToString();
emp.Nation = sdr["Nation"].ToString();
emp.MarriageId = (Guid)sdr["MarriageId"];
emp.InDay = (DateTime)sdr["InDay"];
emp.Email = sdr["Email"].ToString();
emp.EducationId = (Guid)sdr["EducationId"];
emp.DepartmentId = (Guid)sdr["DepartmentId"];
emp.BirthDay = (DateTime)sdr["BirthDay"];
emp.GenderId = (Guid)sdr["GenderId"];
}
return emp;
}
通过传入的员工Id来把该对应的员工给从数据库中删除.
public bool deleteEmployeeById(Guid id)
{
string sql = "delete from Employee where Id = @id";
SqlParameter param = new SqlParameter("@id", id);
if(SqlHelper.ExecuteNonQuery(sql, param) > 0)
{
return true;
}
else
{
return false;
}
}
该方法需要传入两个值,一个存有编辑以后员工的全部信息Employee对象,另一个员工的Id
public bool updateEmployee(Employee emp, Guid id)
{
string sql = "update Employee set Number = @Number, Name = @Name, BirthDay = @BirthDay, InDay = @InDay, MarriageId = @MarriageId, PartyId = @PartyId, EducationId = @EducationId, GenderId = @GenderId, DepartmentId = @DepartmentId, Telephone = @Telephone, Address = @Address, Email = @Email, Remarks = @Remarks, Resume = @Resume, Photo = @Photo, Nation = @Nation, NativePlace = @NativaPalce where Id = @Id";
SqlParameter[] Parameter = {
new SqlParameter("@Id", id),
new SqlParameter("@Number", emp.Number),
new SqlParameter("@Name", emp.Name),
new SqlParameter("@BirthDay", emp.BirthDay),
new SqlParameter("@InDay", emp.InDay),
new SqlParameter("@MarriageId", emp.MarriageId),
new SqlParameter("@PartyId", emp.PartyId),
new SqlParameter("@EducationId", emp.EducationId),
new SqlParameter("@GenderId", emp.GenderId),
new SqlParameter("@DepartmentId", emp.DepartmentId),
new SqlParameter("@Telephone", emp.Telephone),
new SqlParameter("@Address", emp.Address),
new SqlParameter("@Email", emp.Email),
new SqlParameter("@Remarks", emp.Remarks),
new SqlParameter("@Resume", emp.Resume),
new SqlParameter("@Photo", emp.Photo),
new SqlParameter("@Nation", emp.Nation),
new SqlParameter("@NativaPalce", emp.NativePlace),
};
return SqlHelper.ExecuteNonQuery(sql, Parameter) > 0;
}
这个类用来存放获取Dictionary表中的数据方法,应用于赋值给comboBox的功能.
获取Dictionary表中的信息,返回DataTable数据类型
public DataTable GetComboList(string para)
{
string sql = "select * from Dictionary where Category = @para";
SqlParameter param = new SqlParameter("@para", para);
return SqlHelper.GetDataTable(sql, param);
}
用于存放对部门进行操作的一些方法,包括添加部门,删除部门以及获取部门列表功能
添加新部门到数据库中
public bool InsertDepartment(string name)
{
string sql = "INSERT INTO Department(Id, Name) VALUES(NEWID(), @Name)";
SqlParameter paramName = new SqlParameter("@Name", name);
int n = SqlHelper.ExecuteNonQuery(sql, paramName);
return n > 0;
}
从数据库中删除对应的部门信息
public bool deleteDepartment(string name)
{
string sql = "delete from Department where Name = @Name";
SqlParameter paramName = new SqlParameter("@Name", name);
int n = SqlHelper.ExecuteNonQuery(sql, paramName);
return n > 0;
}
获取所有的部门信息
public DataTable GetDeptList()
{
string sql = "select Id as 编号, Name as 部门名 from Department";
return SqlHelper.GetDataTable(sql);
}
用来导出excel文件
首先进行判断,如果文件存储的地址为空时返回
使用SqlHelper.GetDataTable方法来把需要导出的信息查询出来存入DateTable中
然后我们需要用到NPOI相关的东西,所以需要先进行引用以下这五个,大家可以自行下载或者我文件也会发在gitbub和gitee上可以自取
导入方式和其他的一样,在数据层的引用右键添加引用然后浏览对应目录再勾选上五个就可以了
创建IWorkbook对象和我们的表对象ISheet,可以自己命名,然后再统计信息的行列分别有多少再创建对应的行列就可以了
格式问题可以将日期形式和编号的列宽设置长一点,修改sheet的SetColumnWidth属性
为了美观再把表头的文字给居中
为了显示日期更准确,还可以重新设置一下日期的格式
最后再通过FileStream写入磁盘中完成导出Excel
public bool CreateExcel(string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
return false;
}
string sql = "select Id as 编号, Number as 工号, Name as 姓名, InDay as 入职时间, Nation as 民族, NativePlace as 籍贯 from Employee";
DataTable dt = SqlHelper.GetDataTable(sql);
IWorkbook workbook = new HSSFWorkbook();
ISheet sheet = workbook.CreateSheet("员工列表");
sheet.SetColumnWidth(0, 38 * 256);
sheet.SetColumnWidth(3, 30 * 256);
int rowCount = dt.Rows.Count;
int columnCount = dt.Columns.Count;
IRow headerRow = sheet.CreateRow(0);
IFont headerFont = workbook.CreateFont();
ICellStyle headerStyle = workbook.CreateCellStyle();
headerFont.FontName = "宋体";
headerStyle.SetFont(headerFont);
headerStyle.Alignment = HorizontalAlignment.CENTER;
for (int j = 0; j < columnCount; j++)
{
ICell cell = headerRow.CreateCell(j);
cell.CellStyle = headerStyle;
cell.SetCellValue(dt.Columns[j].ColumnName);
}
for (int i = 1; i <= rowCount; i++)
{
IRow row = sheet.CreateRow(i);
for (int j = 0; j < columnCount; j++)
{
ICell cell = row.CreateCell(j);
if (j == 3)
{
cell.SetCellValue((DateTime)dt.Rows[i - 1][j]);
ICellStyle cellStyle = workbook.CreateCellStyle();
cellStyle.DataFormat = workbook.CreateDataFormat().GetFormat("yyyy年m月d日 hh时mm分ss秒");
cell.CellStyle = cellStyle;
}
else
{
cell.SetCellValue(dt.Rows[i - 1][j].ToString());
}
}
}
using (FileStream fs = new FileStream(filePath, FileMode.Create))
{
workbook.Write(fs);
}
return true;
}
存放一些对工资单的操作方法
根据年月和部门来通过SqlHelper.ExecuteScalar获取对应的工资单ID,如果没有找到就返回Guid.Empty.
public Guid GetSalarySheetId(int month, int year, Guid departmentId)
{
string sql = "select Id from SalarySheet where Year = @year and Month = @month and DepartmentId = @deptId";
SqlParameter[] para = {
new SqlParameter("@year", year),
new SqlParameter("@month", month),
new SqlParameter("@deptId", departmentId),
};
Object Id = SqlHelper.ExecuteScalar(sql, para);
if (Id != null)
{
return (Guid)Id;
}
else
{
return Guid.Empty;
}
}
新建新的工资表,并返回对应的工资单的编号
public Guid BuildNewSalarySheet(SalarySheet sheet)
{
string sql = "insert into SalarySheet(Id, Year, Month, DepartmentId) values(@Id, @Year, @Month, @DeptId)";
SqlParameter[] para =
{
new SqlParameter("@Id", sheet.Id),
new SqlParameter("@Year", sheet.Year),
new SqlParameter("@Month", sheet.Month),
new SqlParameter("@DeptId", sheet.DepartmentId),
};
SqlHelper.ExecuteNonQuery(sql, para);
return sheet.Id;
}
存放一些工资表的详细信息的操作方法
新建新的工资单详细信息
使用sql1来找到对应的员工id,再使用sql2来把员工信息和初始化的工资信息存入数据库中.
public void BuildSalarySheetItems(Guid sheetId, Guid deptId)
{
string sql1 = "select Id from Employee where DepartmentId = @deptId";
string sql2 = "insert into SalarySheetItem(Id, SheetId, EmployeeId, BaseSalary, Bonus, Fine, Other) values(@Id, @SheetId, @EmployeeId, 0, 0, 0, 0)";
SqlParameter parameter = new SqlParameter("@deptId", deptId);
SqlDataReader sdr = SqlHelper.ExecuteReader(sql1, parameter);
while (sdr.Read())
{
SqlParameter[] para = new SqlParameter[]
{
new SqlParameter("@Id", Guid.NewGuid()),
new SqlParameter("@SheetId", sheetId),
new SqlParameter("@EmployeeId", sdr["Id"]),
};
SqlHelper.ExecuteNonQuery(sql2, para);
}
}
通过sql查询语句将数据库中的工资单详细信息找出来并且将EmployeeId变成对应的员工姓名,返回一个DataTable用来传给界面中显示.
public DataTable GetSalarySheetItems(Guid sheetId)
{
string sql = "select SalarySheetItem.Id as 编号, Employee.Name as 姓名, SalarySheetItem.BaseSalary as 基本工资, SalarySheetItem.Bonus as 奖金, SalarySheetItem.Fine as 罚款, SalarySheetItem.Other as 其他 from SalarySheetItem, Employee where SheetId = @sheetId and SalarySheetItem.EmployeeId = Employee.Id";
SqlParameter para = new SqlParameter("@sheetId", sheetId);
return SqlHelper.GetDataTable(sql, para);
}
重新生成新的工资单信息,将工资信息都初始化为0
public void ReBuildSalarySheetItem(Guid sheetId)
{
string sql = "update SalarySheetItem set BaseSalary = 0, Bonus = 0, Fine = 0, Other = 0 where SheetId = @sheetId";
SqlParameter para = new SqlParameter("@sheetId", sheetId);
SqlHelper.ExecuteNonQuery(sql, para);
}
将被修改的工资信息在数据库中更新
public void SaveSheetItem(SalarySheetItem sheetitem)
{
string sql = "update SalarySheetItem set BaseSalary = @base, Bonus = @bonus, Fine = @fine, Other = @other where Id = @id";
SqlParameter[] para = new SqlParameter[]
{
new SqlParameter("@base", sheetitem.BaseSalary),
new SqlParameter("@bonus", sheetitem.Bonus),
new SqlParameter("@fine", sheetitem.Fine),
new SqlParameter("@other", sheetitem.Other),
new SqlParameter("@id", sheetitem.Id),
};
SqlHelper.ExecuteNonQuery(sql, para);
}
返回一个报表的DataTable
public DataTable GetReportSheet(Guid sheetId)
{
string sql = "select SalarySheet.Year as year,SalarySheet.Month as month,Department.Name as dept,SalarySheetItem.Id ,Employee.Name as name ,BaseSalary as baseSalary ,Bonus as bonus,Fine as fine ,Other as other from SalarySheetItem ,Employee,SalarySheet ,Department where SheetId=@SheetId and SalarySheetItem.EmployeeId=Employee.Id and SalarySheetItem.SheetId=SalarySheet.Id and SalarySheet.DepartmentId=Department.Id";
SqlParameter para = new SqlParameter("@SheetId", sheetId);
return SqlHelper.GetDataTable(sql, para);
}
创建HRMSystem.BLL类库当作业务逻辑层
新建一个类叫SystemGuard,用来进行登录用户的身份判断
包含一个check方法和一个枚举类型用来存放不同的身份
check方法中调用OperatorService中的GetOperator方法获取操作员的信息,然后进行判断,从而返回不同的枚举类型,从而实现对于不同的登录信息做出不同的响应.
public class SystemGuard
{
public UserType check(string un, string pwd)
{
OperatorService opSev = new OperatorService();
Operator op = opSev.GetOperator(un);
if (op == null) return UserType.noUser;
else if (op.Password != pwd) return UserType.pwdError;
else if (op.UserName == "admin") return UserType.admin;
else if (op.IsLocked) return UserType.locked;
else if (op.IsDeleted) return UserType.deleted;
else return UserType.validUser;
}
public enum UserType { validUser, pwdError, noUser, locked, deleted, admin};
}
这个类是使用单例模式用来对于已经登录的用户进行信息的存放和读取,便于其他功能调用.
其中InitMember方法是将登录的用户信息存入op对象中
GetInstance方法用来在其他地方获取到当前用户的信息.
public class LoginUser
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public bool IsDeleted { get; set; }
public string RealName { get; set; }
public bool IsLocked { get; set; }
private static LoginUser lu = null;
private LoginUser()
{
}
public static LoginUser GetInstance()
{
if(lu == null)
{
lu = new LoginUser();
}
return lu;
}
public void InitMember(string un)
{
OperatorService opSev = new OperatorService();
Operator op = opSev.GetOperator(un);
if(op != null)
{
lu.Id = op.Id;
lu.RealName = op.RealName;
lu.Password = op.Password;
lu.UserName = op.UserName;
lu.IsDeleted = op.IsDeleted;
lu.IsLocked = op.IsLocked;
}
}
}
用来进行一些是否为空的判定
isSheetExist方法用来判断是否存在该Sheet工资单
isEmployeeExist方法用来判断在对应部门里是否存在员工
注意:当使用ExecuteScalar来查询count(*)的时候如果没有查询到记录返回的将是-1,而不是0
public bool isSheetExist(int month, int year, Guid deptId)
{
string sql = "select count(*) from SalarySheet where Year = @year and Month = @month and DepartmentId = @deptId";
SqlParameter[] para = {
new SqlParameter("@year", year),
new SqlParameter("@month", month),
new SqlParameter("@deptId", deptId),
};
if ((int)SqlHelper.ExecuteScalar(sql, para) > 0)
{
return true;
}
else
{
return false;
}
}
public bool isEmployeeExist(Guid depId)
{
string sql = "select count(*) from Employee where DepartmentId = @depId";
SqlParameter para = new SqlParameter("@depId", depId);
return (int)SqlHelper.ExecuteScalar(sql, para) > 0;
}
三层架构中要使用引用来把几个不同的层给连接起来,我们需要建立引用
右键解决方案里的引用
其中界面层引用业务逻辑层模型层和数据层
数据层不用引用
逻辑层引用数据层
因为直接把用户密码明文存储在数据库中太危险了,所以这里使用MD5进行加密.登陆时把外界输入的密码转化成MD5的形式然后与数据库中的MD5形式的密码进行比较,如果一致就登录成功.添加新用户,修改密码时,把新用户的密码通过本方法转化为MD5然后存入数据库中;
public static string GetMd5(string str)
{
MD5 md5 = MD5.Create();
byte[] bytes = Encoding.UTF8.GetBytes(str);
byte[] md5Bytes = md5.ComputeHash(bytes);
StringBuilder sb = new StringBuilder();
foreach(byte b in md5Bytes)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
因为提示成功信息窗口需要被用到很多次,所以每次都要写的话太麻烦了,而且代码重复度也会很高,所以直接封装在CommonHelper类中,下次需要用到的时候直接调用该方法就可以了
public static void ShowSuccMessageBox(string msg)
{
MessageBox.Show(msg, "成功提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
同上
public static void ShowErrorMessageBox(string msg)
{
MessageBox.Show(msg, "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
新建窗体FormLogin
我们首先在界面层也就是我这里的HRMSystem2022,里添加一个Windows窗体
将对应的控件都照上面排列好
为了方便我们测试登录,所以把用户名和密码都直接默认好.在textBox中的text属性里输入用户名为admin,密码为123 (我们数据库中的管理员账户密码就是这个)
为了隐藏密码,所以我们把密码textBox的PasswordChar属性设置成*,达到隐藏的目的
为了更严谨,避免用户把登录窗体放大而出现不美观的情况,我们把登录窗体的最大化给禁用,MaximizeBox属性设置为False
因为每一次登录都需要产生日志,所以我们创建一个ol对象用来存放日志的信息.
首先将当前时间赋值给日志的时间字段,将使用Guid的NewGuid方法获得的随机的新Id赋值给日志的编号字段.
然后通过逻辑业务层的SystemGuard类中的check方法来进行判断当前登录的用户是否登录成功以及它的状态是什么.
对于不同的状态调用不同的提示窗口,同时将该操作的描述以及操作员的id都给存入日志对象中.随后将该日志对象的信息插入到OperationLog表中.即完成一次成功的登录操作.
private void buttonLogin_Click(object sender, EventArgs e)
{
OperationLog ol = new OperationLog();
ol.ActionDate = DateTime.Now;
ol.Id = Guid.NewGuid();
SystemGuard sg = new SystemGuard();
UserType ut = sg.check(textBoxUserName.Text, CommonHelper.GetMd5(textBoxPassword.Text));
if (ut == UserType.noUser)
{
ol.ActionDesc = "登录用户名不存在!";
ol.OperatorId = Guid.Empty;
CommonHelper.ShowErrorMessageBox("用户名错误!");
this.DialogResult = DialogResult.Cancel;
}
else if (ut == UserType.pwdError)
{
ol.ActionDesc = string.Format("用户{0}尝试使用错误密码{1}登录失败", textBoxUserName.Text, textBoxPassword.Text);
ol.OperatorId = Guid.Empty;
CommonHelper.ShowErrorMessageBox("密码错误");
this.DialogResult = DialogResult.Cancel;
}
else if (ut == UserType.deleted)
{
LoginUser lu = LoginUser.GetInstance();
lu.InitMember(textBoxUserName.Text);
ol.ActionDesc = "用户登录成功!";
ol.OperatorId = lu.Id;
CommonHelper.ShowErrorMessageBox("该用户已注销!");
this.DialogResult = DialogResult.Cancel;
}
else if (ut == UserType.locked)
{
LoginUser lu = LoginUser.GetInstance();
lu.InitMember(textBoxUserName.Text);
ol.ActionDesc = "用户登录成功!";
ol.OperatorId = lu.Id;
CommonHelper.ShowErrorMessageBox("该用户已被冻结!");
this.DialogResult = DialogResult.Cancel;
}
else if (ut == UserType.admin)
{
LoginUser lu = LoginUser.GetInstance();
lu.InitMember(textBoxUserName.Text);
ol.ActionDesc = "管理员登录成功!";
ol.OperatorId = lu.Id;
CommonHelper.ShowSuccMessageBox("管理员,欢迎进入本系统!");
this.DialogResult = DialogResult.OK;
}
else
{
LoginUser lu = LoginUser.GetInstance();
lu.InitMember(textBoxUserName.Text);
ol.ActionDesc = "用户登录成功!";
ol.OperatorId = lu.Id;
CommonHelper.ShowSuccMessageBox("欢迎进入本系统!");
this.DialogResult = DialogResult.OK;
}
OperationLogService logServ = new OperationLogService();
logServ.LogAdd(ol);
}
直接返回Cancle
private void buttonCancle_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
}
!!新建好这个窗体后首先要做的事是把程序运行的默认窗体改成该窗体,而不是登录窗体,这样才可以得到登录窗体返回的值从而进行判断.
找到Program.cs这个文件,把里面的Application.Run(new FormMain());
Run里面改成我们的FormMain窗体就可以了.
左上角这个是MenuStrip控件
添加管理员操作和我的,分别为
最下面的是StatusStrip控件,负责状态的展示.
这个FormMain窗体的IsMdiContains属性需要设置为True,这样才可以作为一个容器,存放其他窗体.
可以给该界面美化一下,加个图标,在这里Icon属性进行修改
图标资源我也会上传到github和gitee上,大家自取,链接在文章末尾
新建登录窗体,然后查看窗体的返回值,如果是Cancl就说明登陆失败或者用户状态异常,所以就直接退出程序.如果登录成功就把登录者的信息通过LoginUser类中的GetInstance获取实例,然后在窗体下面的状态栏中显示出用户登录的一些基本信息,例如用户真名以及登录的时间.
如果登录用户不是管理员的话就把管理员操作的Enabled属性改为false,说明普通用户禁止使用管理员操作功能
private void FormMain_Load(object sender, EventArgs e)
{
Form fmLogin = new FormLogin();
DialogResult dr = fmLogin.ShowDialog();
LoginUser lu = LoginUser.GetInstance();
if(lu.UserName != "admin")
{
tsslUserInfo.Text = string.Format("尊敬的用户{0},欢迎您进入本系统!登录时间为{1}", lu.RealName, DateTime.Now.ToString());
adminOperate.Enabled = false;
}
else
{
tsslUserInfo.Text = string.Format("尊敬的管理员{0},欢迎您进入本系统!登录时间为{1}", lu.RealName, DateTime.Now.ToString());
}
if(dr == DialogResult.Cancel)
{
Application.Exit();
}
}
创建添加用户界面的对象,然后将它的MdiParent属性设置为本窗体,并将其Show
private void toolStripMenuItemAdd_Click(object sender, EventArgs e)
{
Form fmAdd = new FormUserAdd();
fmAdd.MdiParent = this;
fmAdd.Show();
}
大致都是创建对应的窗体对象然后,设置Mdi之后将之Show
调用数据层的OperatorService然后将输入在textBox中的信息插入到Operator数据表中
注意:密码输入之后需要把输入值转化成MD5的形式存入才可以,所以还需要嗲用CommonHelper中的GetMd5方法.
private void buttonAdd_Click(object sender, EventArgs e)
{
OperatorService opSev = new OperatorService();
if (opSev.InsertOperator(textBoxPasswordAdd.Text, CommonHelper.GetMd5(textBoxRealNameAdd.Text), textBoxUserNameAdd.Text))
{
CommonHelper.ShowSuccMessageBox("添加成功!");
}
else
{
CommonHelper.ShowErrorMessageBox("添加失败!");
}
}
将输入的用户名删除,之后再把textBox的中值清理干净,方便下次输入.
当没在数据库中找到对应的用户时输出无此用户,删除失败.
private void buttonDelete_Click(object sender, EventArgs e)
{
OperatorService opSev = new OperatorService();
if (opSev.DeleteOperator(textBoxUserName.Text))
{
CommonHelper.ShowSuccMessageBox("删除成功!");
textBoxUserName.Clear();
}
else
{
CommonHelper.ShowErrorMessageBox("无此用户,删除失败!");
}
}
通过输入的用户名来锁定该用户,即把它的用户信息中islocked给改成1
当没找到该用户时弹出错误提示
private void buttonLock_Click(object sender, EventArgs e)
{
OperatorService opSev = new OperatorService();
if (opSev.LockOperator(textBoxUserName.Text))
{
CommonHelper.ShowSuccMessageBox("锁定成功!");
textBoxUserName.Clear();
}
else
{
CommonHelper.ShowErrorMessageBox("无此用户,锁定失败!");
}
}
下方还有六个label控件不要忘了(虽然现在看不见),用来显示查询到的用户的信息.
通过输入的用户名进行查询,如果没查到就说明op等于null,一旦发生这种情况就弹出错误弹窗说明找不到该用户,如果op不等于null,就把op中的信息都赋给对应的label显示在界面中.
private void buttonSearch_Click(object sender, EventArgs e)
{
OperatorService opSev = new OperatorService();
Operator op = opSev.GetOperator(textBoxUserName.Text);
if (op!= null)
{
CommonHelper.ShowSuccMessageBox("查询成功!");
labelUserName.Text = "用户名:" + op.UserName;
labelId.Text = "Id:" + op.Id;
labelIsDeleted.Text = "是否被删除:" + op.IsDeleted;
labelIsLocked.Text = "是否被锁定:" + op.IsLocked;
labelRealName.Text = "姓名:" + op.RealName;
labelPassword.Text = "密码:" + op.Password;
}
else
{
CommonHelper.ShowErrorMessageBox("无此用户,查询失败!");
}
}
首先先通过LoginUser的GetInstance方法获取到当前登录着的用户实例,然后将他的密码和经过MD5转化后的传入的旧密码进行对比,如果一致就说明旧密码正确,通过OperatorService 中的修改密码方法将经过Md5转化后的密码传入,完成修改密码.
如果不相等,说明旧密码输入错误,就弹出错误窗口.
private void buttonModify_Click(object sender, EventArgs e)
{
LoginUser lu = LoginUser.GetInstance();
if (lu.Password.Equals(CommonHelper.GetMd5(textBoxPwdBefore.Text)))
{
OperatorService op = new OperatorService();
op.PwdModify(lu.UserName, CommonHelper.GetMd5(textBoxPwdNew.Text));
lu.Password = CommonHelper.GetMd5(textBoxPwdNew.Text);
CommonHelper.ShowSuccMessageBox("密码修改成功!");
}
else
{
CommonHelper.ShowErrorMessageBox("原密码输入错误!");
}
}
大灰框为dataGridView控件,用来显示日志列表.
下方四个控件都是LinkLabel,相当于时有点击事件的label.
由于日志的数据量比较大,所以可以进行分页展示,然后设置四个链接标签来进行跳转功能,分为首页,尾页,下一页,上一页.同时分页也有一个好处就是可以节省加载时间,节约资源,因为一次性加载全部数据有点浪费,可以分页进行加载.
public static int NUM_PER_PAGE = 13;
public int currentPage = 1;
public OperationLogService logServ = null;
public int pages = 0;
public FormLogQuery()
{
logServ = new OperationLogService();
InitializeComponent();
}
首先先把当前的页码设置为第一页,然后通过GetLogCount方法获取数据库中日志的总条数
通过运算算出一共可以装多少页
再把这个页数显示再总页数label中
给dataGridView对象的DataSource属性赋值,由GetLogList方法提供,这样就实现了将日志显示在界面中的功能
注意:由于我们刚进去是第一页所以上一页这个按钮是不应该能起作用的,要不然会发生去到第0页的事情,所以需要把该linklable的Enabled属性设置为False;
private void FormLogQuery_Load(object sender, EventArgs e)
{
lblPageCurrent.Text = string.Format("第{0}页", currentPage);
int n = logServ.GetLogCount();
pages = n % NUM_PER_PAGE == 0 ? n / NUM_PER_PAGE : n / NUM_PER_PAGE + 1;
lblPageTotal.Text = string.Format("共{0}页", pages);
dataGridView1.DataSource = logServ.GetLogList(currentPage, NUM_PER_PAGE);
llblPrev.Enabled = false;
}
每次完成点击上下页之后需要重新刷新一下dataGridView1.DataSource和当前所在的页码,为了不重复写代码,所以可以使用这个方法来简化.
private void show(int pageNo)
{
dataGridView1.DataSource = logServ.GetLogList(pageNo, NUM_PER_PAGE);
lblPageCurrent.Text = string.Format("第{0}页", pageNo);
}
首先要进行判断,如果当前页码为1时,说明下一页是可以被点击的,所以要把下一页的Eabled属性改为true.否则不这样判断就会在别的事件中可能会把下一页给禁用掉,然后这里没有恢复就形成bug了.
将当前页设置为1,重新刷新列表以及把上一页设置为禁用(因为当前已经是第一页了,要不然就会到第0页去了,这不是我们想要的)
private void llblFirst_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
if (currentPage != 1)
{
llblNext.Enabled = true;
}
currentPage = 1;
show(currentPage);
llblPrev.Enabled = false;
}
同样的我们要进行判断是否为第一页,不是就说明可以上一页
把当前页改为最大页数
刷新
禁用下一页
private void llblLast_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
if (currentPage != 1)
{
llblPrev.Enabled = true;
}
llblPrev.Enabled = true;
currentPage = pages;
show(currentPage);
llblNext.Enabled = false;
}
既然已经按了下一页,所以上一页页应该可以被使用了,遂启用
将当前的页码++
刷新
如果现在是尾页,就把下一页给禁用掉
private void llblNext_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
llblPrev.Enabled = true;
currentPage++;
show(currentPage);
if (currentPage == pages)
{
llblNext.Enabled = false;
}
}
逻辑同上,就不再赘述了
private void llblPrev_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
llblNext.Enabled = true;
currentPage--;
show(currentPage);
if (currentPage == 1)
{
llblPrev.Enabled = false;
}
}
这样显示出来的记录会出现有几行是空的的现象,这是因为sql连接操作丢失了操作员为0的那些记录。我们可以通过在Operator表中增加一条Id为0的记录解决
调用DepartmentService 中的GetDeptList方法来赋值给dataGridView1.DataSource,完成显示部门列表的功能
private void FormShowDept_Load(object sender, EventArgs e)
{
DepartmentService deptSev = new DepartmentService();
dataGridView1.DataSource = deptSev.GetDeptList();
}
通过部门id来进行对应部门的删除操作,如果没找到就提示错误弹窗
private void buttonAdd_Click(object sender, EventArgs e)
{
DepartmentService deptSev = new DepartmentService();
if (deptSev.deleteDepartment(textBoxNameAdd.Text))
{
CommonHelper.ShowSuccMessageBox("删除成功!");
}
else
{
CommonHelper.ShowErrorMessageBox("未找到该部门,删除失败!");
}
}
通过输入的部门名来调用数据层来实现添加功能
private void buttonAdd_Click(object sender, EventArgs e)
{
DepartmentService deptSev = new DepartmentService();
if (deptSev.InsertDepartment(textBoxNameAdd.Text))
{
CommonHelper.ShowSuccMessageBox("添加成功!");
}
else
{
CommonHelper.ShowErrorMessageBox("添加失败");
}
}
此处需要注意,因为我们的迁移日志的操作其实是由两个操作组成的,一个是插入数据到新表,一个是在旧表中删除数据.这也就会产生一个问题.那就事务问题,一旦一个操作完成了一个失败了,那么就会产生严重的后果,比如说是数据的丢失.所以我们需要使用事务来进行操作数据库
所以我们需要调用SqlHelper中的Execute方法来进行操作,传入两个sql语句以及对应的参数就可以了.如果失败的话会回滚,这样就不会出现数据丢失了.
private void button1_Click(object sender, EventArgs e)
{
DateTime datetime = dtp1.Value;
string sql1 = "insert into dbo.NewOperationLog(Id, OperatorId, ActionDate, ActionDesc)(select Id, OperatorId, ActionDate, ActionDesc from dbo.OperationLog where ActionDate <= @date)";
string sql2 = "delete from dbo.OperationLog where ActionDate <= @date";
SqlParameter parameters = new SqlParameter("@date", datetime);
if(SqlHelper.Execute(sql1, sql2, parameters))
{
CommonHelper.ShowSuccMessageBox("迁移成功!");
}
else
{
CommonHelper.ShowErrorMessageBox("迁移失败!");
}
}
左上角的是toolStrip控件.
往里面添加四个toolStripButton
为了表达清楚每个按钮的功能,所以给不同功能的button的Image属性设置一下,就和我上图一样,清晰易懂.
但是首先要先导入image图像
在这里导入图标文件就可以,同样图标文件在github和gitee上都有,请自取
左上角带有搜索条件的控件时groupBox,带有选框的checkBox控件,大灰框是dataGridView控件.
通过GetEmployeeList方法来获取部门的列表传给dataGridView1.DataSource以达到显示的目的.
同时需要对部门comboBox进行数据的绑定,这样可以有下拉框.
这里要先设置comboBox的DisplayMember属性和ValueMember属性,一个是展示的字段,一个实际值的字段,之后再赋给DataSource值,要不然可能会出错.
为了达到一开始的默认值为空,所以把它的SelectedIndex属性给设置为-1.这样就ok了
private void FormEmpList_Load(object sender, EventArgs e)
{
EmployeeService es = new EmployeeService();
DepartmentService ds = new DepartmentService();
dataGridView1.DataSource = es.GetEmployeeList();
comboBox1.ValueMember = "编号";
comboBox1.DisplayMember = "部门名";
comboBox1.DataSource = ds.GetDeptList();
comboBox1.SelectedIndex = -1;
}
首先先新建一个用来存放查询条件信息的对象EmployeeSelect emp,之后确认comboBox是否被选中了,如果没有选中就提示请输入,有输入了就把它的值赋给查询条件对象emp
因为是多条件查询,所以我们不能保证一共有多少格查询条件,所以需要进行识别.而名字和部门都可以通过有没有值来识别,但是日期由于本身默认就有值,所以我们加一个辨识符TimeCheck 来进行识别,一旦被选中,就把TimeCheck 变为true
最后通过GetEmployeeList方法将emp传入进去返回的值赋给datasource显示出来.
private void buttonSearch_Click(object sender, EventArgs e)
{
EmployeeService es = new EmployeeService();
EmployeeSelect emp = new EmployeeSelect();
if (checkBoxName.Checked)
{
if(textBoxName.Text == "")
{
CommonHelper.ShowErrorMessageBox("请输入姓名!");
}
else
{
emp.Name = textBoxName.Text;
}
}
if (checkBoxDept.Checked)
{
if (comboBox1.SelectedIndex == -1)
{
CommonHelper.ShowErrorMessageBox("请选择部门!");
}
else
{
emp.DepartmentId = (Guid)comboBox1.SelectedValue;
}
}
if (checkBoxTime.Checked)
{
emp.TimeCheck = true;
emp.InDay1 = dateTimePicker1.Value;
emp.InDay2 = dateTimePicker2.Value;
}
dataGridView1.DataSource = es.GetEmployeeList(emp);
}
点击之后弹出来一个添加员工的界面,在里面可以输入员工的基本信息
窗口返回值是OK就弹窗保存成功并且刷新一下员工列表
如果是Cancle就弹窗取消保存
如果是其他就弹窗保存失败
private void tsbAdd_Click(object sender, EventArgs e)
{
Form fmEmpAdd = new FormEmployeeAdd();
DialogResult dr = fmEmpAdd.ShowDialog();
if(dr == DialogResult.OK)
{
EmployeeService es = new EmployeeService();
CommonHelper.ShowSuccMessageBox("保存成功!");
dataGridView1.DataSource = es.GetEmployeeList();
}
else if(dr == DialogResult.Cancel)
{
CommonHelper.ShowErrorMessageBox("取消保存!");
}
else
{
CommonHelper.ShowErrorMessageBox("保存失败!");
}
}
如果当前没有选中行,也就是选中的行数小于0时提示请选中行
选中的行id为第0行第0列的value值
如果id等于空就提示请先选中员工
创建添加员工窗体对象(因为这次需要的控件和添加员工界面的一致,所以就直接调用了,不再重新写一个新窗体类了),调用它的有参构造函数,传入一个empid值,这样才能够知道是哪一个员工需要进行编辑.
再添加员工界面的返回值进行判断,根据是否成功弹出成功或者失败的弹窗
当然,需要刷新一下员工列表
private void tsbEdit_Click(object sender, EventArgs e)
{
if(dataGridView1.SelectedRows.Count > 0)
{
string id = "";
id = dataGridView1.SelectedRows[0].Cells[0].Value.ToString();
if(string.IsNullOrEmpty(id))
{
CommonHelper.ShowErrorMessageBox("请先选中员工!");
}
else
{
Guid empid = Guid.Parse(id);
Form fmEmpAdd = new FormEmployeeAdd(empid);
DialogResult dr = fmEmpAdd.ShowDialog();
EmployeeService es = new EmployeeService();
dataGridView1.DataSource = es.GetEmployeeList();
if(dr == DialogResult.OK)
{
CommonHelper.ShowSuccMessageBox("修改成功!");
}
else
{
CommonHelper.ShowErrorMessageBox("取消修改!");
}
}
}
else
{
CommonHelper.ShowErrorMessageBox("请选中行!");
}
}
和编辑的一开始判断都差不多,首先检查是否选中行再输出相干的信息
之后通过id来进行删除操作.
之后再刷新员工列表
private void tsbDelete_Click(object sender, EventArgs e)
{
if (dataGridView1.SelectedRows.Count > 0)
{
string id = dataGridView1.SelectedRows[0].Cells[0].Value.ToString();
if (string.IsNullOrEmpty(id))
{
CommonHelper.ShowErrorMessageBox("请先选中员工!");
}
else
{
Guid empid = Guid.Parse(id);
EmployeeService es = new EmployeeService();
if (es.deleteEmployeeById(empid))
{
CommonHelper.ShowSuccMessageBox("删除成功!");
}
else
{
CommonHelper.ShowErrorMessageBox("删除失败!");
}
dataGridView1.DataSource = es.GetEmployeeList();
}
}
else
{
CommonHelper.ShowErrorMessageBox("请选中行!");
}
}
此处实现的是点击导出Excel按钮就把员工列表中的信息导出到对应的位置下的excel文件.
首先使用SaveFileDialog 来选择需要存储的位置以及excel文件的名字
然后使用ExcelService 中的CreateExcel方法来导出excel文件
private void tsbExport_Click(object sender, EventArgs e)
{
ExcelService es = new ExcelService();
string path = "";
SaveFileDialog sfd = new SaveFileDialog();
sfd.Title = "保存文件";
sfd.Filter = "Excel 文件(*.xls)|*.xls|Excel 文件(*.xlsx)|*.xlsx|所有文件(*.*)|*.*";
sfd.FileName = "员工信息.xls";
if (sfd.ShowDialog() == DialogResult.OK)
{
path = sfd.FileName;
}
else
{
return;
}
if (es.CreateExcel(path))
{
CommonHelper.ShowSuccMessageBox("导出成功!");
}
else
{
CommonHelper.ShowErrorMessageBox("导出失败!");
}
}
基本信息和简历那个控件是tabControl
入职日期和出生日期为datetimepicker
性别,婚姻状况,政治面貌,学历,部门都是comboBox
备注是richTextBox
照片为pictureBox
在简历界面就只有一个大的richtextBox
将empid初始化为Guid.Empty,以及创建一个emp对象.前者用来进行判断是添加员工功能还是编辑员工功能.
后者是用来存放员工的信息
Guid empid = Guid.Empty;
Employee emp = new Employee();
无参构造和有参构造
public FormEmployeeAdd()
{
InitializeComponent();
}
public FormEmployeeAdd(Guid a)
{
InitializeComponent();
empid = a;
}
在载入界面需要把窗体内的comboBox都给和数据进行绑定.
首先我们要把各个comboBox的DisplayMember ,ValueMember 和属性设置为"Name"和"Id".
由于大部分数据都是存在dictionary表中(除了部门).所以我们调用DictionaryService类中的GetComboList方法,通过不同的参数传入,返回对应的数据放入DataSource 中.
然后将comboBox.SelectedIndex设置为-1,初始化为空的
然后进行判断,如果创建该窗体的构造方法是无参的话就说明empid这个字段的值是等于Guid.Empty的,这时我们就不用去执行将员工信息写入空间中显示.
但如果创建窗体的构造方法是有参的话,就说明我们这次需要实现的功能是编辑员工的功能,这时候我们就像需要把emp对象中的信息都读取到窗体空间中显示出来,方便进行编辑.
有一点需要注意的是Photo的赋值方法有点不一样,因为存在emp对象中的是数据流的数据类型,所以不能直接存入pictureBox中,这时候需要使用MemoryStream来把数据流读取出来,这时候再用Bitmap就可以将图片赋值给PictureBox.Image了.
private void FormEmployeeAdd_Load(object sender, EventArgs e)
{
DictionaryService ds = new DictionaryService();
comboBoxGender.DisplayMember = "Name";
comboBoxGender.ValueMember = "Id";
comboBoxGender.DataSource = ds.GetComboList("性别");
comboBoxGender.SelectedIndex = -1;
comboBoxMarrage.DisplayMember = "Name";
comboBoxMarrage.ValueMember = "Id";
comboBoxMarrage.DataSource = ds.GetComboList("婚姻状况");
comboBoxMarrage.SelectedIndex = -1;
comboBoxZhengZhi.DisplayMember = "Name";
comboBoxZhengZhi.ValueMember = "Id";
comboBoxZhengZhi.DataSource = ds.GetComboList("政治面貌");
comboBoxZhengZhi.SelectedIndex = -1;
comboBoxStudy.DisplayMember = "Name";
comboBoxStudy.ValueMember = "Id";
comboBoxStudy.DataSource = ds.GetComboList("学历");
comboBoxStudy.SelectedIndex = -1;
DepartmentService deptSer = new DepartmentService();
comboBoxDept.ValueMember = "编号";
comboBoxDept.DisplayMember = "部门名";
comboBoxDept.DataSource = deptSer.GetDeptList();
comboBoxDept.SelectedIndex = -1;
if(empid != Guid.Empty)
{
EmployeeService es = new EmployeeService();
emp = es.GetEmpById(empid);
textBoxName.Text = emp.Name;
textBoxNation.Text = emp.Nation;
textBoxNumber.Text = emp.Number;
textBoxPlace.Text = emp.Address;
textBoxHomeTown.Text = emp.NativePlace;
textBoxEmail.Text = emp.Email;
textBoxTelephone.Text = emp.Telephone;
richTextBoxResume.Text = emp.Resume;
richTextBoxRemark.Text = emp.Remarks;
comboBoxDept.SelectedValue = emp.DepartmentId;
comboBoxGender.SelectedValue = emp.GenderId;
comboBoxMarrage.SelectedValue = emp.MarriageId;
comboBoxZhengZhi.SelectedValue = emp.PartyId;
comboBoxStudy.SelectedValue = emp.EducationId;
MemoryStream ms = new MemoryStream(emp.Photo);
pictureBox1.Image = new Bitmap(ms);
}
}
首先进行数据的完整性判断,如果有哪些数据没有输入的话就弹出错误提示框然后终止运行
其中备注和简历可以为空,如果为空就默认为空白也就是""
然后将控件中的信息都保存在emp中,其中emp.Id是使用Guid.NewGuid随机生成的.
之后再进行判断,如果empid为空的话就说明这是添加员工功能创建的窗体,所以就传入emp去执行InsertEmployee方法
如果empid不为空就说明是编辑员工创建的窗体,所以传入emp去执行updateEmployee方法
private void button2_Click(object sender, EventArgs e)
{
if (textBoxName.Text == "")
{
CommonHelper.ShowErrorMessageBox("请输入姓名!");
return;
}
else
{
emp.Name = textBoxName.Text;
}
if (textBoxNation.Text == "")
{
CommonHelper.ShowErrorMessageBox("请输入民族!");
return;
}
else
{
emp.Nation = textBoxNation.Text;
}
if (textBoxEmail.Text == "")
{
CommonHelper.ShowErrorMessageBox("请输入电子邮箱!");
return;
}
else
{
emp.Email = textBoxEmail.Text;
}
if (textBoxHomeTown.Text == "")
{
CommonHelper.ShowErrorMessageBox("请输入籍贯!");
return;
}
else
{
emp.NativePlace = textBoxHomeTown.Text;
}
if (textBoxNumber.Text == "")
{
CommonHelper.ShowErrorMessageBox("请输入工号!");
return;
}
else
{
emp.Number = textBoxNumber.Text;
}
if (textBoxPlace.Text == "")
{
CommonHelper.ShowErrorMessageBox("请输入联系地址!");
return;
}
else
{
emp.Address = textBoxPlace.Text;
}
if (textBoxTelephone.Text == "")
{
CommonHelper.ShowErrorMessageBox("请输入联系电话!");
return;
}
else
{
emp.Telephone = textBoxTelephone.Text;
}
if (richTextBoxRemark.Text == "")
{
emp.Remarks = "";
}
else
{
emp.Remarks = richTextBoxRemark.Text;
}
if (richTextBoxResume.Text == "")
{
emp.Resume = "";
}
else
{
emp.Resume = richTextBoxResume.Text;
}
emp.GenderId = (Guid)comboBoxGender.SelectedValue;
emp.MarriageId = (Guid)comboBoxMarrage.SelectedValue;
emp.PartyId = (Guid)comboBoxZhengZhi.SelectedValue;
emp.EducationId = (Guid)comboBoxStudy.SelectedValue;
emp.DepartmentId = (Guid)comboBoxDept.SelectedValue;
emp.BirthDay = dateTimePickerBirth.Value;
emp.InDay = dateTimePickerInDay.Value;
emp.Id = Guid.NewGuid();
if(emp.Photo == null){
CommonHelper.ShowErrorMessageBox("请选择照片!");
return;
}
EmployeeService es = new EmployeeService();
if (empid == Guid.Empty)
{
if (es.InsertEmployee(emp))
{
this.DialogResult = DialogResult.OK;
}
else
{
CommonHelper.ShowErrorMessageBox("保存失败!");
}
}
else
{
if (es.updateEmployee(emp, empid))
{
this.DialogResult = DialogResult.OK;
}
else
{
CommonHelper.ShowErrorMessageBox("修改失败!");
}
}
}
使用OpenFileDialog对象来打开图片文件,设置一个过滤器只能打开规定格式的文件
使用FileStream读出文件数据,存入emp.Photo中.
这个方法需要用异常捕捉一下,要不然如果没有选择图片就退出OpenFileDialog会发生错误.
private void button1_Click(object sender, EventArgs e)
{
try
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "JPG图片|*.jpg|BMP图片|*.bmp|Gif图片|*.gif";
ofd.ShowDialog();
pictureBox1.Image = Image.FromFile(ofd.FileName);
FileStream fs = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read);
emp.Photo = new byte[fs.Length];
fs.Read(emp.Photo, 0, Convert.ToInt32(fs.Length));
}catch(Exception ex)
{
CommonHelper.ShowErrorMessageBox("请选择图片");
}
}
直接返回Cancle
private void button3_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
}
将数据与comboBox绑定
因为没有对应的方法可以查村符合条件的DataTable,所以我们自己创建一个DataTable来作为comboBox的数据源
年份显示的内容前后十年,月份显示的是1-12月
注意要把Display的值和实际数据分开
private void FormSalarySheet_Load(object sender, EventArgs e)
{
DepartmentService ds = new DepartmentService();
comboBoxDepartment.ValueMember = "编号";
comboBoxDepartment.DisplayMember = "部门名";
comboBoxDepartment.DataSource = ds.GetDeptList();
comboBoxDepartment.SelectedIndex = -1;
DataTable months = new DataTable();
months.Columns.Add("Mouth");
months.Columns.Add("Value");
for (int i = 1; i <= 12; i++)
{
DataRow dr = months.NewRow();
dr["Mouth"] = i.ToString();
dr["Value"] = i;
months.Rows.Add(dr);
}
comboBoxMonth.DataSource = months;
comboBoxMonth.DisplayMember = "Month";
comboBoxMonth.ValueMember = "Value";
comboBoxMonth.SelectedIndex = -1;
DataTable years = new DataTable();
years.Columns.Add("Year");
years.Columns.Add("Value");
for (int i = 2012; i <= 2032; i++)
{
DataRow dr = years.NewRow();
dr["Year"] = i.ToString();
dr["Value"] = i;
years.Rows.Add(dr);
}
comboBoxYear.DataSource = years;
comboBoxYear.DisplayMember = "Year";
comboBoxYear.ValueMember = "Value";
comboBoxYear.SelectedIndex = -1;
}
根据选择的年份月份和部门来生成工资单,对于生成过的就进行判断是否覆盖.
当该部门没有人的时候就弹窗该部门没有人,使用EmptyJudge类中isEmployeeExist方法的来进行判断.
使用EmptyJudge类中的isSheetExist方法来判断是否是第一次生成该工资单
对于覆盖的时候进行询问,再次确认,因为是不可撤回的所以需要谨慎.
最后再将装有数据的DataTable传给dataGridView1.DataSource,完成显示.
private void button1_Click(object sender, EventArgs e)
{
if (comboBoxMonth.SelectedIndex == -1 || comboBoxYear.SelectedIndex == -1 || comboBoxDepartment.SelectedIndex == -1)
{
CommonHelper.ShowErrorMessageBox("请选择正确的年份,月份和部门");
return;
}
SalarySheet sheet = new SalarySheet();
sheet.Id = Guid.NewGuid();
sheet.Month = Convert.ToInt32(comboBoxMonth.SelectedValue);
sheet.Year = Convert.ToInt32(comboBoxYear.SelectedValue);
sheet.DepartmentId = (Guid)comboBoxDepartment.SelectedValue;
EmptyJudge ej = new EmptyJudge();
if (!ej.isEmployeeExist(sheet.DepartmentId))
{
CommonHelper.ShowErrorMessageBox("该部门没有员工!");
return;
}
SalarySheetService sss = new SalarySheetService();
SalarySheetItemService ssis = new SalarySheetItemService();
if (!ej.isSheetExist(sheet.Month, sheet.Year, sheet.DepartmentId))
{
Guid sheetId = sss.BuildNewSalarySheet(sheet);
ssis.BuildSalarySheetItems(sheetId, sheet.DepartmentId);
dataGridView1.DataSource = ssis.GetSalarySheetItems(sheetId);
}
else
{
DialogResult dr = CommonHelper.ShowChooseMessageBox("该工资表此前已经生成过了,按'是'显示原有表数据,按'否'重新生成新表");
if (dr == DialogResult.Yes)
{
Guid sheetId = sss.GetSalarySheetId(sheet.Month, sheet.Year, sheet.DepartmentId);
dataGridView1.DataSource = ssis.GetSalarySheetItems(sheetId);
}
else if(dr == DialogResult.No)
{
DialogResult drr = CommonHelper.ShowChooseMessageBox("确认是否重新生成新表?(此操作不可撤回)");
if (drr == DialogResult.Yes)
{
Guid sheetId = sss.GetSalarySheetId(sheet.Month, sheet.Year, sheet.DepartmentId);
ssis.ReBuildSalarySheetItem(sheetId);
dataGridView1.DataSource = ssis.GetSalarySheetItems(sss.GetSalarySheetId(sheet.Month, sheet.Year, sheet.DepartmentId));
}
else
{
Guid sheetId = sss.GetSalarySheetId(sheet.Month, sheet.Year, sheet.DepartmentId);
dataGridView1.DataSource = ssis.GetSalarySheetItems(sheetId);
}
}
}
}
将dataGridView1.DataSource中的被修改过的信息写入数据库完成更新.
money类型的我们要使用decimal才可以不丢失精度.
private void button2_Click(object sender, EventArgs e)
{
SalarySheetItemService ssis = new SalarySheetItemService();
SalarySheetItem item = null;
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++)
{
item = new SalarySheetItem();
item.Id = (Guid)dataGridView1.Rows[i].Cells[0].Value;
item.BaseSalary = (decimal)dataGridView1.Rows[i].Cells[2].Value;
item.Bonus = (decimal)dataGridView1.Rows[i].Cells[3].Value;
item.Fine = (decimal)dataGridView1.Rows[i].Cells[4].Value;
item.Other = (decimal)dataGridView1.Rows[i].Cells[5].Value;
ssis.SaveSheetItem(item);
}
CommonHelper.ShowSuccMessageBox("工资单保存成功!");
}
用来查询对应的工资单同时显示出来,可以进行打印操作.
三个comboBox一个按钮,下面的那个是在工具箱里点开报表就可以看到的ReportViewer.
对于comboBox进行非空判断,然后将comboBox中的数据传给ej.isSheetExist方法来进行判断是否已经生成该工资表,然后再输出对应的报表
对于报表,我这个vs2015里就有这个控件,所以不用进行下载了,如果没有的朋友可以自己去下载.
我们首先要创建一个Report.rdlc
我们首先需要创建一个DataSet,再里面设置好对应的字段(和我们的sql查询语句相对应)包括年月部门基本工资奖金扣款和名字等
然后再在数据集这里右键添加
选择新建,再选择对象
绑定我们的界面层
然后我们再Report1中画出我们需要的打印界面,其中工资单三个大字是用矩形,下面的年月部门可以用矩阵也可以是列表,不过需要把边框设置为空,这样就不会显示格子了.(右键文本框属性里设置)
下面的序号姓名基本工资奖金扣款其他小计都设置好
这时候你可能会发现输出的是空白格,这是因为字体不太对,我们再文本框属性里设置一下字体为中文字体例如宋体就行,同时为了更美观,我们可以再设置一下居中.
然后对应的数据需要进行绑定,我们把鼠标移动到对应的框内会出现
这个按钮,点击就可以选择对应的字段了.
而序号和小计是需要表达式来进行显示的,其中序号的表达式是
=rownumber(nothing)
小计的表达式是
=SUM(Fields!baseSalary.Value+Fields!bonus.Value+Fields!other.Value-Fields!fine.Value)
然后下面这个另一个小计也是一样的表达式
注意:这里的表达式需要前面再加一个SUM,要不然下面这个小计就不会统计上面的小计相加,至于原因,不懂hh
最后再右键这个右箭头选择我们的Report1完成绑定就ok了.
完成comboBox的绑定
private void FormPrintSalarySheet_Load(object sender, EventArgs e)
{
DepartmentService ds = new DepartmentService();
comboBoxDept.ValueMember = "编号";
comboBoxDept.DisplayMember = "部门名";
comboBoxDept.DataSource = ds.GetDeptList();
comboBoxDept.SelectedIndex = -1;
DataTable months = new DataTable();
months.Columns.Add("Mouth");
months.Columns.Add("Value");
for (int i = 1; i <= 12; i++)
{
DataRow dr = months.NewRow();
dr["Mouth"] = i.ToString();
dr["Value"] = i;
months.Rows.Add(dr);
}
comboBoxMonth.DataSource = months;
comboBoxMonth.DisplayMember = "Month";
comboBoxMonth.ValueMember = "Value";
comboBoxMonth.SelectedIndex = -1;
DataTable years = new DataTable();
years.Columns.Add("Year");
years.Columns.Add("Value");
for (int i = 2012; i <= 2032; i++)
{
DataRow dr = years.NewRow();
dr["Year"] = i.ToString();
dr["Value"] = i;
years.Rows.Add(dr);
}
comboBoxYear.DataSource = years;
comboBoxYear.DisplayMember = "Year";
comboBoxYear.ValueMember = "Value";
comboBoxYear.SelectedIndex = -1;
this.reportViewer1.RefreshReport();
}
private void button1_Click(object sender, EventArgs e)
{
if (comboBoxMonth.SelectedIndex == -1 || comboBoxYear.SelectedIndex == -1 || comboBoxDept.SelectedIndex == -1)
{
CommonHelper.ShowErrorMessageBox("请选择正确的年份,月份和部门");
return;
}
SalarySheetService sss = new SalarySheetService();
SalarySheet sheet = new SalarySheet();
sheet.Month = Convert.ToInt32(comboBoxMonth.SelectedValue);
sheet.Year = Convert.ToInt32(comboBoxYear.SelectedValue);
sheet.DepartmentId = (Guid)comboBoxDept.SelectedValue;
sheet.Id = sss.GetSalarySheetId(sheet.Month, sheet.Year, sheet.DepartmentId);
EmptyJudge ej = new EmptyJudge();
SalarySheetItemService ssis = new SalarySheetItemService();
if (ej.isSheetExist(sheet.Month, sheet.Year, sheet.DepartmentId))
{
DataTable dt1 = new DataTable();
dt1 = ssis.GetReportSheet(sheet.Id);
reportViewer1.LocalReport.DataSources.Clear();
reportViewer1.LocalReport.DataSources.Add(new ReportDataSource("DataSet1", dt1));
reportViewer1.RefreshReport();
}
else
{
CommonHelper.ShowErrorMessageBox("该工资单尚未生成!");
}
}
urce = months;
comboBoxMonth.DisplayMember = “Month”;
comboBoxMonth.ValueMember = “Value”;
comboBoxMonth.SelectedIndex = -1;
DataTable years = new DataTable();
years.Columns.Add("Year");
years.Columns.Add("Value");
for (int i = 2012; i <= 2032; i++)
{
DataRow dr = years.NewRow();
dr["Year"] = i.ToString();
dr["Value"] = i;
years.Rows.Add(dr);
}
comboBoxYear.DataSource = years;
comboBoxYear.DisplayMember = "Year";
comboBoxYear.ValueMember = "Value";
comboBoxYear.SelectedIndex = -1;
this.reportViewer1.RefreshReport();
}
***
#### 2. 查询点击事件
```C#
private void button1_Click(object sender, EventArgs e)
{
if (comboBoxMonth.SelectedIndex == -1 || comboBoxYear.SelectedIndex == -1 || comboBoxDept.SelectedIndex == -1)
{
CommonHelper.ShowErrorMessageBox("请选择正确的年份,月份和部门");
return;
}
SalarySheetService sss = new SalarySheetService();
SalarySheet sheet = new SalarySheet();
sheet.Month = Convert.ToInt32(comboBoxMonth.SelectedValue);
sheet.Year = Convert.ToInt32(comboBoxYear.SelectedValue);
sheet.DepartmentId = (Guid)comboBoxDept.SelectedValue;
sheet.Id = sss.GetSalarySheetId(sheet.Month, sheet.Year, sheet.DepartmentId);
EmptyJudge ej = new EmptyJudge();
SalarySheetItemService ssis = new SalarySheetItemService();
if (ej.isSheetExist(sheet.Month, sheet.Year, sheet.DepartmentId))
{
DataTable dt1 = new DataTable();
dt1 = ssis.GetReportSheet(sheet.Id);
reportViewer1.LocalReport.DataSources.Clear();
reportViewer1.LocalReport.DataSources.Add(new ReportDataSource("DataSet1", dt1));
reportViewer1.RefreshReport();
}
else
{
CommonHelper.ShowErrorMessageBox("该工资单尚未生成!");
}
}
Gitee
Github
★,°:.☆( ̄▽ ̄)/$:.°★ 。
新手上路,有错请指正;