diff --git a/common/model/staff_salary.go b/common/model/staff_salary.go
index d80237b..4a3341e 100644
--- a/common/model/staff_salary.go
+++ b/common/model/staff_salary.go
@@ -13,6 +13,16 @@ var (
 	StaffSalaryStatusPayed = 2
 )
 
+type SalaryBillLine struct {
+	Label string
+	Value string
+}
+
+type SalaryBillSection struct {
+	Name string
+	Line []*SalaryBillLine
+}
+
 type StaffSalary struct {
 	Id             int64
 	CorpId         int64
diff --git a/conf/template/salary_bill.html b/conf/template/salary_bill.html
new file mode 100644
index 0000000..b700e2b
--- /dev/null
+++ b/conf/template/salary_bill.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>工资单</title>
+    <style>
+        body {
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%);
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            min-height: 100vh;
+            margin: 0;
+        }
+
+        .payroll-container {
+            background: white;
+            border-radius: 15px;
+            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
+            padding: 30px;
+            width: 90%;
+            max-width: 500px;
+            animation: fadeIn 0.6s ease;
+        }
+
+        @keyframes fadeIn {
+            from {
+                opacity: 0;
+                transform: translateY(-20px);
+            }
+            to {
+                opacity: 1;
+                transform: translateY(0);
+            }
+        }
+
+        h1 {
+            text-align: center;
+            color: #00838f;
+            font-size: 28px;
+            margin-bottom: 20px;
+        }
+
+        .section {
+            background: #f9f9f9;
+            border-radius: 10px;
+            padding: 20px;
+            margin-bottom: 20px;
+        }
+
+        h2 {
+            color: #006064;
+            font-size: 20px;
+            margin-bottom: 15px;
+        }
+
+        .info-row {
+            display: flex;
+            justify-content: space-between;
+            padding: 10px 0;
+            border-bottom: 1px solid #eee;
+        }
+
+        .info-row:last-child {
+            border-bottom: none;
+        }
+
+        .info-label {
+            font-weight: 600;
+            color: #333;
+        }
+
+        .info-value {
+            color: #555;
+        }
+
+        .total-row {
+            font-weight: bold;
+            font-size: 1.2em;
+            color: #00838f;
+            border-top: 2px solid #00838f;
+            padding-top: 15px;
+        }
+    </style>
+</head>
+
+<body>
+<div class="payroll-container">
+    <h1>{{.title}}</h1>
+
+    {{range $i, $data := .datas}}
+    <div class="section">
+        <h2>{{.Name}}</h2>
+
+        {{range $j, $v := .Line}}
+        <div class="info-row">
+            <span class="info-label">{{ $v.Label }}</span>
+            <span class="info-value">{{ $v.Value }}</span>
+        </div>
+        {{end}}
+    </div>
+    {{end}}
+
+
+    <div class="info-row total-row">
+        <span class="info-label">实发工资</span>
+        <span class="info-value">{{ .salary }}</span>
+    </div>
+</div>
+</body>
+
+</html>    
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 0ddf035..58b5618 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@ require (
 	git.u8t.cn/open/gosdk v0.0.0-20250309163531-2f47649d3dbd
 	github.com/ArtisanCloud/PowerWeChat/v3 v3.2.27
 	github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874
+	github.com/fogleman/gg v1.3.0
 	github.com/gin-gonic/gin v1.10.0
 	github.com/go-co-op/gocron v1.37.0
 	github.com/gogap/errors v0.0.0-20210818113853-edfbba0ddea9
@@ -49,6 +50,7 @@ require (
 	github.com/go-sql-driver/mysql v1.7.0 // indirect
 	github.com/goccy/go-json v0.10.2 // indirect
 	github.com/gogap/stack v0.0.0-20150131034635-fef68dddd4f8 // indirect
+	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
@@ -92,6 +94,7 @@ require (
 	go.uber.org/zap v1.21.0 // indirect
 	golang.org/x/arch v0.8.0 // indirect
 	golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
+	golang.org/x/image v0.14.0 // indirect
 	golang.org/x/net v0.27.0 // indirect
 	golang.org/x/sys v0.31.0 // indirect
 	golang.org/x/text v0.23.0 // indirect
diff --git a/server/controller/salary.go b/server/controller/salary.go
index a887ac0..cbb636c 100644
--- a/server/controller/salary.go
+++ b/server/controller/salary.go
@@ -92,3 +92,9 @@ func (s *Salary) SyncStaffSalary(ctx *gin.Context) {
 	go new(worker.Staff).SyncStaffSalary(corp.Id, "")
 	ctx.JSON(http.StatusOK, session.NewRspOk())
 }
+
+func (s *Salary) Bill(ctx *gin.Context) {
+	id := cast.ToInt64(ctx.Query("id"))
+
+	service.NewStaffSalary().Bill(id, ctx)
+}
diff --git a/server/server.go b/server/server.go
index d7931fe..6786443 100644
--- a/server/server.go
+++ b/server/server.go
@@ -38,6 +38,7 @@ func initRoutge(engine *gin.Engine) {
 	apiGroup.POST("/salary/pay", controller.NewSalary().Pay)
 	apiGroup.PUT("/staff/salary", controller.NewSalary().Update)
 	apiGroup.DELETE("/staff/salary", controller.NewSalary().Delete)
+	noTokenGroup.GET("/staff/salary/bill", controller.NewSalary().Bill)
 
 	apiGroup.GET("/staff/suggest", controller.NewStaff().Suggest)
 	apiGroup.POST("/staff/pay", controller.NewStaff().Pay)
diff --git a/server/service/staff_salary.go b/server/service/staff_salary.go
index 7391207..38eedb1 100644
--- a/server/service/staff_salary.go
+++ b/server/service/staff_salary.go
@@ -426,3 +426,70 @@ func (s *StaffSalary) toExcel(filePath string, header []string, datas [][]string
 	ctx.Writer.Header().Add("Content-Type", "application/msexcel")
 	ctx.File(filePath)
 }
+
+func (s *StaffSalary) Bill(id int64, ctx *gin.Context) {
+
+	salary, err := dao.NewStaffSalaryDao().Get(id)
+	session.CheckDBError(err)
+	session.CheckNilError(salary, "工资单不存在")
+	corp, err := dao.NewCorpDao().Get(salary.CorpId)
+	session.CheckDBError(err)
+	session.CheckNilError(corp, "企业不存在")
+	user, err := dao.NewStaffUserDao().Get(salary.UserId)
+	session.CheckDBError(err)
+	session.CheckNilError(user, "用户不存在")
+
+	datas := make([]*model.SalaryBillSection, 0)
+
+	baseDetail := make([]*model.SalaryBillLine, 0)
+	baseDetail = append(baseDetail, &model.SalaryBillLine{Label: "员工姓名", Value: fmt.Sprintf("%s", user.Realname)})
+	baseDetail = append(baseDetail, &model.SalaryBillLine{Label: "基本工资", Value: fmt.Sprintf("%s", user.GetSalary().Base)})
+	baseDetail = append(baseDetail, &model.SalaryBillLine{Label: "出勤工资", Value: fmt.Sprintf("%s", user.GetSalary().Target)})
+	datas = append(datas, &model.SalaryBillSection{
+		Name: "基本信息",
+		Line: baseDetail,
+	})
+
+	salaryDetail := make([]*model.SalaryBillLine, 0)
+
+	salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "出勤收入", Value: fmt.Sprintf("%.2f 元", salary.AttendSalary)})
+	salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "绩效收入", Value: fmt.Sprintf("%.2f 元", salary.TargetSalary)})
+
+	if salary.AwardSalary != 0 {
+		salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "奖金收入", Value: fmt.Sprintf("%.2f 元", salary.AwardSalary)})
+	}
+	if salary.OtherSalary != 0 {
+		salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "其他收入", Value: fmt.Sprintf("%.2f 元", salary.OtherSalary)})
+	}
+	if salary.SocialDeduct != 0 {
+		salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "社保扣除", Value: fmt.Sprintf("%.2f 元", salary.SocialDeduct)})
+	}
+	if salary.HouseDeduct != 0 {
+		salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "公积金扣除", Value: fmt.Sprintf("%.2f 元", salary.HouseDeduct)})
+	}
+
+	salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "个税扣除", Value: fmt.Sprintf("%.2f 元", salary.PersonalDeduct)})
+	if salary.OtherSalary != 0 {
+		salaryDetail = append(salaryDetail, &model.SalaryBillLine{Label: "其他扣除", Value: fmt.Sprintf("%.2f 元", salary.OtherSalary)})
+	}
+	datas = append(datas, &model.SalaryBillSection{
+		Name: "工资明细",
+		Line: salaryDetail,
+	})
+
+	attendDetail := make([]*model.SalaryBillLine, 0)
+	attendDetail = append(attendDetail, &model.SalaryBillLine{Label: "应出勤", Value: fmt.Sprintf("%d 天", salary.ShouldDay)})
+	attendDetail = append(attendDetail, &model.SalaryBillLine{Label: "休假天数", Value: fmt.Sprintf("%.2f 天", salary.HolidayDay)})
+	attendDetail = append(attendDetail, &model.SalaryBillLine{Label: "缺勤天数", Value: fmt.Sprintf("%.2f 天", float64(salary.ShouldDay)-salary.AttendDay-salary.HolidayDay)})
+	attendDetail = append(attendDetail, &model.SalaryBillLine{Label: "实际出勤", Value: fmt.Sprintf("%.2f 天", salary.AttendDay)})
+	datas = append(datas, &model.SalaryBillSection{
+		Name: "出勤信息",
+		Line: attendDetail,
+	})
+
+	ctx.HTML(http.StatusOK, "salary_bill.html", gin.H{
+		"title":  "工资单",
+		"salary": fmt.Sprintf("%.2f 元", salary.GetRealSalary()),
+		"datas":  datas,
+	})
+}