导入导出excel示例

This commit is contained in:
xiangheng
2024-01-20 01:20:42 +08:00
parent d7f13cb80b
commit ea481b867c
10 changed files with 659 additions and 5 deletions

View File

@@ -1,6 +1,7 @@
package flow_apply
import (
"errors"
"x_admin/admin/flow/flow_template"
"x_admin/core"
"x_admin/core/request"
@@ -65,6 +66,7 @@ func (Service flowApplyService) List(page request.PageReq, listReq FlowApplyList
if listReq.Status > 0 {
dbModel = dbModel.Where("status = ?", listReq.Status)
}
dbModel = dbModel.Where("is_delete = ?", 0)
// 总数
var count int64
err := dbModel.Count(&count).Error
@@ -90,7 +92,7 @@ func (Service flowApplyService) List(page request.PageReq, listReq FlowApplyList
// Detail 申请流程详情
func (Service flowApplyService) Detail(id int) (res FlowApplyResp, e error) {
var obj model.FlowApply
err := Service.db.Where("id = ?", id).Limit(1).First(&obj).Error
err := Service.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&obj).Error
if e = response.CheckErrDBNotRecord(err, "数据不存在!"); e != nil {
return
}
@@ -124,7 +126,7 @@ func (Service flowApplyService) Add(addReq FlowApplyAddReq) (e error) {
// Edit 申请流程编辑
func (Service flowApplyService) Edit(editReq FlowApplyEditReq) (e error) {
var obj model.FlowApply
err := Service.db.Where("id = ?", editReq.Id).Limit(1).First(&obj).Error
err := Service.db.Where("id = ? AND is_delete = ?", editReq.Id, 0).Limit(1).First(&obj).Error
// 校验
if e = response.CheckErrDBNotRecord(err, "数据不存在!"); e != nil {
return
@@ -142,7 +144,7 @@ func (Service flowApplyService) Edit(editReq FlowApplyEditReq) (e error) {
// Del 申请流程删除
func (Service flowApplyService) Del(id int) (e error) {
var obj model.FlowApply
err := Service.db.Where("id = ?", id).Limit(1).First(&obj).Error
err := Service.db.Where("id = ? AND is_delete = ?", id, 0).Limit(1).First(&obj).Error
// 校验
if e = response.CheckErrDBNotRecord(err, "数据不存在!"); e != nil {
return
@@ -150,8 +152,14 @@ func (Service flowApplyService) Del(id int) (e error) {
if e = response.CheckErr(err, "Del First err"); e != nil {
return
}
if obj.Status == 2 {
// 审批中不允许删除
e = errors.New("审批中不允许删除")
return
}
// 删除
err = Service.db.Delete(&obj).Error
e = response.CheckErr(err, "Del Delete err")
obj.IsDelete = 1
err = Service.db.Save(&obj).Error
e = response.CheckErr(err, "Del Save err")
return
}

View File

@@ -59,7 +59,10 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.3 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
@@ -72,6 +75,9 @@ require (
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca // indirect
github.com/xuri/excelize/v2 v2.8.0 // indirect
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.6.0 // indirect

View File

@@ -263,6 +263,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@@ -289,6 +291,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
@@ -345,6 +352,12 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca h1:uvPMDVyP7PXMMioYdyPH+0O+Ta/UO1WFfNYMO3Wz0eg=
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.8.0 h1:Vd4Qy809fupgp1v7X+nCS/MioeQmYVVzi495UCTqB7U=
github.com/xuri/excelize/v2 v2.8.0/go.mod h1:6iA2edBTKxKbZAa7X5bDhcCg51xdOn1Ar5sfoXRGrQg=
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a h1:Mw2VNrNNNjDtw68VsEj2+st+oCSn4Uz7vZw6TbhcV1o=
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -376,6 +389,7 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -393,6 +407,7 @@ golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUU
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -417,6 +432,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -455,7 +471,10 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -478,6 +497,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -534,6 +554,8 @@ golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -543,6 +565,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -597,6 +621,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -14,6 +14,7 @@ type FlowApply struct {
FlowProcessDataList string `gorm:"comment:'流程配置list数据'"` // 流程配置list数据
FormValue string `gorm:"comment:'表单值'"` // 表单值
Status int `gorm:"comment:'状态1待提交2审批中3审批完成4审批失败'"` // 状态0待提交1审批中2审批完成3审批失败
IsDelete int `gorm:"comment:'是否删除'"` // 是否删除
UpdateTime int64 `gorm:"autoUpdateTime;comment:'更新时间'"` // 更新时间
CreateTime int64 `gorm:"autoCreateTime;comment:'创建时间'"` // 创建时间
DeleteTime int64 `gorm:"comment:'删除时间'"` // 删除时间

View File

@@ -0,0 +1,97 @@
package excel
import (
"github.com/xuri/excelize/v2"
)
type Excel struct {
F *excelize.File // excel 对象
TitleStyle int // 表头样式
HeadStyle int // 表头样式
ContentStyle1 int // 主体样式1无背景色
ContentStyle2 int // 主体样式2有背景色
}
// 初始化
func ExcelInit() (e *Excel) {
e = &Excel{}
// excel构建
e.F = excelize.NewFile()
// 初始化样式
e.getTitleRowStyle()
e.getHeadRowStyle()
e.getDataRowStyle()
return e
}
// 获取边框样式
func getBorder() []excelize.Border {
return []excelize.Border{ // 边框
{Type: "top", Color: "000000", Style: 1},
{Type: "bottom", Color: "000000", Style: 1},
{Type: "left", Color: "000000", Style: 1},
{Type: "right", Color: "000000", Style: 1},
}
}
// 标题样式
func (e *Excel) getTitleRowStyle() {
e.TitleStyle, _ = e.F.NewStyle(&excelize.Style{
Alignment: &excelize.Alignment{ // 对齐方式
Horizontal: "center", // 水平对齐居中
Vertical: "center", // 垂直对齐居中
},
Fill: excelize.Fill{ // 背景颜色
Type: "pattern",
Color: []string{"#fff2cc"},
Pattern: 1,
},
Font: &excelize.Font{ // 字体
Bold: true,
Size: 16,
},
Border: getBorder(),
})
}
// 列头行样式
func (e *Excel) getHeadRowStyle() {
e.HeadStyle, _ = e.F.NewStyle(&excelize.Style{
Alignment: &excelize.Alignment{ // 对齐方式
Horizontal: "center", // 水平对齐居中
Vertical: "center", // 垂直对齐居中
WrapText: true, // 自动换行
},
Fill: excelize.Fill{ // 背景颜色
Type: "pattern",
Color: []string{"#FDE9D8"},
Pattern: 1,
},
Font: &excelize.Font{ // 字体
Bold: true,
Size: 14,
},
Border: getBorder(),
})
}
// 数据行样式
func (e *Excel) getDataRowStyle() {
style := excelize.Style{}
style.Border = getBorder()
style.Alignment = &excelize.Alignment{
Horizontal: "center", // 水平对齐居中
Vertical: "center", // 垂直对齐居中
WrapText: true, // 自动换行
}
style.Font = &excelize.Font{
Size: 12,
}
e.ContentStyle1, _ = e.F.NewStyle(&style)
style.Fill = excelize.Fill{ // 背景颜色
Type: "pattern",
Color: []string{"#cce7f5"},
Pattern: 1,
}
e.ContentStyle2, _ = e.F.NewStyle(&style)
}

View File

@@ -0,0 +1,254 @@
package excel
import (
"errors"
"fmt"
"net/http"
"reflect"
"sort"
"strconv"
"strings"
"github.com/xuri/excelize/v2"
)
// GetExcelColumnName 根据列数生成 Excel 列名
func GetExcelColumnName(columnNumber int) string {
columnName := ""
for columnNumber > 0 {
columnNumber--
columnName = fmt.Sprint('A'+columnNumber%26) + columnName
columnNumber /= 26
}
return columnName
}
// ExportExcel excel导出
func ExportExcel(sheet, title, fields string, isGhbj, isIgnore bool, list interface{}, changeHead map[string]string, e *Excel) (err error) {
index, _ := e.F.GetSheetIndex(sheet)
if index < 0 { // 如果sheet名称不存在
e.F.NewSheet(sheet)
}
// 构造excel表格
// 取目标对象的元素类型、字段类型和 tag
dataValue := reflect.ValueOf(list)
// 判断数据的类型
if dataValue.Kind() != reflect.Slice {
err = errors.New("invalid data type")
return
}
// 构造表头
endColName, dataRow, err := normalBuildTitle(e, sheet, title, fields, isIgnore, changeHead, dataValue)
if err != nil {
return
}
// 构造数据行
err = normalBuildDataRow(e, sheet, endColName, fields, dataRow, isGhbj, isIgnore, dataValue)
return
}
// ================================= 普通导出 =================================
// NormalDownLoad 导出excel并下载单个sheet
func NormalDownLoad(fileName, sheet, title string, isGhbj bool, list interface{}, res http.ResponseWriter) error {
f, err := NormalDynamicExport(list, sheet, title, "", isGhbj, false, nil)
if err != nil {
return err
}
DownLoadExcel(fileName, res, f)
return nil
}
// NormalDynamicDownLoad 动态导出excel并下载单个sheet
// isIgnore 是否忽略指定字段true 要忽略的字段 false 要导出的字段)
// fields 选择的字段多个字段用逗号隔开最后一个字段后面也要加逗号字段1,字段2,字段3,
// changeHead 要改变表头的字段,格式是{"字段1":"更改的表头1","字段2":"更改的表头2"}
func NormalDynamicDownLoad(fileName, sheet, title, fields string, isGhbj, isIgnore bool,
list interface{}, changeHead map[string]string, res http.ResponseWriter) error {
f, err := NormalDynamicExport(list, sheet, title, fields, isGhbj, isIgnore, changeHead)
if err != nil {
return err
}
DownLoadExcel(fileName, res, f)
return nil
}
// NormalDynamicExport 导出excel
// ** 需要在传入的结构体中的字段加上tagexcelize:"title:列头名称;index:列下标(从0开始);"
// list 需要导出的对象数组、sheet sheet名称、title 标题、isGhbj 是否设置隔行背景色
func NormalDynamicExport(list interface{}, sheet, title, fields string, isGhbj, isIgnore bool, changeHead map[string]string) (file *excelize.File, err error) {
e := ExcelInit()
err = ExportExcel(sheet, title, fields, isGhbj, isIgnore, list, changeHead, e)
if err != nil {
return
}
return e.F, err
}
// 构造表头endColName 最后一列的列名 dataRow 数据行开始的行号)
func normalBuildTitle(e *Excel, sheet, title, fields string, isIgnore bool, changeHead map[string]string, dataValue reflect.Value) (endColName string, dataRow int, err error) {
dataType := dataValue.Type().Elem() // 获取导入目标对象的类型信息
var exportTitle []ExcelTag // 遍历目标对象的字段
for i := 0; i < dataType.NumField(); i++ {
var excelTag ExcelTag
field := dataType.Field(i) // 获取字段信息和tag
tag := field.Tag.Get(ExcelTagKey)
if tag == "" { // 如果非导出则跳过
continue
}
if fields != "" { // 选择要导出或要忽略的字段
if isIgnore && strings.Contains(fields, field.Name+",") { // 忽略指定字段
continue
}
if !isIgnore && !strings.Contains(fields, field.Name+",") { // 导出指定字段
continue
}
}
err = excelTag.GetTag(tag)
if err != nil {
return
}
// 更改指定字段的表头标题
if changeHead != nil && changeHead[field.Name] != "" {
excelTag.Name = changeHead[field.Name]
}
exportTitle = append(exportTitle, excelTag)
}
// 排序
sort.Slice(exportTitle, func(i, j int) bool {
return exportTitle[i].Index < exportTitle[j].Index
})
var titleRowData []interface{} // 列头行
for i, colTitle := range exportTitle {
endColName := GetExcelColumnName(i + 1)
if colTitle.Width > 0 { // 根据给定的宽度设置列宽
_ = e.F.SetColWidth(sheet, endColName, endColName, float64(colTitle.Width))
} else {
_ = e.F.SetColWidth(sheet, endColName, endColName, float64(20)) // 默认宽度为20
}
titleRowData = append(titleRowData, colTitle.Name)
}
endColName = GetExcelColumnName(len(titleRowData)) // 根据列数生成 Excel 列名
if title != "" {
dataRow = 3 // 如果有title那么从第3行开始就是数据行第1行是title第2行是表头
e.F.SetCellValue(sheet, "A1", title)
e.F.MergeCell(sheet, "A1", endColName+"1") // 合并标题单元格
e.F.SetCellStyle(sheet, "A1", endColName+"1", e.TitleStyle)
e.F.SetRowHeight(sheet, 1, float64(30)) // 第一行行高
e.F.SetRowHeight(sheet, 2, float64(30)) // 第二行行高
e.F.SetCellStyle(sheet, "A2", endColName+"2", e.HeadStyle)
if err = e.F.SetSheetRow(sheet, "A2", &titleRowData); err != nil {
return
}
} else {
dataRow = 2 // 如果没有title那么从第2行开始就是数据行第1行是表头
e.F.SetRowHeight(sheet, 1, float64(30))
e.F.SetCellStyle(sheet, "A1", endColName+"1", e.HeadStyle)
if err = e.F.SetSheetRow(sheet, "A1", &titleRowData); err != nil {
return
}
}
return
}
// 构造数据行
func normalBuildDataRow(e *Excel, sheet, endColName, fields string, row int, isGhbj, isIgnore bool, dataValue reflect.Value) (err error) {
//实时写入数据
for i := 0; i < dataValue.Len(); i++ {
startCol := fmt.Sprintf("A%d", row)
endCol := fmt.Sprintf("%s%d", endColName, row)
item := dataValue.Index(i)
typ := item.Type()
num := item.NumField()
var exportRow []ExcelTag
maxLen := 0 // 记录这一行中,数据最多的单元格的值的长度
//遍历结构体的所有字段
for j := 0; j < num; j++ {
dataField := typ.Field(j) //获取到struct标签需要通过reflect.Type来获取tag标签的值
tagVal := dataField.Tag.Get(ExcelTagKey)
if tagVal == "" { // 如果非导出则跳过
continue
}
if fields != "" { // 选择要导出或要忽略的字段
if isIgnore && strings.Contains(fields, dataField.Name+",") { // 忽略指定字段
continue
}
if !isIgnore && !strings.Contains(fields, dataField.Name+",") { // 导出指定字段
continue
}
}
var dataCol ExcelTag
err = dataCol.GetTag(tagVal)
fieldData := item.FieldByName(dataField.Name) // 取字段值
if fieldData.Type().String() == "string" { // string类型的才计算长度
rwsTemp := fieldData.Len() // 当前单元格内容的长度
if rwsTemp > maxLen { //这里取每一行中的每一列字符长度最大的那一列的字符
maxLen = rwsTemp
}
}
// 替换
if dataCol.Replace != "" {
split := strings.Split(dataCol.Replace, ",")
for j := range split {
s := strings.Split(split[j], "_") // 根据下划线进行分割格式需要替换的内容_替换后的内容
value := fieldData.String()
if strings.Contains(fieldData.Type().String(), "int") {
value = strconv.Itoa(int(fieldData.Int()))
} else if fieldData.Type().String() == "bool" {
value = strconv.FormatBool(fieldData.Bool())
} else if strings.Contains(fieldData.Type().String(), "float") {
value = strconv.FormatFloat(fieldData.Float(), 'f', -1, 64)
}
if s[0] == value {
dataCol.Value = s[1]
}
}
} else {
dataCol.Value = fieldData
}
if err != nil {
return
}
exportRow = append(exportRow, dataCol)
}
// 排序
sort.Slice(exportRow, func(i, j int) bool {
return exportRow[i].Index < exportRow[j].Index
})
var rowData []interface{} // 数据列
for _, colTitle := range exportRow {
rowData = append(rowData, colTitle.Value)
}
if isGhbj && row%2 == 0 {
_ = e.F.SetCellStyle(sheet, startCol, endCol, e.ContentStyle2)
} else {
_ = e.F.SetCellStyle(sheet, startCol, endCol, e.ContentStyle1)
}
if maxLen > 25 { // 自适应行高
d := maxLen / 25
f := 25 * d
_ = e.F.SetRowHeight(sheet, row, float64(f))
} else {
_ = e.F.SetRowHeight(sheet, row, float64(25)) // 默认行高25
}
if err = e.F.SetSheetRow(sheet, startCol, &rowData); err != nil {
return
}
row++
}
return
}
// 下载
func DownLoadExcel(fileName string, res http.ResponseWriter, file *excelize.File) {
// 设置响应头
res.Header().Set("Content-Type", "text/html; charset=UTF-8")
res.Header().Set("Content-Type", "application/octet-stream")
res.Header().Set("Content-Disposition", "attachment; filename="+fileName+".xlsx")
res.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
err := file.Write(res) // 写入Excel文件内容到响应体
if err != nil {
http.Error(res, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@@ -0,0 +1,121 @@
package excel
import (
"errors"
"reflect"
"strconv"
"github.com/xuri/excelize/v2"
)
// ImportExcel 导入数据单个sheet
// 需要在传入的结构体中的字段加上tagexcel:"title:列头名称;"
// f 获取到的excel对象、dst 导入目标对象【传指针】
// headIndex 表头的索引从0开始用于获取表头名字
// startRow 头行行数从第startRow+1行开始扫
func ImportExcel(f *excelize.File, dst interface{}, headIndex, startRow int) (err error) {
sheetName := f.GetSheetName(0) // 单个sheet时默认读取第一个sheet
err = importData(f, dst, sheetName, headIndex, startRow)
return
}
// ImportBySheet 导入数据读取指定sheetsheetName Sheet名称
func ImportBySheet(f *excelize.File, dst interface{}, sheetName string, headIndex, startRow int) (err error) {
// 当需要读取多个sheet时可以通过下面的方式来调用 ImportBySheet 这个函数
//sheetList := f.GetSheetList()
//for _, sheetName := range sheetList {
// ImportBySheet(f,dst,sheetName,headIndex,startRow)
//}
err = importData(f, dst, sheetName, headIndex, startRow)
return
}
// 判断数组中是否包含指定元素
func IsContain(items interface{}, item interface{}) bool {
switch items.(type) {
case []int:
intArr := items.([]int)
for _, value := range intArr {
if value == item.(int) {
return true
}
}
case []string:
strArr := items.([]string)
for _, value := range strArr {
if value == item.(string) {
return true
}
}
default:
return false
}
return false
}
// 解析数据
func importData(f *excelize.File, dst interface{}, sheetName string, headIndex, startRow int) (err error) {
rows, err := f.GetRows(sheetName) // 获取所有行
if err != nil {
err = errors.New(sheetName + "工作表不存在")
return
}
dataValue := reflect.ValueOf(dst) // 取目标对象的元素类型、字段类型和 tag
// 判断数据的类型
if dataValue.Kind() != reflect.Ptr || dataValue.Elem().Kind() != reflect.Slice {
err = errors.New("invalid data type")
}
heads := []string{} // 表头
dataType := dataValue.Elem().Type().Elem() // 获取导入目标对象的类型信息
// 遍历行,解析数据并填充到目标对象中
for rowIndex, row := range rows {
if rowIndex == headIndex {
heads = row
}
if rowIndex < startRow { // 跳过头行
continue
}
newData := reflect.New(dataType).Elem() // 创建新的目标对象
// 遍历目标对象的字段
for i := 0; i < dataType.NumField(); i++ {
// 这里要用构造函数构造函数里指定了Index默认值为-1当目标结构体的tag没有指定index的话那么 excelTag.Index 就一直为0
// 那么 row[excelizeIndex] 就始终是 row[0],始终拿的是第一列的数据
var excelTag = NewExcelTag()
field := dataType.Field(i) // 获取字段信息和tag
tag := field.Tag.Get(ExcelTagKey)
if tag == "" { // 如果tag不存在则跳过
continue
}
err = excelTag.GetTag(tag)
if err != nil {
return
}
cellValue := ""
if excelTag.Index >= 0 { // 当tag里指定了index时根据这个index来拿数据
excelizeIndex := excelTag.Index // 解析tag的值
if excelizeIndex >= len(row) { // 防止下标越界
continue
}
cellValue = row[excelizeIndex] // 获取单元格的值
} else { // 否则根据表头名称来拿数据
if IsContain(heads, excelTag.Name) { // 当tag里的表头名称和excel表格里面的表头名称相匹配时
if i >= len(row) { // 防止下标越界
continue
}
cellValue = row[i] // 获取单元格的值
}
}
// 根据字段类型设置值
switch field.Type.Kind() {
case reflect.Int:
v, _ := strconv.ParseInt(cellValue, 10, 64)
newData.Field(i).SetInt(v)
case reflect.String:
newData.Field(i).SetString(cellValue)
}
}
// 将新的目标对象添加到导入目标对象的slice中
dataValue.Elem().Set(reflect.Append(dataValue.Elem(), newData))
}
return
}

View File

@@ -0,0 +1,74 @@
package excel
import (
"errors"
"regexp"
"strconv"
"strings"
)
// 定义正则表达式模式
const (
ExcelTagKey = "excel"
Pattern = "name:(.*?);|index:(.*?);|width:(.*?);|needMerge:(.*?);|replace:(.*?);"
)
type ExcelTag struct {
Value interface{}
Name string // 表头标题
Index int // 列下标(从0开始)
Width int // 列宽
NeedMerge bool // 是否需要合并
Replace string // 替换需要替换的内容_替换后的内容。比如1_未开始 ==> 表示1替换为未开始
}
// 构造函数,返回一个带有默认值的 ExcelTag 实例
func NewExcelTag() ExcelTag {
return ExcelTag{
// 导入时会根据这个下标来拿单元格的值当目标结构体字段没有设置index时
// 解析字段tag值时Index没读到就一直默认为0拿单元格的值时就始终拿的是第一列的值
Index: -1, // 设置 Index 的默认值为 -1
}
}
// 读取字段tag值
func (e *ExcelTag) GetTag(tag string) (err error) {
// 编译正则表达式
re := regexp.MustCompile(Pattern)
matches := re.FindAllStringSubmatch(tag, -1)
if len(matches) > 0 {
for _, match := range matches {
for i, val := range match {
if i != 0 && val != "" {
e.setValue(match, val)
}
}
}
} else {
err = errors.New("未匹配到值")
return
}
return
}
// 设置ExcelTag 对应字段的值
func (e *ExcelTag) setValue(tag []string, value string) {
if strings.Contains(tag[0], "name") {
e.Name = value
}
if strings.Contains(tag[0], "index") {
v, _ := strconv.ParseInt(value, 10, 8)
e.Index = int(v)
}
if strings.Contains(tag[0], "width") {
v, _ := strconv.ParseInt(value, 10, 8)
e.Width = int(v)
}
if strings.Contains(tag[0], "needMerge") {
v, _ := strconv.ParseBool(value)
e.NeedMerge = v
}
if strings.Contains(tag[0], "replace") {
e.Replace = value
}
}

View File

@@ -0,0 +1,68 @@
// 测试源码的文件名以 _test.go 结尾。
// 测试函数的函数名以 Test 开头。
// 函数签名为 func (t *testing.T)。
// https://blog.csdn.net/weixin_43165220/article/details/132939884
package excel
import (
"fmt"
"testing"
"github.com/xuri/excelize/v2"
)
type Test struct {
Id string `excel:"name:用户账号;"`
Name string `excel:"name:用户姓名;index:1;"`
Email string `excel:"name:用户邮箱;width:25;"`
Com string `excel:"name:所属公司;"`
Dept string `excel:"name:所在部门;"`
RoleKey string `excel:"name:角色代码;"`
RoleName string `excel:"name:角色名称;replace:1_超级管理员,2_普通用户;"`
Remark string `excel:"name:备注;width:40;"`
}
// 导出
func TestExport(t *testing.T) {
var testList = []Test{
{"fuhua", "符华", "fuhua@123.com", "太虚剑派", "开发部", "CJGLY", "1", "备注备注"},
{"baiye", "白夜", "baiye@123.com", "天命科技有限公司", "执行部", "PTYG", "2", ""},
{"chiling", "炽翎", "chiling@123.com", "太虚剑派", "行政部", "PTYG", "2", "备注备注备注备注"},
{"yunmo", "云墨", "yunmo@123.com", "太虚剑派", "财务部", "CJGLY", "1", ""},
{"yuelun", "月轮", "yuelun@123.com", "天命科技有限公司", "执行部", "CJGLY", "1", ""},
{"xunyu", "迅羽",
"xunyu@123.com哈哈哈哈哈哈哈哈这里是最大行高测试哈哈哈哈哈哈哈哈这11111111111里是最大行高测试哈哈哈哈哈哈哈哈这里是最大行高测试",
"天命科技有限公司", "开发部", "PTYG", "2",
"备注备注备注备注com哈哈哈哈哈哈哈哈这里是最大行高测试哈哈哈哈哈哈哈哈这里是最大行高测试哈哈哈哈哈哈哈哈这里是最大行高测里是最大行高测试哈哈哈哈哈哈哈哈这里是最大行高测试"},
}
changeHead := map[string]string{"Id": "账号", "Name": "真实姓名"}
//f, err := excel.NormalExport(testList, "Sheet1", "用户信息", "Id,Email,", true, true, changeHead)
f, err := NormalDynamicExport(testList, "Sheet1", "用户信息", "", true, false, changeHead)
if err != nil {
fmt.Println(err)
return
}
f.Path = "./测试.xlsx"
if err := f.Save(); err != nil {
fmt.Println(err)
return
}
}
// 导入
func TestImports(t *testing.T) {
f, err := excelize.OpenFile("./测试.xlsx")
if err != nil {
fmt.Println("文件打开失败")
}
importList := []Test{}
err = ImportExcel(f, &importList, 1, 2)
if err != nil {
fmt.Println(err)
}
for _, t := range importList {
fmt.Println(t)
}
}

Binary file not shown.