正在显示
9 个修改的文件
包含
320 行增加
和
112 行删除
| @@ -9,6 +9,7 @@ import ( | @@ -9,6 +9,7 @@ import ( | ||
| 9 | serveaudit "oppmg/services/audit" | 9 | serveaudit "oppmg/services/audit" |
| 10 | servecommon "oppmg/services/common" | 10 | servecommon "oppmg/services/common" |
| 11 | serverbac "oppmg/services/rbac" | 11 | serverbac "oppmg/services/rbac" |
| 12 | + "oppmg/utils/exceltool" | ||
| 12 | "strconv" | 13 | "strconv" |
| 13 | "time" | 14 | "time" |
| 14 | ) | 15 | ) |
| @@ -370,3 +371,55 @@ func (c *AuditController) ChanceStoreChange() { | @@ -370,3 +371,55 @@ func (c *AuditController) ChanceStoreChange() { | ||
| 370 | msg = protocol.NewReturnResponse(nil, nil) | 371 | msg = protocol.NewReturnResponse(nil, nil) |
| 371 | return | 372 | return |
| 372 | } | 373 | } |
| 374 | + | ||
| 375 | +//ChanceDataExcel 机会列表数据导出为excel | ||
| 376 | +//@router /v1/chance/export_chance_list | ||
| 377 | +func (c *AuditController) ChanceDataExcel() { | ||
| 378 | + var ( | ||
| 379 | + param protocol.RequestAuditList | ||
| 380 | + msg *protocol.ResponseMessage | ||
| 381 | + ) | ||
| 382 | + param.Status = -1 | ||
| 383 | + param.ReviewStatus = -1 | ||
| 384 | + param.PublishStatus = -1 | ||
| 385 | + if err := json.Unmarshal(c.Ctx.Input.RequestBody, ¶m); err != nil { | ||
| 386 | + log.Error("json 解析失败", err) | ||
| 387 | + msg = protocol.BadRequestParam("1") | ||
| 388 | + c.ResposeJson(msg) | ||
| 389 | + return | ||
| 390 | + } | ||
| 391 | + beginTime, err := time.ParseInLocation("2006-01-02", param.CreateTimeBeginS, time.Local) | ||
| 392 | + if err == nil && len(param.CreateTimeBeginS) > 0 { | ||
| 393 | + param.CreateTimeBegin = beginTime.Unix() | ||
| 394 | + } | ||
| 395 | + endTime, err := time.ParseInLocation("2006-01-02", param.CreateTimeEndS, time.Local) | ||
| 396 | + if err == nil && len(param.CreateTimeEndS) > 0 { | ||
| 397 | + param.CreateTimeEnd = endTime.Unix() + 86399 | ||
| 398 | + } | ||
| 399 | + fmt.Println(err, param.CreateTimeBegin, param.CreateTimeEnd) | ||
| 400 | + if len(param.StatusS) > 0 { | ||
| 401 | + param.Status, _ = strconv.Atoi(param.StatusS) | ||
| 402 | + } | ||
| 403 | + if len(param.ReviewStatusS) > 0 { | ||
| 404 | + param.ReviewStatus, _ = strconv.Atoi(param.ReviewStatusS) | ||
| 405 | + } | ||
| 406 | + if len(param.PublishStatusS) > 0 { | ||
| 407 | + param.PublishStatus, _ = strconv.Atoi(param.PublishStatusS) | ||
| 408 | + } | ||
| 409 | + uid := c.GetUserId() | ||
| 410 | + companyId := c.GetCompanyId() | ||
| 411 | + param.PageIndex = 1 | ||
| 412 | + param.PageIndex = 10 | ||
| 413 | + sourceData, excelHead := serveaudit.GetAuditListForExcel(param, companyId, uid) | ||
| 414 | + xlsMaker := exceltool.NewExcelMaker() | ||
| 415 | + xlsMaker.SetListHead(excelHead) | ||
| 416 | + xlsMaker.MakeListExcelForBeego(sourceData) | ||
| 417 | + err = xlsMaker.Xlsx.Write(c.Ctx.Output.Context.ResponseWriter) | ||
| 418 | + if err != nil { | ||
| 419 | + msg = protocol.BadRequestParam("1") | ||
| 420 | + c.ResposeJson(msg) | ||
| 421 | + return | ||
| 422 | + } | ||
| 423 | + c.ResponseExcelByFile(c.Ctx, xlsMaker) | ||
| 424 | + return | ||
| 425 | +} |
| @@ -108,7 +108,7 @@ func (this *BaseController) Valid(obj interface{}) (result bool, msg *protocol.R | @@ -108,7 +108,7 @@ func (this *BaseController) Valid(obj interface{}) (result bool, msg *protocol.R | ||
| 108 | } | 108 | } |
| 109 | 109 | ||
| 110 | func (this *BaseController) ResponseExcelByFile(ctx *context.Context, excelMaker *exceltool.ExcelMaker) error { | 110 | func (this *BaseController) ResponseExcelByFile(ctx *context.Context, excelMaker *exceltool.ExcelMaker) error { |
| 111 | - ctx.Output.Header("Content-Disposition", "attachment; filename="+excelMaker.FileName) | 111 | + ctx.Output.Header("Content-Disposition", "attachment; filename="+excelMaker.GetFileName()) |
| 112 | ctx.Output.Header("Content-Description", "File Transfer") | 112 | ctx.Output.Header("Content-Description", "File Transfer") |
| 113 | ctx.Output.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | 113 | ctx.Output.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") |
| 114 | ctx.Output.Header("Content-Transfer-Encoding", "binary") | 114 | ctx.Output.Header("Content-Transfer-Encoding", "binary") |
| @@ -75,15 +75,6 @@ func DeleteChanceType(id int) (err error) { | @@ -75,15 +75,6 @@ func DeleteChanceType(id int) (err error) { | ||
| 75 | return | 75 | return |
| 76 | } | 76 | } |
| 77 | 77 | ||
| 78 | -func GetChanceTypeAll() (v []*ChanceType, err error) { | ||
| 79 | - o := orm.NewOrm() | ||
| 80 | - sql := "select * from chance_type " | ||
| 81 | - if _, err = o.Raw(sql).QueryRows(&v); err == nil { | ||
| 82 | - return | ||
| 83 | - } | ||
| 84 | - return | ||
| 85 | -} | ||
| 86 | - | ||
| 87 | func GetChanceTypeByCompany(companyId int) (v []*ChanceType, err error) { | 78 | func GetChanceTypeByCompany(companyId int) (v []*ChanceType, err error) { |
| 88 | o := orm.NewOrm() | 79 | o := orm.NewOrm() |
| 89 | sql := "select * from chance_type where company_id=? order by sort_num" | 80 | sql := "select * from chance_type where company_id=? order by sort_num" |
| @@ -116,6 +116,7 @@ func init() { | @@ -116,6 +116,7 @@ func init() { | ||
| 116 | beego.NSRouter("/reserve_type/delete", &controllers.AuditController{}, "post:DeleteReserveType"), | 116 | beego.NSRouter("/reserve_type/delete", &controllers.AuditController{}, "post:DeleteReserveType"), |
| 117 | beego.NSRouter("/reserve_type/edit", &controllers.AuditController{}, "post:EditReserveType"), | 117 | beego.NSRouter("/reserve_type/edit", &controllers.AuditController{}, "post:EditReserveType"), |
| 118 | beego.NSRouter("/store/change", &controllers.AuditController{}, "post:ChanceStoreChange"), | 118 | beego.NSRouter("/store/change", &controllers.AuditController{}, "post:ChanceStoreChange"), |
| 119 | + beego.NSRouter("/export_chance_list", &controllers.AuditController{}, "post:ChanceDataExcel"), | ||
| 119 | ), | 120 | ), |
| 120 | beego.NSNamespace("/rank", | 121 | beego.NSNamespace("/rank", |
| 121 | beego.NSRouter("/type/list", &controllers.RankController{}, "post:GetRankType"), | 122 | beego.NSRouter("/type/list", &controllers.RankController{}, "post:GetRankType"), |
| @@ -139,51 +139,17 @@ func buildSqlForAuditList(usercompanyid int64, companyid int64, userid int64) st | @@ -139,51 +139,17 @@ func buildSqlForAuditList(usercompanyid int64, companyid int64, userid int64) st | ||
| 139 | return fmt.Sprintf(allsql, strings.Join(sqlslice, " UNION ")) | 139 | return fmt.Sprintf(allsql, strings.Join(sqlslice, " UNION ")) |
| 140 | } | 140 | } |
| 141 | 141 | ||
| 142 | -func GetAuditList(param protocol.RequestAuditList, companyid int64, userid int64) (protocol.ResponseAuditList, error) { | ||
| 143 | - type SqlData struct { | ||
| 144 | - Id int64 `orm:"column(id)"` | ||
| 145 | - UserId int64 `orm:"column(user_id)"` | ||
| 146 | - NickName string `orm:"column(nick_name)"` | ||
| 147 | - DepartmentId int64 `orm:"column(department_id)"` | ||
| 148 | - AuditTemplateId int64 `orm:"column(audit_template_id)"` | ||
| 149 | - ChanceTypeId int `orm:"column(chance_type_id)"` | ||
| 150 | - PublishStatus int `orm:"column(publish_status)"` | ||
| 151 | - CreateAt string `orm:"column(create_at)"` | ||
| 152 | - ReviewStatus int8 `orm:"column(review_status)"` | ||
| 153 | - Status int8 `orm:"column(status)"` | ||
| 154 | - DiscoveryScore string `orm:"column(discovery_score)"` | ||
| 155 | - CommentTotal string `orm:"column(comment_total)"` | ||
| 156 | - Code string `orm:"column(code)"` | ||
| 157 | - StoreType int8 `orm:"column(type)"` | ||
| 158 | - ReserveTypeId int `orm:"column(reserve_type_id)"` | ||
| 159 | - } | 142 | +func GetAuditListSql(param protocol.RequestAuditList, companyid int64, userid int64, ucompanyid int64) (coutsql string, |
| 143 | + datasql string, cond []interface{}) { | ||
| 160 | var ( | 144 | var ( |
| 161 | - datasql = strings.Builder{} | ||
| 162 | - countsql = strings.Builder{} | ||
| 163 | - data []SqlData | ||
| 164 | - cnt int | ||
| 165 | - err error | ||
| 166 | - cond []interface{} | 145 | + datasqlBuild = strings.Builder{} |
| 146 | + countsqlBuild = strings.Builder{} | ||
| 167 | sqlFromPermission string | 147 | sqlFromPermission string |
| 168 | ) | 148 | ) |
| 169 | - | ||
| 170 | - returnData := protocol.ResponseAuditList{ | ||
| 171 | - ResponsePageInfo: protocol.ResponsePageInfo{ | ||
| 172 | - TotalPage: 0, | ||
| 173 | - CurrentPage: param.PageIndex, | ||
| 174 | - }, | ||
| 175 | - List: make([]protocol.RspAuditList, 0), | ||
| 176 | - } | ||
| 177 | - usercompany, err := models.GetUserCompanyBy(userid, companyid) | ||
| 178 | - if err != nil { | ||
| 179 | - log.Error("GetUserCompanyBy(userid, companyid) err:%s", err) | ||
| 180 | - return returnData, protocol.NewErrWithMessage("1") | ||
| 181 | - } | ||
| 182 | - | ||
| 183 | s1 := `SELECT a.id,a.department_id,a.audit_template_id,a.chance_type_id | 149 | s1 := `SELECT a.id,a.department_id,a.audit_template_id,a.chance_type_id |
| 184 | ,a.publish_status,a.create_at,a.review_status,a.status | 150 | ,a.publish_status,a.create_at,a.review_status,a.status |
| 185 | ,a.discovery_score,a.comment_total ,a.code,d.nick_name,d.id as user_id | 151 | ,a.discovery_score,a.comment_total ,a.code,d.nick_name,d.id as user_id |
| 186 | - ,a.type,a.reserve_type_id | 152 | + ,a.type,a.reserve_type_id,a.source_content |
| 187 | FROM chance AS a | 153 | FROM chance AS a |
| 188 | JOIN user_company AS c ON c.id = a.user_id | 154 | JOIN user_company AS c ON c.id = a.user_id |
| 189 | JOIN user AS d ON c.user_id = d.id | 155 | JOIN user AS d ON c.user_id = d.id |
| @@ -197,7 +163,7 @@ func GetAuditList(param protocol.RequestAuditList, companyid int64, userid int64 | @@ -197,7 +163,7 @@ func GetAuditList(param protocol.RequestAuditList, companyid int64, userid int64 | ||
| 197 | if companyinfo, err := models.GetCompanyById(companyid); err == nil { | 163 | if companyinfo, err := models.GetCompanyById(companyid); err == nil { |
| 198 | //非主管进行权限过滤 | 164 | //非主管进行权限过滤 |
| 199 | if companyinfo.AdminId != userid { | 165 | if companyinfo.AdminId != userid { |
| 200 | - sqlFromPermission = buildSqlForAuditList(usercompany.Id, usercompany.CompanyId, usercompany.UserId) | 166 | + sqlFromPermission = buildSqlForAuditList(ucompanyid, companyid, userid) |
| 201 | } | 167 | } |
| 202 | } | 168 | } |
| 203 | if len(sqlFromPermission) > 0 { | 169 | if len(sqlFromPermission) > 0 { |
| @@ -208,90 +174,134 @@ func GetAuditList(param protocol.RequestAuditList, companyid int64, userid int64 | @@ -208,90 +174,134 @@ func GetAuditList(param protocol.RequestAuditList, companyid int64, userid int64 | ||
| 208 | s1 = fmt.Sprintf(s1, "") | 174 | s1 = fmt.Sprintf(s1, "") |
| 209 | s2 = fmt.Sprintf(s2, "") | 175 | s2 = fmt.Sprintf(s2, "") |
| 210 | } | 176 | } |
| 211 | - datasql.WriteString(s1) | ||
| 212 | - countsql.WriteString(s2) | 177 | + datasqlBuild.WriteString(s1) |
| 178 | + countsqlBuild.WriteString(s2) | ||
| 213 | cond = append(cond, companyid) | 179 | cond = append(cond, companyid) |
| 214 | if param.ChanceTypeId > 0 { | 180 | if param.ChanceTypeId > 0 { |
| 215 | //一级分类过滤 | 181 | //一级分类过滤 |
| 216 | cond = append(cond, param.ChanceTypeId) | 182 | cond = append(cond, param.ChanceTypeId) |
| 217 | s := ` And a.chance_type_id=? ` | 183 | s := ` And a.chance_type_id=? ` |
| 218 | - datasql.WriteString(s) | ||
| 219 | - countsql.WriteString(s) | 184 | + datasqlBuild.WriteString(s) |
| 185 | + countsqlBuild.WriteString(s) | ||
| 220 | } | 186 | } |
| 221 | if param.TempalteId > 0 { | 187 | if param.TempalteId > 0 { |
| 222 | cond = append(cond, param.TempalteId) | 188 | cond = append(cond, param.TempalteId) |
| 223 | s := ` AND a.audit_template_id=? ` | 189 | s := ` AND a.audit_template_id=? ` |
| 224 | - datasql.WriteString(s) | ||
| 225 | - countsql.WriteString(s) | 190 | + datasqlBuild.WriteString(s) |
| 191 | + countsqlBuild.WriteString(s) | ||
| 226 | } | 192 | } |
| 227 | if param.PublishStatus >= 0 { | 193 | if param.PublishStatus >= 0 { |
| 228 | cond = append(cond, param.PublishStatus) | 194 | cond = append(cond, param.PublishStatus) |
| 229 | s := ` And a.publish_status=? ` | 195 | s := ` And a.publish_status=? ` |
| 230 | - datasql.WriteString(s) | ||
| 231 | - countsql.WriteString(s) | 196 | + datasqlBuild.WriteString(s) |
| 197 | + countsqlBuild.WriteString(s) | ||
| 232 | } | 198 | } |
| 233 | if param.Status >= 0 { | 199 | if param.Status >= 0 { |
| 234 | cond = append(cond, param.Status) | 200 | cond = append(cond, param.Status) |
| 235 | s := ` AND a.status=? ` | 201 | s := ` AND a.status=? ` |
| 236 | - datasql.WriteString(s) | ||
| 237 | - countsql.WriteString(s) | 202 | + datasqlBuild.WriteString(s) |
| 203 | + countsqlBuild.WriteString(s) | ||
| 238 | } | 204 | } |
| 239 | if param.ReviewStatus >= 0 { | 205 | if param.ReviewStatus >= 0 { |
| 240 | cond = append(cond, param.ReviewStatus) | 206 | cond = append(cond, param.ReviewStatus) |
| 241 | s := ` AND a.review_status=? ` | 207 | s := ` AND a.review_status=? ` |
| 242 | - datasql.WriteString(s) | ||
| 243 | - countsql.WriteString(s) | 208 | + datasqlBuild.WriteString(s) |
| 209 | + countsqlBuild.WriteString(s) | ||
| 244 | } | 210 | } |
| 245 | if param.CreateTimeBegin > 0 { | 211 | if param.CreateTimeBegin > 0 { |
| 246 | cond = append(cond, param.CreateTimeBegin) | 212 | cond = append(cond, param.CreateTimeBegin) |
| 247 | s := ` AND UNIX_TIMESTAMP(a.create_at)>=? ` | 213 | s := ` AND UNIX_TIMESTAMP(a.create_at)>=? ` |
| 248 | - datasql.WriteString(s) | ||
| 249 | - countsql.WriteString(s) | 214 | + datasqlBuild.WriteString(s) |
| 215 | + countsqlBuild.WriteString(s) | ||
| 250 | } | 216 | } |
| 251 | if param.CreateTimeEnd > 0 { | 217 | if param.CreateTimeEnd > 0 { |
| 252 | cond = append(cond, param.CreateTimeEnd) | 218 | cond = append(cond, param.CreateTimeEnd) |
| 253 | s := ` AND UNIX_TIMESTAMP(a.create_at)<=? ` | 219 | s := ` AND UNIX_TIMESTAMP(a.create_at)<=? ` |
| 254 | - datasql.WriteString(s) | ||
| 255 | - countsql.WriteString(s) | 220 | + datasqlBuild.WriteString(s) |
| 221 | + countsqlBuild.WriteString(s) | ||
| 256 | } | 222 | } |
| 257 | if len(param.Code) > 0 { | 223 | if len(param.Code) > 0 { |
| 258 | cond = append(cond, "%"+param.Code+"%") | 224 | cond = append(cond, "%"+param.Code+"%") |
| 259 | s := ` And a.code like ? ` | 225 | s := ` And a.code like ? ` |
| 260 | - datasql.WriteString(s) | ||
| 261 | - countsql.WriteString(s) | 226 | + datasqlBuild.WriteString(s) |
| 227 | + countsqlBuild.WriteString(s) | ||
| 262 | } | 228 | } |
| 263 | if len(param.UserName) > 0 { | 229 | if len(param.UserName) > 0 { |
| 264 | cond = append(cond, "%"+param.UserName+"%") | 230 | cond = append(cond, "%"+param.UserName+"%") |
| 265 | s := ` And d.nick_name like ? ` | 231 | s := ` And d.nick_name like ? ` |
| 266 | - datasql.WriteString(s) | ||
| 267 | - countsql.WriteString(s) | 232 | + datasqlBuild.WriteString(s) |
| 233 | + countsqlBuild.WriteString(s) | ||
| 268 | } | 234 | } |
| 269 | if param.DepartmentID > 0 { | 235 | if param.DepartmentID > 0 { |
| 270 | //提交的部门 | 236 | //提交的部门 |
| 271 | cond = append(cond, param.DepartmentID) | 237 | cond = append(cond, param.DepartmentID) |
| 272 | s := ` And a.department_id=? ` | 238 | s := ` And a.department_id=? ` |
| 273 | - datasql.WriteString(s) | ||
| 274 | - countsql.WriteString(s) | 239 | + datasqlBuild.WriteString(s) |
| 240 | + countsqlBuild.WriteString(s) | ||
| 275 | } | 241 | } |
| 276 | if param.ReserveType > 0 { | 242 | if param.ReserveType > 0 { |
| 277 | cond = append(cond, param.ReserveType) | 243 | cond = append(cond, param.ReserveType) |
| 278 | s := ` And a.reserve_type_id=? ` | 244 | s := ` And a.reserve_type_id=? ` |
| 279 | - datasql.WriteString(s) | ||
| 280 | - countsql.WriteString(s) | 245 | + datasqlBuild.WriteString(s) |
| 246 | + countsqlBuild.WriteString(s) | ||
| 281 | } | 247 | } |
| 282 | if len(param.StoreType) > 0 { | 248 | if len(param.StoreType) > 0 { |
| 283 | storeType, _ := strconv.Atoi(param.StoreType) | 249 | storeType, _ := strconv.Atoi(param.StoreType) |
| 284 | if storeType >= 0 { | 250 | if storeType >= 0 { |
| 285 | cond = append(cond, storeType) | 251 | cond = append(cond, storeType) |
| 286 | s := ` And a.type=? ` | 252 | s := ` And a.type=? ` |
| 287 | - datasql.WriteString(s) | ||
| 288 | - countsql.WriteString(s) | 253 | + datasqlBuild.WriteString(s) |
| 254 | + countsqlBuild.WriteString(s) | ||
| 289 | } | 255 | } |
| 290 | } | 256 | } |
| 291 | 257 | ||
| 292 | dataStart := (param.PageIndex - 1) * param.PageSize | 258 | dataStart := (param.PageIndex - 1) * param.PageSize |
| 293 | - datasql.WriteString(fmt.Sprintf(` ORDER BY a.create_at DESC limit %d,%d `, dataStart, param.PageSize)) | ||
| 294 | - err = utils.ExecuteQueryOne(&cnt, countsql.String(), cond...) | 259 | + datasqlBuild.WriteString(fmt.Sprintf(` ORDER BY a.create_at DESC limit %d,%d `, dataStart, param.PageSize)) |
| 260 | + return countsqlBuild.String(), datasqlBuild.String(), cond | ||
| 261 | +} | ||
| 262 | + | ||
| 263 | +func GetAuditList(param protocol.RequestAuditList, companyid int64, userid int64) (protocol.ResponseAuditList, error) { | ||
| 264 | + type SqlData struct { | ||
| 265 | + Id int64 `orm:"column(id)"` | ||
| 266 | + UserId int64 `orm:"column(user_id)"` | ||
| 267 | + NickName string `orm:"column(nick_name)"` | ||
| 268 | + DepartmentId int64 `orm:"column(department_id)"` | ||
| 269 | + AuditTemplateId int64 `orm:"column(audit_template_id)"` | ||
| 270 | + ChanceTypeId int `orm:"column(chance_type_id)"` | ||
| 271 | + PublishStatus int `orm:"column(publish_status)"` | ||
| 272 | + CreateAt string `orm:"column(create_at)"` | ||
| 273 | + ReviewStatus int8 `orm:"column(review_status)"` | ||
| 274 | + Status int8 `orm:"column(status)"` | ||
| 275 | + DiscoveryScore string `orm:"column(discovery_score)"` | ||
| 276 | + CommentTotal string `orm:"column(comment_total)"` | ||
| 277 | + Code string `orm:"column(code)"` | ||
| 278 | + StoreType int8 `orm:"column(type)"` | ||
| 279 | + ReserveTypeId int `orm:"column(reserve_type_id)"` | ||
| 280 | + } | ||
| 281 | + var ( | ||
| 282 | + datasql string | ||
| 283 | + countsql string | ||
| 284 | + data []SqlData | ||
| 285 | + cnt int | ||
| 286 | + err error | ||
| 287 | + cond []interface{} | ||
| 288 | + ) | ||
| 289 | + | ||
| 290 | + returnData := protocol.ResponseAuditList{ | ||
| 291 | + ResponsePageInfo: protocol.ResponsePageInfo{ | ||
| 292 | + TotalPage: 0, | ||
| 293 | + CurrentPage: param.PageIndex, | ||
| 294 | + }, | ||
| 295 | + List: make([]protocol.RspAuditList, 0), | ||
| 296 | + } | ||
| 297 | + usercompany, err := models.GetUserCompanyBy(userid, companyid) | ||
| 298 | + if err != nil { | ||
| 299 | + log.Error("GetUserCompanyBy(userid, companyid) err:%s", err) | ||
| 300 | + return returnData, protocol.NewErrWithMessage("1") | ||
| 301 | + } | ||
| 302 | + | ||
| 303 | + countsql, datasql, cond = GetAuditListSql(param, companyid, userid, usercompany.Id) | ||
| 304 | + err = utils.ExecuteQueryOne(&cnt, countsql, cond...) | ||
| 295 | if err != nil { | 305 | if err != nil { |
| 296 | log.Error("EXCUTE SQL ERR:%s", err) | 306 | log.Error("EXCUTE SQL ERR:%s", err) |
| 297 | return returnData, nil | 307 | return returnData, nil |
| @@ -299,7 +309,7 @@ func GetAuditList(param protocol.RequestAuditList, companyid int64, userid int64 | @@ -299,7 +309,7 @@ func GetAuditList(param protocol.RequestAuditList, companyid int64, userid int64 | ||
| 299 | if cnt <= 0 { | 309 | if cnt <= 0 { |
| 300 | return returnData, nil | 310 | return returnData, nil |
| 301 | } | 311 | } |
| 302 | - err = utils.ExecuteQueryAll(&data, datasql.String(), cond...) | 312 | + err = utils.ExecuteQueryAll(&data, datasql, cond...) |
| 303 | if err != nil { | 313 | if err != nil { |
| 304 | log.Error("EXCUTE SQL ERR:%s", err) | 314 | log.Error("EXCUTE SQL ERR:%s", err) |
| 305 | return returnData, nil | 315 | return returnData, nil |
services/audit/excel_out.go
0 → 100644
| 1 | +package audit | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "encoding/json" | ||
| 5 | + "fmt" | ||
| 6 | + "oppmg/common/log" | ||
| 7 | + "oppmg/models" | ||
| 8 | + "oppmg/protocol" | ||
| 9 | + "oppmg/utils/exceltool" | ||
| 10 | + "strconv" | ||
| 11 | + "strings" | ||
| 12 | + | ||
| 13 | + "github.com/astaxie/beego/orm" | ||
| 14 | +) | ||
| 15 | + | ||
| 16 | +func GetAuditListForExcel(param protocol.RequestAuditList, companyid int64, userid int64) ([]orm.Params, []exceltool.ExcelHead) { | ||
| 17 | + usercompany, err := models.GetUserCompanyBy(userid, companyid) | ||
| 18 | + if err != nil { | ||
| 19 | + log.Error("GetUserCompanyBy(userid, companyid) err:%s", err) | ||
| 20 | + return nil, nil | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + datasql, _, cond := GetAuditListSql(param, companyid, userid, usercompany.Id) | ||
| 24 | + var soureData []orm.Params | ||
| 25 | + o := orm.NewOrm() | ||
| 26 | + _, err = o.Raw(datasql, cond...).Values(&soureData) | ||
| 27 | + if err != nil { | ||
| 28 | + log.Error("获取机会数据失败err:%s", err) | ||
| 29 | + return nil, nil | ||
| 30 | + } | ||
| 31 | + //https://web-open.fjmaimaimai.com/#/ 生产 | ||
| 32 | + // mmm-web-open-test.fjmaimaimai.com/ | ||
| 33 | + // SELECT a.id,a.department_id,a.audit_template_id,a.chance_type_id | ||
| 34 | + // ,a.publish_status,a.create_at,a.review_status,a.status | ||
| 35 | + // ,a.discovery_score,a.comment_total ,a.code,d.nick_name,d.id as user_id | ||
| 36 | + // ,a.type,a.reserve_type_id | ||
| 37 | + // FROM chance AS a | ||
| 38 | + // JOIN user_company AS c ON c.id = a.user_id | ||
| 39 | + // JOIN user AS d ON c.user_id = d.id | ||
| 40 | + //进行数据整理 | ||
| 41 | + var ( | ||
| 42 | + reserveTypeCache map[string]*models.ChanceReserveType | ||
| 43 | + chanceTypeCache map[string]*models.ChanceType | ||
| 44 | + auditTemplateCache map[string]*models.AuditTemplate | ||
| 45 | + departmentCache map[string]*models.Department | ||
| 46 | + ) | ||
| 47 | + for i := range soureData { | ||
| 48 | + storeTypeS := fmt.Sprint(soureData[i]["type"]) | ||
| 49 | + storeType, _ := strconv.ParseInt(storeTypeS, 10, 8) | ||
| 50 | + soureData[i]["type"] = models.ChanceStoreTypeMap[int8(storeType)] | ||
| 51 | + reserveTypeIdS := fmt.Sprint(soureData[i]["reserve_type_id"]) | ||
| 52 | + soureData[i]["reserve_type"] = "" | ||
| 53 | + if reserveType, ok := reserveTypeCache[reserveTypeIdS]; ok { | ||
| 54 | + soureData[i]["reserve_type"] = reserveType.Name | ||
| 55 | + } else { | ||
| 56 | + reserveTypeId, _ := strconv.Atoi(reserveTypeIdS) | ||
| 57 | + reserveType, err := models.GetChanceReserveTypeById(reserveTypeId) | ||
| 58 | + if err == nil { | ||
| 59 | + reserveTypeCache[reserveTypeIdS] = reserveType | ||
| 60 | + soureData[i]["reserve_type"] = reserveType.Name | ||
| 61 | + } | ||
| 62 | + } | ||
| 63 | + chanceTypeIdS := fmt.Sprint(soureData[i]["chance_type_id"]) | ||
| 64 | + soureData[i]["chance_type"] = "" | ||
| 65 | + if chanceType, ok := chanceTypeCache[chanceTypeIdS]; ok { | ||
| 66 | + soureData[i]["chance_type"] = chanceType.Name | ||
| 67 | + } else { | ||
| 68 | + chanceTypeId, _ := strconv.Atoi(chanceTypeIdS) | ||
| 69 | + chanceType, err := models.GetChanceTypeById(chanceTypeId) | ||
| 70 | + if err == nil { | ||
| 71 | + chanceTypeCache[chanceTypeIdS] = chanceType | ||
| 72 | + soureData[i]["chance_type"] = chanceType.Name | ||
| 73 | + } | ||
| 74 | + } | ||
| 75 | + auditTemplateIdS := fmt.Sprint(soureData[i]["audit_template_id"]) | ||
| 76 | + soureData[i]["audit_template"] = "" | ||
| 77 | + if auditTempalte, ok := auditTemplateCache[auditTemplateIdS]; ok { | ||
| 78 | + soureData[i]["audit_template"] = auditTempalte.Name | ||
| 79 | + } else { | ||
| 80 | + auditTemplateId, _ := strconv.ParseInt(auditTemplateIdS, 10, 64) | ||
| 81 | + auditTempalte, err := models.GetAuditTemplateById(auditTemplateId) | ||
| 82 | + if err == nil { | ||
| 83 | + auditTemplateCache[auditTemplateIdS] = auditTempalte | ||
| 84 | + soureData[i]["audit_template"] = auditTempalte.Name | ||
| 85 | + } | ||
| 86 | + } | ||
| 87 | + departmentIdS := fmt.Sprint(soureData[i]["department_id"]) | ||
| 88 | + if department, ok := departmentCache[departmentIdS]; ok { | ||
| 89 | + soureData[i]["department"] = department.Name | ||
| 90 | + } else { | ||
| 91 | + departmentId, _ := strconv.ParseInt(departmentIdS, 10, 64) | ||
| 92 | + department, err := models.GetDepartmentById(departmentId) | ||
| 93 | + if err == nil { | ||
| 94 | + departmentCache[departmentIdS] = department | ||
| 95 | + soureData[i]["department"] = department.Name | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | + reviewStatusS := fmt.Sprint(soureData[i]["review_status"]) | ||
| 99 | + reviewStatus, _ := strconv.ParseInt(reviewStatusS, 10, 8) | ||
| 100 | + soureData[i]["review_status"] = models.ChanceReviewStatusMap[int8(reviewStatus)] | ||
| 101 | + statusS := fmt.Sprint(soureData[i]["status"]) | ||
| 102 | + status, _ := strconv.ParseInt(statusS, 10, 8) | ||
| 103 | + soureData[i]["status"] = models.ChanceStatusMap[int8(status)] | ||
| 104 | + publishStatusS := fmt.Sprint(soureData[i]["publish_status"]) | ||
| 105 | + publishStatus, _ := strconv.Atoi(publishStatusS) | ||
| 106 | + soureData[i]["publish_status"] = models.ChancePublishStatusMap[publishStatus] | ||
| 107 | + soureContentJson := fmt.Sprint(soureData[i]["soure_content"]) | ||
| 108 | + var ( | ||
| 109 | + soureContentData []protocol.InputElement | ||
| 110 | + soureContentText strings.Builder | ||
| 111 | + ) | ||
| 112 | + err := json.Unmarshal([]byte(soureContentJson), &soureContentData) | ||
| 113 | + if err == nil { | ||
| 114 | + for ii := range soureContentData { | ||
| 115 | + soureContentText.WriteString(soureContentData[ii].Label + ":" + soureContentData[ii].CurrentValue + ",") | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + soureData[i]["soure_content"] = soureContentText.String() | ||
| 119 | + soureData[i]["media"] = fmt.Sprintf("chance_id=%v&check_sum=%s", soureData[i]["id"], "xx") | ||
| 120 | + | ||
| 121 | + } | ||
| 122 | + excelhead := []exceltool.ExcelHead{ | ||
| 123 | + exceltool.ExcelHead{Key: "type", Name: "类型"}, | ||
| 124 | + exceltool.ExcelHead{Key: "reserve_type", Name: "储备池分类"}, | ||
| 125 | + exceltool.ExcelHead{Key: "code", Name: "机会编码"}, | ||
| 126 | + exceltool.ExcelHead{Key: "chance_type", Name: "一级分类"}, | ||
| 127 | + exceltool.ExcelHead{Key: "audit_template", Name: "二级分类"}, | ||
| 128 | + exceltool.ExcelHead{Key: "nick_name", Name: "提交人"}, | ||
| 129 | + exceltool.ExcelHead{Key: "department_id", Name: "提交部门"}, | ||
| 130 | + exceltool.ExcelHead{Key: "create_at", Name: "提交时间"}, | ||
| 131 | + exceltool.ExcelHead{Key: "publish_status", Name: "公开状态"}, | ||
| 132 | + exceltool.ExcelHead{Key: "review_status", Name: "审核状态"}, | ||
| 133 | + exceltool.ExcelHead{Key: "status", Name: "关闭状态"}, | ||
| 134 | + exceltool.ExcelHead{Key: "soure_content", Name: "内容"}, | ||
| 135 | + exceltool.ExcelHead{Key: "media", Name: "视频/图片/语音"}, | ||
| 136 | + exceltool.ExcelHead{Key: "discovery_score", Name: "得分"}, | ||
| 137 | + } | ||
| 138 | + return soureData, excelhead | ||
| 139 | +} |
| @@ -564,11 +564,12 @@ func ExportFeedBacks(companyId int64, request *protocol.ExportFeedBacksRequest) | @@ -564,11 +564,12 @@ func ExportFeedBacks(companyId int64, request *protocol.ExportFeedBacksRequest) | ||
| 564 | return | 564 | return |
| 565 | } | 565 | } |
| 566 | excel = exceltool.NewExcelMaker() | 566 | excel = exceltool.NewExcelMaker() |
| 567 | - headTitle := [][]string{ | ||
| 568 | - []string{"time", "时间"}, | ||
| 569 | - []string{"name", "姓名"}, | ||
| 570 | - []string{"content", "选项"}, | 567 | + headTitle := []exceltool.ExcelHead{ |
| 568 | + exceltool.ExcelHead{Key: "time", Name: "时间"}, | ||
| 569 | + exceltool.ExcelHead{Key: "name", Name: "姓名"}, | ||
| 570 | + exceltool.ExcelHead{Key: "content", Name: "选项"}, | ||
| 571 | } | 571 | } |
| 572 | + excel.SetListHead(headTitle) | ||
| 572 | for i := range feedbackRsp.List { | 573 | for i := range feedbackRsp.List { |
| 573 | item := feedbackRsp.List[i] | 574 | item := feedbackRsp.List[i] |
| 574 | row := make(map[string]string) | 575 | row := make(map[string]string) |
| @@ -577,7 +578,7 @@ func ExportFeedBacks(companyId int64, request *protocol.ExportFeedBacksRequest) | @@ -577,7 +578,7 @@ func ExportFeedBacks(companyId int64, request *protocol.ExportFeedBacksRequest) | ||
| 577 | row["content"] = item.Content | 578 | row["content"] = item.Content |
| 578 | sourceData = append(sourceData, row) | 579 | sourceData = append(sourceData, row) |
| 579 | } | 580 | } |
| 580 | - if err = excel.MakeListExcel(sourceData, headTitle); err != nil { | 581 | + if err = excel.MakeListExcel(sourceData); err != nil { |
| 581 | log.Error(err.Error()) | 582 | log.Error(err.Error()) |
| 582 | return | 583 | return |
| 583 | } | 584 | } |
| @@ -10,10 +10,16 @@ import ( | @@ -10,10 +10,16 @@ import ( | ||
| 10 | "github.com/astaxie/beego/orm" | 10 | "github.com/astaxie/beego/orm" |
| 11 | ) | 11 | ) |
| 12 | 12 | ||
| 13 | -//(新)ExcelMaker 构建excel文档 | 13 | +type ExcelHead struct { |
| 14 | + Name string | ||
| 15 | + Key string | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +//ExcelMaker 构建excel文档 | ||
| 14 | type ExcelMaker struct { | 19 | type ExcelMaker struct { |
| 15 | Xlsx *excelize.File | 20 | Xlsx *excelize.File |
| 16 | - FileName string | 21 | + fileName string |
| 22 | + header []ExcelHead | ||
| 17 | } | 23 | } |
| 18 | 24 | ||
| 19 | //NewExcelMaker .... | 25 | //NewExcelMaker .... |
| @@ -23,22 +29,30 @@ func NewExcelMaker() *ExcelMaker { | @@ -23,22 +29,30 @@ func NewExcelMaker() *ExcelMaker { | ||
| 23 | } | 29 | } |
| 24 | } | 30 | } |
| 25 | 31 | ||
| 32 | +func (e *ExcelMaker) SetListHead(h []ExcelHead) { | ||
| 33 | + e.header = h | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +func (e *ExcelMaker) SetFileName(n string) { | ||
| 37 | + e.fileName = n | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +func (e *ExcelMaker) GetFileName() string { | ||
| 41 | + return e.fileName | ||
| 42 | +} | ||
| 43 | + | ||
| 26 | //MakeListExcel 根据列表形式的数据创建excel文档 | 44 | //MakeListExcel 根据列表形式的数据创建excel文档 |
| 27 | //@sourData []map[string]string; 原始数据,要输入excel文档的数据 | 45 | //@sourData []map[string]string; 原始数据,要输入excel文档的数据 |
| 28 | -//@xlsxHeader [][]string{ {"数据字段英文名",""excel字段中文描述"},{"数据字段英文名",""excel字段中文描述"} } | ||
| 29 | -func (e *ExcelMaker) MakeListExcel(sourData []map[string]string, xlsxHeader [][]string) (err error) { | ||
| 30 | - if len(xlsxHeader) == 0 { | 46 | +func (e *ExcelMaker) MakeListExcel(sourData []map[string]string) (err error) { |
| 47 | + if len(e.header) == 0 { | ||
| 31 | return errors.New("xlsHeader 数据格式错误") | 48 | return errors.New("xlsHeader 数据格式错误") |
| 32 | } | 49 | } |
| 33 | headEn := []string{} //数据字段英文名 | 50 | headEn := []string{} //数据字段英文名 |
| 34 | headCn := []string{} //excel字段中文描述 | 51 | headCn := []string{} //excel字段中文描述 |
| 35 | alphaSlice := []string{} //excel列字母索引 | 52 | alphaSlice := []string{} //excel列字母索引 |
| 36 | - for key, val := range xlsxHeader { | ||
| 37 | - if len(val) != 2 { | ||
| 38 | - return errors.New("xlsHeader 数据格式错误") | ||
| 39 | - } | ||
| 40 | - headEn = append(headEn, xlsxHeader[key][0]) | ||
| 41 | - headCn = append(headCn, xlsxHeader[key][1]) | 53 | + for key, val := range e.header { |
| 54 | + headEn = append(headEn, val.Key) | ||
| 55 | + headCn = append(headCn, val.Name) | ||
| 42 | //alpha, err := excelize.ColumnNumberToName(key) | 56 | //alpha, err := excelize.ColumnNumberToName(key) |
| 43 | //if err != nil { | 57 | //if err != nil { |
| 44 | // return err | 58 | // return err |
| @@ -48,39 +62,35 @@ func (e *ExcelMaker) MakeListExcel(sourData []map[string]string, xlsxHeader [][] | @@ -48,39 +62,35 @@ func (e *ExcelMaker) MakeListExcel(sourData []map[string]string, xlsxHeader [][] | ||
| 48 | } | 62 | } |
| 49 | 63 | ||
| 50 | //设置excel文档第一行的字段中文描述 | 64 | //设置excel文档第一行的字段中文描述 |
| 51 | - for index, _ := range headCn { | 65 | + for index := range headCn { |
| 52 | //索引转列名,索引从0开始 | 66 | //索引转列名,索引从0开始 |
| 53 | cellAlpha := fmt.Sprintf("%s%d", alphaSlice[index], 1) // 单元格行坐标从1开始,如:a1,指第一行a列。 | 67 | cellAlpha := fmt.Sprintf("%s%d", alphaSlice[index], 1) // 单元格行坐标从1开始,如:a1,指第一行a列。 |
| 54 | e.Xlsx.SetCellStr("Sheet1", cellAlpha, headCn[index]) | 68 | e.Xlsx.SetCellStr("Sheet1", cellAlpha, headCn[index]) |
| 55 | 69 | ||
| 56 | } | 70 | } |
| 57 | //从excel第二行开始设置实际数据的值 | 71 | //从excel第二行开始设置实际数据的值 |
| 58 | - for key1, _ := range sourData { | 72 | + for key1 := range sourData { |
| 59 | for i := 0; i < len(headEn); i++ { | 73 | for i := 0; i < len(headEn); i++ { |
| 60 | cellAlpha := fmt.Sprintf("%s%d", alphaSlice[i], key1+2) // 单元格行坐标从1开始,如:a1,指第一行a列。 | 74 | cellAlpha := fmt.Sprintf("%s%d", alphaSlice[i], key1+2) // 单元格行坐标从1开始,如:a1,指第一行a列。 |
| 61 | e.Xlsx.SetCellStr("Sheet1", cellAlpha, sourData[key1][headEn[i]]) | 75 | e.Xlsx.SetCellStr("Sheet1", cellAlpha, sourData[key1][headEn[i]]) |
| 62 | } | 76 | } |
| 63 | } | 77 | } |
| 64 | - e.FileName = GetRandomString(8) + ".xlsx" | 78 | + e.fileName = GetRandomString(8) + ".xlsx" |
| 65 | return nil | 79 | return nil |
| 66 | } | 80 | } |
| 67 | 81 | ||
| 68 | -//MakeListExcel 根据数据创建列表形式的excel文档 | 82 | +//MakeListExcelForBeego 根据数据创建列表形式的excel文档 |
| 69 | //@sourData []orm.Params; 原始数据,要输入excel文档的数据 | 83 | //@sourData []orm.Params; 原始数据,要输入excel文档的数据 |
| 70 | -//@xlsxHeader [][]string{ {"数据字段英文名",""excel字段中文描述"},{"数据字段英文名",""excel字段中文描述"} } | ||
| 71 | -func (e *ExcelMaker) MakeListExcelForBeego(sourData []orm.Params, xlsxHeader [][]string) (err error) { | ||
| 72 | - if len(xlsxHeader) == 0 { | 84 | +func (e *ExcelMaker) MakeListExcelForBeego(sourData []orm.Params) (err error) { |
| 85 | + if len(e.header) == 0 { | ||
| 73 | return errors.New("xlsHeader 数据格式错误") | 86 | return errors.New("xlsHeader 数据格式错误") |
| 74 | } | 87 | } |
| 75 | headEn := []string{} //数据字段英文名 | 88 | headEn := []string{} //数据字段英文名 |
| 76 | headCn := []string{} //excel字段中文描述 | 89 | headCn := []string{} //excel字段中文描述 |
| 77 | alphaSlice := []string{} //excel列字母索引 | 90 | alphaSlice := []string{} //excel列字母索引 |
| 78 | - for key, val := range xlsxHeader { | ||
| 79 | - if len(val) != 2 { | ||
| 80 | - return errors.New("xlsHeader 数据格式错误") | ||
| 81 | - } | ||
| 82 | - headEn = append(headEn, xlsxHeader[key][0]) | ||
| 83 | - headCn = append(headCn, xlsxHeader[key][1]) | 91 | + for key, val := range e.header { |
| 92 | + headEn = append(headEn, val.Key) | ||
| 93 | + headCn = append(headCn, val.Name) | ||
| 84 | alpha, err := excelize.ColumnNumberToName(key) | 94 | alpha, err := excelize.ColumnNumberToName(key) |
| 85 | if err != nil { | 95 | if err != nil { |
| 86 | return err | 96 | return err |
| @@ -89,14 +99,14 @@ func (e *ExcelMaker) MakeListExcelForBeego(sourData []orm.Params, xlsxHeader [][ | @@ -89,14 +99,14 @@ func (e *ExcelMaker) MakeListExcelForBeego(sourData []orm.Params, xlsxHeader [][ | ||
| 89 | } | 99 | } |
| 90 | 100 | ||
| 91 | //设置excel文档第一行的字段中文描述 | 101 | //设置excel文档第一行的字段中文描述 |
| 92 | - for index, _ := range headCn { | 102 | + for index := range headCn { |
| 93 | //索引转列名,索引从0开始 | 103 | //索引转列名,索引从0开始 |
| 94 | cellAlpha := fmt.Sprintf("%s%d", alphaSlice[index], 1) // 单元格行坐标从1开始,如:a1,指第一行a列。 | 104 | cellAlpha := fmt.Sprintf("%s%d", alphaSlice[index], 1) // 单元格行坐标从1开始,如:a1,指第一行a列。 |
| 95 | e.Xlsx.SetCellStr("Sheet1", cellAlpha, headCn[index]) | 105 | e.Xlsx.SetCellStr("Sheet1", cellAlpha, headCn[index]) |
| 96 | 106 | ||
| 97 | } | 107 | } |
| 98 | //从excel第二行开始设置实际数据的值 | 108 | //从excel第二行开始设置实际数据的值 |
| 99 | - for key1, _ := range sourData { | 109 | + for key1 := range sourData { |
| 100 | for i := 0; i < len(headEn); i++ { | 110 | for i := 0; i < len(headEn); i++ { |
| 101 | cellAlpha := fmt.Sprintf("%s%d", alphaSlice[i], key1+2) // 单元格行坐标从1开始,如:a1,指第一行a列。 | 111 | cellAlpha := fmt.Sprintf("%s%d", alphaSlice[i], key1+2) // 单元格行坐标从1开始,如:a1,指第一行a列。 |
| 102 | if sourData[key1][headEn[i]] == nil { | 112 | if sourData[key1][headEn[i]] == nil { |
| @@ -105,11 +115,13 @@ func (e *ExcelMaker) MakeListExcelForBeego(sourData []orm.Params, xlsxHeader [][ | @@ -105,11 +115,13 @@ func (e *ExcelMaker) MakeListExcelForBeego(sourData []orm.Params, xlsxHeader [][ | ||
| 105 | e.Xlsx.SetCellStr("Sheet1", cellAlpha, fmt.Sprintf("%s", sourData[key1][headEn[i]])) | 115 | e.Xlsx.SetCellStr("Sheet1", cellAlpha, fmt.Sprintf("%s", sourData[key1][headEn[i]])) |
| 106 | } | 116 | } |
| 107 | } | 117 | } |
| 108 | - e.FileName = GetRandomString(8) + ".xlsx" | 118 | + if len(e.fileName) == 0 { |
| 119 | + e.fileName = GetRandomString(8) + ".xlsx" | ||
| 120 | + } | ||
| 109 | return nil | 121 | return nil |
| 110 | } | 122 | } |
| 111 | 123 | ||
| 112 | -//生成随机字符串 | 124 | +//GetRandomString 生成随机字符串 |
| 113 | func GetRandomString(lenght int) string { | 125 | func GetRandomString(lenght int) string { |
| 114 | str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | 126 | str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| 115 | bytes := []byte(str) | 127 | bytes := []byte(str) |
utils/signature/signature.go
0 → 100644
| 1 | +package signature |
-
请 注册 或 登录 后发表评论