瀏覽代碼

添加流程相关功能

yxh 5 年之前
父節點
當前提交
839755d60d
共有 53 個文件被更改,包括 7867 次插入22 次删除
  1. 414 0
      app/controller/admin/wf_flow.go
  2. 112 0
      app/controller/admin/wf_news.go
  3. 11 11
      app/model/admin/cms_news/cms_news.go
  4. 27 0
      app/model/admin/wf_business_checker/business_checker.go
  5. 61 0
      app/model/admin/wf_business_checker/business_checker_entity.go
  6. 300 0
      app/model/admin/wf_business_checker/business_checker_model.go
  7. 265 0
      app/model/admin/wf_flow/flow.go
  8. 65 0
      app/model/admin/wf_flow/flow_entity.go
  9. 359 0
      app/model/admin/wf_flow/flow_model.go
  10. 327 0
      app/model/admin/wf_flow_process/flow_process.go
  11. 83 0
      app/model/admin/wf_flow_process/flow_process_entity.go
  12. 395 0
      app/model/admin/wf_flow_process/flow_process_model.go
  13. 154 0
      app/model/admin/wf_news/news.go
  14. 61 0
      app/model/admin/wf_news/news_entity.go
  15. 351 0
      app/model/admin/wf_news/news_model.go
  16. 80 0
      app/model/admin/wf_run/run.go
  17. 78 0
      app/model/admin/wf_run/run_entity.go
  18. 385 0
      app/model/admin/wf_run/run_model.go
  19. 30 0
      app/model/admin/wf_run_cache/wf_run_cache.go
  20. 66 0
      app/model/admin/wf_run_cache/wf_run_cache_entity.go
  21. 361 0
      app/model/admin/wf_run_cache/wf_run_cache_model.go
  22. 42 0
      app/model/admin/wf_run_log/wf_run_log.go
  23. 67 0
      app/model/admin/wf_run_log/wf_run_log_entity.go
  24. 363 0
      app/model/admin/wf_run_log/wf_run_log_model.go
  25. 116 0
      app/model/admin/wf_run_process/run_process.go
  26. 81 0
      app/model/admin/wf_run_process/run_process_entity.go
  27. 391 0
      app/model/admin/wf_run_process/run_process_model.go
  28. 43 0
      app/model/admin/wf_run_sign/run_sign.go
  29. 66 0
      app/model/admin/wf_run_sign/run_sign_entity.go
  30. 361 0
      app/model/admin/wf_run_sign/run_sign_model.go
  31. 33 0
      app/model/admin/wf_workinfo/wf_workinfo.go
  32. 63 0
      app/model/admin/wf_workinfo/wf_workinfo_entity.go
  33. 353 0
      app/model/admin/wf_workinfo/wf_workinfo_model.go
  34. 89 0
      app/service/admin/flow_news_service/news.go
  35. 64 0
      app/service/admin/flow_service/flow.go
  36. 10 0
      app/service/common/work_flow_service/back.go
  37. 107 0
      app/service/common/work_flow_service/business_checker.go
  38. 474 0
      app/service/common/work_flow_service/flow.go
  39. 417 0
      app/service/common/work_flow_service/process.go
  40. 140 0
      app/service/common/work_flow_service/run.go
  41. 23 0
      app/service/common/work_flow_service/run_cache.go
  42. 208 0
      app/service/common/work_flow_service/run_log.go
  43. 143 0
      app/service/common/work_flow_service/run_process.go
  44. 7 0
      app/service/common/work_flow_service/sign.go
  45. 39 0
      app/service/common/work_flow_service/sing.go
  46. 125 0
      app/service/common/work_flow_service/task.go
  47. 2 2
      go.mod
  48. 4 8
      go.sum
  49. 1 1
      plugin/blog/controller/home/index.go
  50. 7 0
      router/routerAdmin.go
  51. 16 0
      swagger/docs.go
  52. 16 0
      swagger/swagger.json
  53. 11 0
      swagger/swagger.yaml

+ 414 - 0
app/controller/admin/wf_flow.go

@@ -0,0 +1,414 @@
+// ==========================================================================
+// 生成日期:2020-08-24 17:13:46
+// 生成人:gfast
+// ==========================================================================
+package admin
+
+import (
+	"gfast/app/model/admin/sys_dept"
+	"gfast/app/model/admin/user"
+	flowModel "gfast/app/model/admin/wf_flow"
+	"gfast/app/model/admin/wf_flow_process"
+	"gfast/app/service/admin/dept_service"
+	flowService "gfast/app/service/admin/flow_service"
+	"gfast/app/service/admin/user_service"
+	"gfast/app/service/common/work_flow_service"
+	"gfast/library/response"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/util/gconv"
+	"github.com/gogf/gf/util/gvalid"
+)
+
+//控制器
+type Flow struct{}
+
+//列表页
+func (c *Flow) List(r *ghttp.Request) {
+	// 定义一个结构体存储请求参数
+	var req *flowModel.SelectPageReq
+	// 获取参数
+	err := r.Parse(&req)
+	if err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	total, page, list, err := flowService.SelectListByPage(req)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	//获取流程运行状态
+	listData, err := flowService.GetRunningStatus(list)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	result := g.Map{
+		"currentPage": page,
+		"total":       total,
+		"list":        listData,
+	}
+	response.SusJson(true, r, "获取列表数据成功", result)
+}
+
+// 新增
+func (c *Flow) Add(r *ghttp.Request) {
+	if r.Method == "POST" {
+		var req *flowModel.AddReq
+		// 通过Parse方法解析获取参数
+		err := r.Parse(&req)
+		if err != nil {
+			response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+		}
+		req.Uid = user_service.GetLoginID(r)
+		// 调用service中的添加函数添加
+		err = flowService.AddSave(req)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		response.SusJson(true, r, "添加成功")
+	}
+}
+
+// 修改
+func (c *Flow) Edit(r *ghttp.Request) {
+	// 如果是post提交的请求就执行修改操作
+	if r.Method == "POST" {
+		var editReq *flowModel.EditReq
+		// 通过Parse方法解析获取参数
+		err := r.Parse(&editReq)
+		if err != nil {
+			response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+		}
+		err = flowService.EditSave(editReq)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		response.SusJson(true, r, "修改参数成功")
+	}
+	// 不是post提交的请求就到修改页面后查询出要修改的记录
+	id := r.GetInt("id")
+	params, err := flowService.GetByID(int64(id))
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "ok", params)
+}
+
+// 删除
+func (c *Flow) Delete(r *ghttp.Request) {
+	var req *flowModel.RemoveReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	err := flowService.DeleteByIds(req.Ids)
+	if err != nil {
+		response.FailJson(true, r, "删除失败")
+	}
+	response.SusJson(true, r, "删除成功")
+}
+
+//设置状态
+func (c *Flow) StatusSetFlow(r *ghttp.Request) {
+	var req *flowModel.StatusReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	err := flowService.SetStatus(req)
+	if err != nil {
+		response.FailJson(true, r, "状态设置失败")
+	}
+	response.SusJson(true, r, "状态设置成功")
+}
+
+//工作流设计页
+func (c *Flow) DesignFlow(r *ghttp.Request) {
+	id := r.GetInt64("id")
+	if id == 0 {
+		response.FailJson(true, r, "参数错误")
+	}
+	//获取工作流信息
+	flowInfo, err := work_flow_service.GetFlowInfoById(id)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	if flowInfo == nil {
+		response.FailJson(true, r, "未找到数据,请返回重试!")
+	}
+	total, processList, err := work_flow_service.ProcessAll(id)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	processStep := make([]*wf_flow_process.ProcessStepData, 0, 20)
+	for _, process := range processList {
+		step, err := work_flow_service.GetProcessStep(process, processList)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		if step != nil {
+			processStep = append(processStep, step...)
+		}
+	}
+	//获取流程对应表字段信息
+	fields, err := work_flow_service.GetFlowTableFields(flowInfo.Type)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "ok", g.Map{
+		"flowInfo":    flowInfo,
+		"processList": processList,
+		"total":       total,
+		"fields":      fields,
+		"processStep": processStep,
+	})
+}
+
+//添加流程步骤
+func (c *Flow) AddProcess(r *ghttp.Request) {
+	id := r.GetInt64("id")
+	if id == 0 {
+		response.FailJson(true, r, "参数错误")
+	}
+	//获取工作流信息
+	flowInfo, err := work_flow_service.GetFlowInfoById(id)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	if flowInfo == nil {
+		response.FailJson(true, r, "添加失败,未找到流程")
+	}
+	err = work_flow_service.ProcessAdd(id)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "添加成功")
+}
+
+//保存设计
+func (c *Flow) SaveProcess(r *ghttp.Request) {
+	var req *wf_flow_process.SaveProcessReqBatch
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	for _, rq := range req.Data {
+		// 使用结构体定义的校验规则和错误提示进行校验
+		if err := gvalid.CheckStruct(rq, nil); err != nil {
+			response.FailJson(true, r, err.FirstString())
+		}
+	}
+	if err := work_flow_service.SaveProcess(req); err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "保存成功")
+}
+
+//获取办理对象(人员/部门)
+func (c *Flow) GetManager(r *ghttp.Request) {
+	manageType := r.GetString("type")
+	//kid := r.GetString("kid")
+	keyWords := r.GetString("key")
+	if manageType == "getUser" {
+		req := &user.SearchReq{
+			KeyWords: keyWords,
+			PageSize: 50,
+		}
+		_, _, userList, err := user_service.GetAdminList(req)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		res := make(g.ListStrAny, len(userList))
+		for k, v := range userList {
+			res[k] = g.MapStrAny{
+				"label": v.UserNickname,
+				"value": v.Id,
+			}
+		}
+		response.SusJson(true, r, "ok", res)
+	} else if manageType == "getDepartment" {
+		req := &sys_dept.SearchParams{
+			DeptName: keyWords,
+		}
+		list, err := dept_service.GetList(req)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		res := make(g.ListStrAny, len(list))
+		for k, v := range list {
+			res[k] = g.MapStrAny{
+				"label": v.DeptName,
+				"value": v.DeptID,
+			}
+		}
+		response.SusJson(true, r, "ok", res)
+	}
+	response.FailJson(true, r, "参数错误")
+}
+
+//保存节点属性
+func (c *Flow) SaveAttr(r *ghttp.Request) {
+	var req *wf_flow_process.SaveAttrReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	if err := work_flow_service.SaveProcessAttr(req); err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "保存成功", req)
+}
+
+//删除节点
+func (c *Flow) DeleteProcess(r *ghttp.Request) {
+	var req *wf_flow_process.DeleteProcessReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	if err := work_flow_service.ProcessDelete(req); err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "删除成功")
+}
+
+//清除流程节点
+func (c *Flow) DeleteProcessAll(r *ghttp.Request) {
+	flowId := r.GetInt("flow_id")
+	if flowId == 0 {
+		response.FailJson(true, r, "参数错误")
+	}
+	if err := work_flow_service.ProcessDeleteAll(flowId); err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "清除成功")
+}
+
+//检查流程逻辑
+func (c *Flow) CheckFlow(r *ghttp.Request) {
+	flowId := r.GetInt64("flow_id")
+	if flowId == 0 {
+		response.FailJson(true, r, "参数错误")
+	}
+	err := work_flow_service.CheckFlow(flowId)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "简单逻辑检查通过,请自行检查转出条件!")
+}
+
+//发起选择工作流程
+func (c *Flow) WfStart(r *ghttp.Request) {
+	var req *flowModel.StartFlowReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	flow, err := work_flow_service.GetWorkFlowByType(req.WfType)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+
+	response.SusJson(true, r, "ok", g.Map{
+		"flow": flow,
+		"info": req,
+	})
+}
+
+//保存业务流程信息
+func (c *Flow) SaveStartWf(r *ghttp.Request) {
+	var req *flowModel.SaveWfFlowReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	req.UserId = user_service.GetLoginID(r)
+	err := work_flow_service.StartWorkFlow(req)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "发起成功")
+}
+
+//审批
+func (c *Flow) WfCheck(r *ghttp.Request) {
+	var req *flowModel.CheckWfReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	//当前用户信息
+	userInfo := user_service.GetLoginAdminInfo(r)
+	//获取流程信息
+	flowInfo, err := work_flow_service.WorkFlowInfo(req.WfFid, req.WfType, userInfo.Id, userInfo.DeptId)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	//获取流程审批日志信息
+	flowLogs, err := work_flow_service.FlowLog("logs", req.WfFid, req.WfType)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	//获取业务信息
+	table, err := work_flow_service.GetBill(gconv.Int(req.WfFid), req.WfType)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "ok", g.Map{
+		"info":     req,
+		"flowInfo": flowInfo,
+		"flowLogs": flowLogs,
+		"table":    table,
+	})
+}
+
+//审批数据保存
+func (c *Flow) CheckSave(r *ghttp.Request) {
+	var req *flowModel.CheckWfSaveReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	req.UserId = user_service.GetLoginID(r)
+	err := work_flow_service.WorkCheckAction(req)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "ok", req)
+}
+
+//获取回退步骤数据
+func (c *Flow) GetBackTodo(r *ghttp.Request) {
+	var req *flowModel.BackTodoReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	todo, err := work_flow_service.GetBackTodo(req)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "ok", todo)
+}
+
+//流程监控
+func (c *Flow) Monitoring(r *ghttp.Request) {
+	list, err := work_flow_service.GetRunningFlow()
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "ok", list)
+}
+
+//终止流程
+func (c *Flow) StopFlow(r *ghttp.Request) {
+	runId := r.GetUint("id")
+	wfStatusField := r.GetString("WfStatusField")
+	if runId == 0 || wfStatusField == "" {
+		response.FailJson(true, r, "参数错误")
+	}
+	userId := user_service.GetLoginID(r)
+	if err := work_flow_service.DoSupEnd(runId, userId, wfStatusField); err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "终止成功")
+}

+ 112 - 0
app/controller/admin/wf_news.go

@@ -0,0 +1,112 @@
+// ==========================================================================
+// 生成日期:2020-09-17 10:13:16
+// 生成人:gfast
+// ==========================================================================
+package admin
+
+import (
+	wfNewsModel "gfast/app/model/admin/wf_news"
+	wfNewsService "gfast/app/service/admin/flow_news_service"
+	"gfast/app/service/admin/user_service"
+	"gfast/app/service/common/work_flow_service"
+
+	"gfast/library/response"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/util/gvalid"
+)
+
+//控制器
+type WfNews struct{}
+
+//列表页
+func (c *WfNews) List(r *ghttp.Request) {
+	// 定义一个结构体存储请求参数
+	var req *wfNewsModel.SelectPageReq
+	// 获取参数
+	err := r.Parse(&req)
+	if err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	userInfo := user_service.GetLoginAdminInfo(r)
+	total, page, list, err := wfNewsService.SelectListByPage(req, userInfo.Id, userInfo.DeptId)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	result := g.Map{
+		"currentPage": page,
+		"total":       total,
+		"list":        list,
+	}
+	response.SusJson(true, r, "获取列表数据成功", result)
+}
+
+// 新增
+func (c *WfNews) Add(r *ghttp.Request) {
+	if r.Method == "POST" {
+		var req *wfNewsModel.AddReq
+		// 通过Parse方法解析获取参数
+		err := r.Parse(&req)
+		if err != nil {
+			response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+		}
+		req.Uid = user_service.GetLoginID(r)
+		// 调用service中的添加函数添加
+		err = wfNewsService.AddSave(req)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		response.SusJson(true, r, "添加成功")
+	}
+}
+
+// 修改
+func (c *WfNews) Edit(r *ghttp.Request) {
+	// 如果是post提交的请求就执行修改操作
+	if r.Method == "POST" {
+		var editReq *wfNewsModel.EditReq
+		// 通过Parse方法解析获取参数
+		err := r.Parse(&editReq)
+		if err != nil {
+			response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+		}
+		err = wfNewsService.EditSave(editReq)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		response.SusJson(true, r, "修改参数成功")
+	}
+	// 不是post提交的请求就到修改页面后查询出要修改的记录
+	id := r.GetInt("id")
+	params, err := wfNewsService.GetByID(int64(id))
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "ok", params)
+}
+
+// 删除
+func (c *WfNews) Delete(r *ghttp.Request) {
+	var req *wfNewsModel.RemoveReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+	}
+	err := wfNewsService.DeleteByIds(req.Ids)
+	if err != nil {
+		response.FailJson(true, r, "删除失败")
+	}
+	response.SusJson(true, r, "删除成功")
+}
+
+//审批日志
+func (c *WfNews) CheckLog(r *ghttp.Request) {
+	id := r.GetUint("id")
+	wfType := r.GetString("wf_type")
+	//获取流程审批日志信息
+	flowLogs, err := work_flow_service.FlowLog("logs", id, wfType)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "ok", flowLogs)
+}

+ 11 - 11
app/model/admin/cms_news/cms_news.go

@@ -26,17 +26,17 @@ type NewsList struct {
 
 //添加文章参数
 type ReqAddParams struct {
-	NewsStatus    uint   `p:"status"    v:"in:0,1#状态只能为0或1"`                                // 状态;1:已发布;0:未发布;
-	Attr          []int  `p:"attr"`                                                         //文章标记 置顶 推荐
-	PublishedTime string `p:"published_time"`                                               // 发布时间
-	NewsTitle     string `p:"title"     v:"required#标题不能为空"`                                // post标题
-	NewsKeywords  string `p:"keywords"`                                                     // seo keywords
-	NewsExcerpt   string `p:"excerpt"`                                                      // post摘要
-	NewsSource    string `p:"source"  `                                                     // 转载文章的来源
-	Thumbnail     string `p:"thumbnail"    `                                                // 缩略图
-	IsJump        uint   `p:"IsJump"        v:"in:0,1#跳转类型只能为0或1"`                          // 是否跳转地址
-	JumpUrl       string `p:"JumpUrl"      v:"required-if:IsJump,1|url#跳转地址不能为空|跳转地址格式不正确"` // 跳转地址
-	ModelForm     g.Map  `p:"modelForm"`
+	NewsStatus    uint                   `p:"status"    v:"in:0,1#状态只能为0或1"`                                // 状态;1:已发布;0:未发布;
+	Attr          []int                  `p:"attr"`                                                         //文章标记 置顶 推荐
+	PublishedTime string                 `p:"published_time"`                                               // 发布时间
+	NewsTitle     string                 `p:"title"     v:"required#标题不能为空"`                                // post标题
+	NewsKeywords  string                 `p:"keywords"`                                                     // seo keywords
+	NewsExcerpt   string                 `p:"excerpt"`                                                      // post摘要
+	NewsSource    string                 `p:"source"  `                                                     // 转载文章的来源
+	Thumbnail     string                 `p:"thumbnail"    `                                                // 缩略图
+	IsJump        uint                   `p:"IsJump"        v:"in:0,1#跳转类型只能为0或1"`                          // 是否跳转地址
+	JumpUrl       string                 `p:"JumpUrl"      v:"required-if:IsJump,1|url#跳转地址不能为空|跳转地址格式不正确"` // 跳转地址
+	ModelForm     map[string]interface{} `p:"modelForm"`
 }
 
 //文章搜索参数

+ 27 - 0
app/model/admin/wf_business_checker/business_checker.go

@@ -0,0 +1,27 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package wf_business_checker
+
+import (
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+)
+
+type SaveParams struct {
+	FromTable    string `orm:"from_table"    json:"from_table"`    // 业务表名
+	FromId       uint64 `orm:"from_id"       json:"from_id"`       // 业务id
+	UserId       string `orm:"user_id"       json:"user_id"`       // 用户id
+	DepartmentId string `orm:"department_id" json:"department_id"` // 部门id
+}
+
+func SaveInfo(data *SaveParams, tx *gdb.TX) error {
+	_, err := Model.TX(tx).Save(data)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("保存业务审批人员/部门信息失败")
+	}
+	return nil
+}

+ 61 - 0
app/model/admin/wf_business_checker/business_checker_entity.go

@@ -0,0 +1,61 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_business_checker
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+)
+
+// Entity is the golang structure for table wf_business_checker.
+type Entity struct {
+	Id           uint64 `orm:"id,primary"    json:"id"`            // 主键
+	FromTable    string `orm:"from_table"    json:"from_table"`    // 业务表名
+	FromId       uint64 `orm:"from_id"       json:"from_id"`       // 业务id
+	UserId       string `orm:"user_id"       json:"user_id"`       // 用户id
+	DepartmentId string `orm:"department_id" json:"department_id"` // 部门id
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 300 - 0
app/model/admin/wf_business_checker/business_checker_model.go

@@ -0,0 +1,300 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_business_checker
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table wf_business_checker operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of wf_business_checker.
+	Table = "wf_business_checker"
+	// Model is the model object of wf_business_checker.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table wf_business_checker.
+	Columns = struct {
+		Id           string // 主键
+		FromTable    string // 业务表名
+		FromId       string // 业务id
+		UserId       string // 用户id
+		DepartmentId string // 部门id
+	}{
+		Id:           "id",
+		FromTable:    "from_table",
+		FromId:       "from_id",
+		UserId:       "user_id",
+		DepartmentId: "department_id",
+	}
+)
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields ...string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields ...string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// WherePri does the same logic as Model.Where except that if the parameter <where>
+// is a single condition like int/string/float/slice, it treats the condition as the primary
+// key value. That is, if primary key is "id" and given <where> parameter as "123", the
+// WherePri function treats the condition as "id=123", but Model.Where treats the condition
+// as string "123".
+func (m *arModel) WherePri(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.WherePri(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 265 - 0
app/model/admin/wf_flow/flow.go

@@ -0,0 +1,265 @@
+// ==========================================================================
+// 生成日期:2020-08-24 17:13:46
+// 生成人:gfast
+// ==========================================================================
+package wf_flow
+
+import (
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/util/gconv"
+)
+
+// AddReq 用于存储新增请求的请求参数
+type AddReq struct {
+	Type      string `p:"type" `
+	FlowName  string `p:"flowName" v:"required#流程名称不能为空"`
+	FlowDesc  string `p:"flowDesc" `
+	SortOrder uint   `p:"sortOrder" `
+	Status    uint   `p:"status" v:"required#状态不能为空"`
+	Uid       uint64
+	AddTime   int
+}
+
+// EditReq 用于存储修改请求参数
+type EditReq struct {
+	Id        int64  `p:"id" v:"required#主键ID不能为空"`
+	Type      string `p:"type" `
+	FlowName  string `p:"flowName" v:"required#流程名称不能为空"`
+	FlowDesc  string `p:"flowDesc" `
+	SortOrder uint   `p:"sortOrder" `
+	Status    uint   `p:"status" v:"required#状态不能为空"`
+}
+type RemoveReq struct {
+	Ids []int `p:"ids"` //删除id
+}
+
+type StatusReq struct {
+	Id     int  `p:"id" v:"required|min:1#ID不能为空|ID不能为空"`
+	Status uint `p:"status" v:"required|in:0,1#状态不能为空|状态不能为空"`
+}
+
+// SelectPageReq 用于存储分页查询的请求参数
+type SelectPageReq struct {
+	Type      string `p:"type"`      //流程类别
+	FlowName  string `p:"flowName"`  //流程名称
+	Status    string `p:"status"`    //状态
+	BeginTime string `p:"beginTime"` //开始时间
+	EndTime   string `p:"endTime"`   //结束时间
+	PageNum   int64  `p:"pageNum"`   //当前页码
+	PageSize  int    `p:"pageSize"`  //每页数
+}
+
+//发起工作流参数
+type StartFlowReq struct {
+	WfType        string `p:"wf_type" v:"required#业务表类型不能为空" json:"wf_type"`
+	WfStatusField string `p:"wf_status_field" v:"required#业务表状态字段名称不能为空" json:"wf_status_field"`
+	WfTitle       string `p:"wf_title" v:"required#wf_title字段值不能为空" json:"wf_title"`
+	WfFid         uint   `p:"wf_fid" v:"required|min:1#业务ID不能为空|业务ID不能为空" json:"wf_fid"`
+}
+
+//保存发起信息请求参数
+type SaveWfFlowReq struct {
+	WfType        string `p:"wfType" v:"required#业务表类型不能为空" json:"wf_type"`
+	WfFid         int    `p:"wfFid" v:"required|min:1#业务ID不能为空|业务ID不能为空" json:"wf_fid"`
+	WfId          int64  `p:"wfId" v:"required|min:1#工作流ID不能为空|工作流ID不能为空" json:"wf_id"`
+	WfTitle       string `p:"WfTitle" v:"required#标题字段名称不能为空"`       //业务表标题字段名称
+	WfStatusField string `p:"WfStatusField" v:"required#状态字段名称不能为空"` //业务表状态字段名称
+	NewType       uint   `p:"newType"`                               //审批紧急程度
+	CheckCon      string `p:"checkCon"`
+	RunProcess    uint   `p:"runProcess"`
+	RunId         uint   `p:"runId"`
+	Art           string `p:"art"`
+	UserId        uint64
+}
+
+//获取流程审批数据参数
+type CheckWfReq struct {
+	WfTitle       string `p:"wf_title" json:"wf_title"`
+	WfFid         uint   `p:"wf_fid" v:"required|min:1#业务ID不能为空|业务ID不能为空" json:"wf_fid"`
+	WfType        string `p:"wf_type" v:"required#业务表类型不能为空" json:"wf_type"`
+	WfStatusField string `p:"wf_status_field" v:"required#业务表状态字段名称不能为空" json:"wf_status_field"`
+}
+
+//保存流程审批参数
+type CheckWfSaveReq struct {
+	WfTitle       string `p:"wfTitle" json:"wf_title"`
+	WfFid         uint   `p:"wfFid"  json:"wf_fid" v:"required|min:1#业务ID不能为空|业务ID不能为空"`
+	WfType        string `p:"wfType" v:"required#业务表类型不能为空"  json:"wf_type"`
+	WfStatusField string `p:"WfStatusField" v:"required#状态字段名称不能为空"` //业务表状态字段名称
+	FlowId        int64  `p:"flowId" v:"required|min:1#流程Id不能为空|流程Id不能为空"  json:"flow_id"`
+	FlowProcess   uint   `p:"flowProcess" v:"required|min:1#流程步骤Id不能为空|流程步骤Id不能为空" json:"flow_process"`
+	RunId         uint   `p:"runId" json:"run_id" v:"required|min:1#当前运行流程Id不能为空|当前运行流程Id不能为空"`
+	RunProcess    uint   `p:"runProcess" json:"run_process" v:"required|min:1#当前运行流程步骤Id不能为空|当前运行流程步骤Id不能为空"`
+	Npid          string `p:"npid" json:"npid"`
+	WfMode        uint   `p:"wfMode" json:"wf_mode"`
+	CheckCon      string `p:"checkCon" json:"check_con"`
+	Art           string `p:"art" json:"art"`
+	SingSt        uint   `p:"singSt" json:"sing_st"`
+	SubmitToSave  string `p:"submitToSave" json:"submit_to_save" v:"required#审批方式不能为空"`
+	UserId        uint64 `json:"user_id"`
+	Sup           int    `p:"sup"  json:"sup"`
+	Todo          string `p:"todo" json:"todo"`
+	WfSingFlow    uint64 `p:"WfSingFlow"` //会签人
+	WfBackFlow    uint   `p:"WfBackFlow"` //退回的步骤ID,如果等于0则默认是第一步
+	BTodo         string `p:"bTodo"`
+}
+
+type BackTodoReq struct {
+	Pid   string `p:"backId" v:required#流程步骤ID必须`
+	RunId uint   `p:"runId" v:required|min:1#流程运行ID必须|流程步骤ID必须`
+}
+
+// GetPlugAdByID 根据ID查询记录
+func GetByID(id int64) (*Entity, error) {
+	entity, err := Model.FindOne(id)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("根据ID查询记录出错")
+	}
+	if entity == nil {
+		err = gerror.New("根据ID未能查询到记录")
+	}
+	return entity, nil
+}
+
+// AddSave 添加
+func AddSave(req *AddReq) error {
+	entity := new(Entity)
+	entity.Type = req.Type
+	entity.FlowName = req.FlowName
+	entity.FlowDesc = req.FlowDesc
+	entity.SortOrder = req.SortOrder
+	entity.Status = req.Status
+	entity.Uid = req.Uid
+	entity.AddTime = gconv.Int(gtime.Timestamp())
+	result, err := entity.Insert()
+	if err != nil {
+		return err
+	}
+	_, err = result.LastInsertId()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 删除
+func DeleteByIds(Ids []int) error {
+	_, err := Model.Where("id in(?)", Ids).Update(g.Map{"is_del": 1})
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("删除失败")
+	}
+	return nil
+}
+
+func SetStatus(req *StatusReq) error {
+	entity, err := Model.Where("id", req.Id).One()
+	if err != nil {
+		g.Log().Debug(err)
+		return gerror.New("设置失败")
+	}
+	if entity != nil {
+		entity.Status = req.Status
+		_, err := Model.Save(entity)
+		if err != nil {
+			g.Log().Debug(err)
+			return gerror.New("设置失败")
+		}
+	}
+	return nil
+}
+
+// 根据ID来修改信息
+func EditSave(req *EditReq) error {
+	// 先根据ID来查询要修改的记录
+	entity, err := GetByID(req.Id)
+	if err != nil {
+		return err
+	}
+	// 修改实体
+	entity.Type = req.Type
+	entity.FlowName = req.FlowName
+	entity.FlowDesc = req.FlowDesc
+	entity.SortOrder = req.SortOrder
+	entity.Status = req.Status
+	_, err = Model.Save(entity)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("修改失败")
+	}
+	return nil
+}
+
+// 分页查询,返回值total总记录数,page当前页
+func SelectListByPage(req *SelectPageReq) (total int, page int64, list []*Entity, err error) {
+	model := Model
+	if req != nil {
+		if req.Type != "" {
+			model = model.Where("type = ?", req.Type)
+		}
+		if req.FlowName != "" {
+			model = model.Where("flow_name like ?", "%"+req.FlowName+"%")
+		}
+		if req.Status != "" {
+			model = model.Where("status", gconv.Int(req.Status))
+		}
+	}
+	model = model.Where("is_del", 0)
+	// 查询总记录数(总行数)
+	total, err = model.Count()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取总记录数失败")
+		return
+	}
+	if req.PageNum == 0 {
+		req.PageNum = 1
+	}
+	page = req.PageNum
+	if req.PageSize == 0 {
+		req.PageSize = 10
+	}
+	// 分页排序查询
+	list, err = model.Page(int(page), int(req.PageSize)).Order("sort_order asc , id asc").All()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("分页查询失败")
+		return
+	}
+	return
+}
+
+// 获取所有数据
+func SelectListAll(req *SelectPageReq) (list []*Entity, err error) {
+	model := Model
+	if req != nil {
+		if req.Type != "" {
+			model.Where("type = ?", req.Type)
+		}
+		if req.FlowName != "" {
+			model.Where("flow_name like ?", "%"+req.FlowName+"%")
+		}
+	}
+	// 查询
+	list, err = model.Order("id asc").All()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("查询失败")
+		return
+	}
+	return
+}
+
+//获取对应类型的工作流
+func GetWorkFlowByType(wfType string) (flows []*Entity, err error) {
+	flows, err = Model.Where(Columns.Type, wfType).Where(Columns.Status, 1).
+		Where(Columns.IsDel, 0).FindAll()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取工作流列表数据失败")
+	}
+	return
+}

+ 65 - 0
app/model/admin/wf_flow/flow_entity.go

@@ -0,0 +1,65 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_flow
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+)
+
+// Entity is the golang structure for table wf_flow.
+type Entity struct {
+	Id        uint   `orm:"id,primary" json:"id"`         //
+	Type      string `orm:"type"       json:"type"`       // 流程类别
+	FlowName  string `orm:"flow_name"  json:"flow_name"`  // 流程名称
+	FlowDesc  string `orm:"flow_desc"  json:"flow_desc"`  // 描述
+	SortOrder uint   `orm:"sort_order" json:"sort_order"` // 排序
+	Status    uint   `orm:"status"     json:"status"`     // 0不可用1正常
+	IsDel     uint   `orm:"is_del"     json:"is_del"`     //
+	Uid       uint64 `orm:"uid"        json:"uid"`        // 添加用户
+	AddTime   int    `orm:"add_time"   json:"add_time"`   // 添加时间
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 359 - 0
app/model/admin/wf_flow/flow_model.go

@@ -0,0 +1,359 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_flow
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table wf_flow operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of wf_flow.
+	Table = "wf_flow"
+	// Model is the model object of wf_flow.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table wf_flow.
+	Columns = struct {
+		Id        string //
+		Type      string // 流程类别
+		FlowName  string // 流程名称
+		FlowDesc  string // 描述
+		SortOrder string // 排序
+		Status    string // 0不可用1正常
+		IsDel     string //
+		Uid       string // 添加用户
+		AddTime   string // 添加时间
+	}{
+		Id:        "id",
+		Type:      "type",
+		FlowName:  "flow_name",
+		FlowDesc:  "flow_desc",
+		SortOrder: "sort_order",
+		Status:    "status",
+		IsDel:     "is_del",
+		Uid:       "uid",
+		AddTime:   "add_time",
+	}
+)
+
+// FindOne is a convenience method for Model.FindOne.
+// See Model.FindOne.
+func FindOne(where ...interface{}) (*Entity, error) {
+	return Model.FindOne(where...)
+}
+
+// FindAll is a convenience method for Model.FindAll.
+// See Model.FindAll.
+func FindAll(where ...interface{}) ([]*Entity, error) {
+	return Model.FindAll(where...)
+}
+
+// FindValue is a convenience method for Model.FindValue.
+// See Model.FindValue.
+func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
+	return Model.FindValue(fieldsAndWhere...)
+}
+
+// FindArray is a convenience method for Model.FindArray.
+// See Model.FindArray.
+func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) {
+	return Model.FindArray(fieldsAndWhere...)
+}
+
+// FindCount is a convenience method for Model.FindCount.
+// See Model.FindCount.
+func FindCount(where ...interface{}) (int, error) {
+	return Model.FindCount(where...)
+}
+
+// Insert is a convenience method for Model.Insert.
+func Insert(data ...interface{}) (result sql.Result, err error) {
+	return Model.Insert(data...)
+}
+
+// InsertIgnore is a convenience method for Model.InsertIgnore.
+func InsertIgnore(data ...interface{}) (result sql.Result, err error) {
+	return Model.InsertIgnore(data...)
+}
+
+// Replace is a convenience method for Model.Replace.
+func Replace(data ...interface{}) (result sql.Result, err error) {
+	return Model.Replace(data...)
+}
+
+// Save is a convenience method for Model.Save.
+func Save(data ...interface{}) (result sql.Result, err error) {
+	return Model.Save(data...)
+}
+
+// Update is a convenience method for Model.Update.
+func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
+	return Model.Update(dataAndWhere...)
+}
+
+// Delete is a convenience method for Model.Delete.
+func Delete(where ...interface{}) (result sql.Result, err error) {
+	return Model.Delete(where...)
+}
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 327 - 0
app/model/admin/wf_flow_process/flow_process.go

@@ -0,0 +1,327 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package wf_flow_process
+
+import (
+	"github.com/gogf/gf/container/garray"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/encoding/gjson"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/text/gstr"
+	"github.com/gogf/gf/util/gconv"
+)
+
+type ProcessData struct {
+	Id          int     `json:"id"`
+	Mode        string  `json:"mode"`
+	Name        string  `json:"name"`
+	FlowId      uint    `json:"flow_id"`
+	ProcessName string  `json:"process_name"`
+	ProcessTo   string  `json:"process_to"`
+	Style       string  `json:"style"`
+	Process     *Entity `json:"process"`
+}
+
+//流程转出信息
+type ProcessStepData struct {
+	PrevId      int      `json:"prev_id"`
+	Id          int      `json:"id"`
+	ProcessName string   `json:"process_name"`
+	ProcessType string   `json:"process_type"`
+	Condition   []string `json:"condition"`
+}
+
+type SaveProcessReq struct {
+	Id        int    `p:"id" v:"required#流程ID不能为空"`
+	Left      string `p:"left" v:"required#流程坐标left不能为空"`
+	Top       string `p:"top" v:"required#流程坐标top不能为空"`
+	ProcessTo string `p:"process_to" `
+}
+
+type SaveProcessReqBatch struct {
+	Data []*SaveProcessReq `p:"data" v:"required#数据不能为空"`
+}
+
+type SaveAttrReq struct {
+	FlowId           int               `p:"flow_id" v:"required#工作流id不能为空"`
+	ProcessId        int               `p:"process_id" v:required#流程id不能为空`
+	ProcessName      string            `p:"process_name" v:required#流程名称不能为空`
+	StyleWidth       string            `p:"style_width"`
+	StyleHeight      string            `p:"style_height"`
+	ProcessType      string            `p:"process_type"`
+	WfAction         string            `p:"wf_action"`
+	IsSing           string            `p:"is_sing"`
+	IsBack           string            `p:"is_back"`
+	AutoPerson       string            `p:"auto_person"`
+	RangeUserIds     string            `p:"range_user_ids"`
+	RangeUserText    string            `p:"range_user_text"`
+	AutoSponsorIds   string            `p:"auto_sponsor_ids"`
+	AutoSponsorText  string            `p:"auto_sponsor_text"`
+	AutoRoleIds      string            `p:"auto_role_ids"`
+	AutoRoleText     string            `p:"auto_role_text"`
+	WorkText         string            `p:"work_text"`
+	WorkIds          string            `p:"work_ids"`
+	WfMode           string            `p:"wf_mode"`
+	ProcessInSet     []ProcessInSetReq `p:"process_in_set"`
+	WorkSql          string            `p:"work_sql"`
+	WorkMsg          string            `p:"work_msg"`
+	ProcessCondition string            `p:"process_condition"`
+}
+
+type ProcessInSetReq struct {
+	Id        int      `p:"id"`
+	Condition []string `p:"condition"`
+}
+
+//流程管理表字段信息
+type FlowTableField struct {
+	FieldName string `json:"field_name"`
+	FieldText string `json:"field_text"`
+}
+
+type DeleteProcessReq struct {
+	ProcessId int `p:"process_id" v:"required#流程ID不能为空" json:"process_id"`
+	FlowId    int `p:"flow_id" v:"required#工作流Id不能为空" json:"flow_id"`
+}
+
+//获取流程下所有步骤信息
+func ProcessAll(flowId int64) (total int64, processData []*ProcessData, err error) {
+	var list []*Entity
+	list, err = Model.Where("flow_id", flowId).Where("is_del", 0).Order("id asc").FindAll()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取流程数据失败")
+		return
+	}
+	processData = make([]*ProcessData, len(list))
+	for k, entity := range list {
+		total += 1
+		style := gjson.New(entity.Style)
+		model := `<span style="color:red">未设置</span>`
+		name := `<span style="color:red">未设置</span>`
+		var work map[string]string
+		if entity.AutoPerson == 3 {
+			model = "办理人员"
+			name = entity.RangeUserText
+		}
+		if entity.AutoPerson == 4 {
+			model = "办理人员"
+			name = entity.AutoSponsorText
+		}
+		if entity.AutoPerson == 5 {
+			model = "办理角色"
+			name = entity.AutoRoleText
+		}
+		if entity.AutoPerson == 6 {
+			work = map[string]string{
+				"1": "制单人员",
+				"2": "制单人员领导",
+			}
+			model = `<span style="color:blue">事务处理</span>`
+			name = work[entity.WorkIds]
+		}
+		nameAttr := ""
+		if entity.ProcessType == "is_one" {
+			nameAttr = `<span style="color:blue">[开始]</span>`
+		} else {
+			if entity.WfMode == 0 {
+				nameAttr = "[直线]"
+			} else if entity.WfMode == 1 {
+				nameAttr = `<span style="color:lightgreen">[转出]</span>`
+			} else {
+				nameAttr = `<span style="color:red">[同步]</span>`
+			}
+		}
+		heightUnit := "px"
+		if style.GetString("height") == "auto" {
+			heightUnit = ""
+		}
+		processData[k] = &ProcessData{
+			Id:          entity.Id,
+			Mode:        model,
+			Name:        name,
+			FlowId:      entity.FlowId,
+			ProcessName: nameAttr + entity.ProcessName,
+			ProcessTo:   entity.ProcessTo,
+			Style: "width:" + style.GetString("width") + "px;height:" + style.GetString("height") +
+				heightUnit + ";line-height:30px;color:#0e76a8;left:" + gconv.String(entity.Setleft) + "px;top:" + gconv.String(entity.Settop) + "px;",
+			Process: entity,
+		}
+	}
+	return
+}
+
+//保存流程步骤
+func ProcessAdd(flowId int64) error {
+	processList, err := Model.Order("id desc").FindAll("flow_id", flowId)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("获取流程数据失败")
+	}
+	processCount := len(processList)
+	processType := "is_step"
+	var processSetLeft uint = 100
+	var processSetTop uint = 100
+	if processCount == 0 {
+		processType = "is_one"
+		processSetLeft = 100
+		processSetTop = 100
+	} else {
+		//新建步骤显示在上一个步骤下方
+		processType = "is_step"
+		processSetLeft = processList[0].Setleft + 30
+		processSetTop = processList[0].Settop + 30
+	}
+
+	style := gjson.New(nil)
+	style.Set("width", "120")
+	style.Set("height", "auto")
+	style.Set("color", "#0e76a8")
+	entity := &Entity{
+		FlowId:      gconv.Uint(flowId),
+		Setleft:     processSetLeft,
+		Settop:      processSetTop,
+		ProcessType: processType,
+		Style:       style.MustToJsonString(),
+		AutoPerson:  4,
+		ProcessName: "步骤",
+		IsSing:      1,
+		IsBack:      1,
+		WfAction:    "view",
+	}
+	_, err = Model.Save(entity)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("保存流程步骤失败")
+	}
+	return nil
+}
+
+//保存设计
+func SaveProcess(req *SaveProcessReqBatch) error {
+	if req != nil && req.Data != nil {
+		tx, err := g.DB().Begin()
+		if err != nil {
+			g.Log().Error(err)
+			return gerror.New("保存失败")
+		}
+		entities := make([]*Entity, len(req.Data))
+		for k, rq := range req.Data {
+			entity, err := Model.FindOne(rq.Id)
+			if err != nil || entity == nil {
+				if err != nil {
+					g.Log().Error(err)
+				}
+				tx.Rollback()
+				return gerror.New("不存在的流程信息,请刷新重试。")
+			}
+			entity.Setleft = gconv.Uint(rq.Left)
+			entity.Settop = gconv.Uint(rq.Top)
+			entity.ProcessTo = rq.ProcessTo
+			entities[k] = entity
+		}
+		_, err = Model.TX(tx).Data(entities).Save()
+		if err != nil {
+			g.Log().Error(err)
+			tx.Rollback()
+			return gerror.New("保存数据失败")
+		}
+		tx.Commit()
+	}
+	return nil
+}
+
+//获取流程管理表字段信息
+func GetFlowTableFields(dbPrefix, database, tableName string) (fields []*FlowTableField, err error) {
+	sql := "SELECT COLUMN_NAME as field_name,column_comment as field_text FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = ? " +
+		"AND table_schema = ? "
+	var result gdb.Result
+	result, err = g.DB().GetAll(sql, dbPrefix+tableName, database)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("查询表字段数据失败")
+	}
+	result.Structs(&fields)
+	return
+}
+
+//删除流程节点
+func ProcessDelete(req *DeleteProcessReq) error {
+	tx, err := g.DB().Begin()
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("开启处理事务失败")
+	}
+	_, err = Model.TX(tx).Delete(g.Map{
+		Columns.Id:     req.ProcessId,
+		Columns.FlowId: req.FlowId,
+		Columns.IsDel:  0,
+	})
+	if err != nil {
+		g.Log().Error(err)
+		tx.Rollback()
+		return gerror.New("删除失败")
+	}
+	list, err := Model.Where(Columns.FlowId, req.FlowId).Where(Columns.IsDel, 0).
+		Where("FIND_IN_SET(?,process_to)", gconv.String(req.ProcessId)).FindAll()
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("获取节点连接失败")
+	}
+	if list != nil {
+		for _, value := range list {
+			s := gstr.Split(value.ProcessTo, ",")
+			arr := garray.NewStrArrayFrom(s)
+			key := arr.Search(gconv.String(req.ProcessId))
+			arr.Remove(key)
+			processTo := ""
+			if arr.Len() > 0 {
+				processTo = arr.Join(",")
+			}
+			value.Updatetime = gconv.Uint(gtime.Timestamp())
+			value.ProcessTo = processTo
+			_, err := Model.TX(tx).Save(value)
+			if err != nil {
+				g.Log().Error(err)
+				tx.Rollback()
+				return gerror.New("删除节点连线失败")
+			}
+		}
+	}
+	tx.Commit()
+	return nil
+}
+
+//通过ID获取流程步骤信息
+func GetProcessInfoById(id uint) (entity *Entity, err error) {
+	entity, err = Model.FindOne(id)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("查询流程信息失败")
+	}
+	return
+}
+
+//查询流程信息
+func GetProcessesByMap(where g.Map) (entities []*Entity, err error) {
+	entities, err = Model.Where(where).FindAll()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("查询流程信息失败")
+	}
+	return
+}
+
+//通过ids获取多条流程步骤信息
+func GetProcessInfosByIds(ids []uint) (entities []*Entity, err error) {
+	entities, err = Model.Where("id in (?)", ids).FindAll()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("查询流程信息失败")
+	}
+	return
+}

+ 83 - 0
app/model/admin/wf_flow_process/flow_process_entity.go

@@ -0,0 +1,83 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_flow_process
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+)
+
+// Entity is the golang structure for table wf_flow_process.
+type Entity struct {
+	Id              int    `orm:"id,primary"        json:"id"`                //
+	FlowId          uint   `orm:"flow_id"           json:"flow_id"`           // 流程ID
+	ProcessName     string `orm:"process_name"      json:"process_name"`      // 步骤名称
+	ProcessType     string `orm:"process_type"      json:"process_type"`      // 步骤类型
+	ProcessTo       string `orm:"process_to"        json:"process_to"`        // 转交下一步骤号
+	AutoPerson      uint   `orm:"auto_person"       json:"auto_person"`       // 3自由选择|4指定人员|5指定角色|6事务接受
+	AutoSponsorIds  string `orm:"auto_sponsor_ids"  json:"auto_sponsor_ids"`  // 4指定步骤主办人ids
+	AutoSponsorText string `orm:"auto_sponsor_text" json:"auto_sponsor_text"` // 4指定步骤主办人text
+	WorkIds         string `orm:"work_ids"          json:"work_ids"`          // 6事务接受
+	WorkText        string `orm:"work_text"         json:"work_text"`         // 6事务接受
+	AutoRoleIds     string `orm:"auto_role_ids"     json:"auto_role_ids"`     // 5角色ids
+	AutoRoleText    string `orm:"auto_role_text"    json:"auto_role_text"`    // 5角色 text
+	RangeUserIds    string `orm:"range_user_ids"    json:"range_user_ids"`    // 3自由选择IDS
+	RangeUserText   string `orm:"range_user_text"   json:"range_user_text"`   // 3自由选择用户ID
+	IsSing          uint   `orm:"is_sing"           json:"is_sing"`           // 1允许|2不允许
+	IsBack          uint   `orm:"is_back"           json:"is_back"`           // 1允许|2不允许
+	OutCondition    string `orm:"out_condition"     json:"out_condition"`     // 转出条件
+	Setleft         uint   `orm:"setleft"           json:"setleft"`           // 左 坐标
+	Settop          uint   `orm:"settop"            json:"settop"`            // 上 坐标
+	Style           string `orm:"style"             json:"style"`             // 样式 序列化
+	IsDel           uint   `orm:"is_del"            json:"is_del"`            //
+	Updatetime      uint   `orm:"updatetime"        json:"updatetime"`        // 更新时间
+	Dateline        uint   `orm:"dateline"          json:"dateline"`          //
+	WfMode          uint   `orm:"wf_mode"           json:"wf_mode"`           // 0 单一线性,1,转出条件 2,同步模式
+	WfAction        string `orm:"wf_action"         json:"wf_action"`         // 对应方法
+	WorkSql         string `orm:"work_sql"          json:"work_sql"`          //
+	WorkMsg         string `orm:"work_msg"          json:"work_msg"`          //
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 395 - 0
app/model/admin/wf_flow_process/flow_process_model.go

@@ -0,0 +1,395 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_flow_process
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table wf_flow_process operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of wf_flow_process.
+	Table = "wf_flow_process"
+	// Model is the model object of wf_flow_process.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table wf_flow_process.
+	Columns = struct {
+		Id              string //
+		FlowId          string // 流程ID
+		ProcessName     string // 步骤名称
+		ProcessType     string // 步骤类型
+		ProcessTo       string // 转交下一步骤号
+		AutoPerson      string // 3自由选择|4指定人员|5指定角色|6事务接受
+		AutoSponsorIds  string // 4指定步骤主办人ids
+		AutoSponsorText string // 4指定步骤主办人text
+		WorkIds         string // 6事务接受
+		WorkText        string // 6事务接受
+		AutoRoleIds     string // 5角色ids
+		AutoRoleText    string // 5角色 text
+		RangeUserIds    string // 3自由选择IDS
+		RangeUserText   string // 3自由选择用户ID
+		IsSing          string // 1允许|2不允许
+		IsBack          string // 1允许|2不允许
+		OutCondition    string // 转出条件
+		Setleft         string // 左 坐标
+		Settop          string // 上 坐标
+		Style           string // 样式 序列化
+		IsDel           string //
+		Updatetime      string // 更新时间
+		Dateline        string //
+		WfMode          string // 0 单一线性,1,转出条件 2,同步模式
+		WfAction        string // 对应方法
+		WorkSql         string //
+		WorkMsg         string //
+	}{
+		Id:              "id",
+		FlowId:          "flow_id",
+		ProcessName:     "process_name",
+		ProcessType:     "process_type",
+		ProcessTo:       "process_to",
+		AutoPerson:      "auto_person",
+		AutoSponsorIds:  "auto_sponsor_ids",
+		AutoSponsorText: "auto_sponsor_text",
+		WorkIds:         "work_ids",
+		WorkText:        "work_text",
+		AutoRoleIds:     "auto_role_ids",
+		AutoRoleText:    "auto_role_text",
+		RangeUserIds:    "range_user_ids",
+		RangeUserText:   "range_user_text",
+		IsSing:          "is_sing",
+		IsBack:          "is_back",
+		OutCondition:    "out_condition",
+		Setleft:         "setleft",
+		Settop:          "settop",
+		Style:           "style",
+		IsDel:           "is_del",
+		Updatetime:      "updatetime",
+		Dateline:        "dateline",
+		WfMode:          "wf_mode",
+		WfAction:        "wf_action",
+		WorkSql:         "work_sql",
+		WorkMsg:         "work_msg",
+	}
+)
+
+// FindOne is a convenience method for Model.FindOne.
+// See Model.FindOne.
+func FindOne(where ...interface{}) (*Entity, error) {
+	return Model.FindOne(where...)
+}
+
+// FindAll is a convenience method for Model.FindAll.
+// See Model.FindAll.
+func FindAll(where ...interface{}) ([]*Entity, error) {
+	return Model.FindAll(where...)
+}
+
+// FindValue is a convenience method for Model.FindValue.
+// See Model.FindValue.
+func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
+	return Model.FindValue(fieldsAndWhere...)
+}
+
+// FindArray is a convenience method for Model.FindArray.
+// See Model.FindArray.
+func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) {
+	return Model.FindArray(fieldsAndWhere...)
+}
+
+// FindCount is a convenience method for Model.FindCount.
+// See Model.FindCount.
+func FindCount(where ...interface{}) (int, error) {
+	return Model.FindCount(where...)
+}
+
+// Insert is a convenience method for Model.Insert.
+func Insert(data ...interface{}) (result sql.Result, err error) {
+	return Model.Insert(data...)
+}
+
+// InsertIgnore is a convenience method for Model.InsertIgnore.
+func InsertIgnore(data ...interface{}) (result sql.Result, err error) {
+	return Model.InsertIgnore(data...)
+}
+
+// Replace is a convenience method for Model.Replace.
+func Replace(data ...interface{}) (result sql.Result, err error) {
+	return Model.Replace(data...)
+}
+
+// Save is a convenience method for Model.Save.
+func Save(data ...interface{}) (result sql.Result, err error) {
+	return Model.Save(data...)
+}
+
+// Update is a convenience method for Model.Update.
+func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
+	return Model.Update(dataAndWhere...)
+}
+
+// Delete is a convenience method for Model.Delete.
+func Delete(where ...interface{}) (result sql.Result, err error) {
+	return Model.Delete(where...)
+}
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 154 - 0
app/model/admin/wf_news/news.go

@@ -0,0 +1,154 @@
+// ==========================================================================
+// 生成日期:2020-09-17 10:13:16
+// 生成人:gfast
+// ==========================================================================
+package wf_news
+
+import (
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/util/gconv"
+)
+
+// AddReq 用于存储新增请求的请求参数
+type AddReq struct {
+	Title string `p:"title" `
+	Uid   uint64
+}
+
+// EditReq 用于存储修改请求参数
+type EditReq struct {
+	Id    int64  `p:"id" v:"required#主键ID不能为空"`
+	Title string `p:"title" `
+}
+type RemoveReq struct {
+	Ids []int `p:"ids"` //删除id
+}
+
+// SelectPageReq 用于存储分页查询的请求参数
+type SelectPageReq struct {
+	Title     string `p:"title"`     //标题
+	Status    string `p:"status"`    //-1回退修改0 保存中1流程中 2通过
+	BeginTime string `p:"beginTime"` //开始时间
+	EndTime   string `p:"endTime"`   //结束时间
+	PageNum   int64  `p:"pageNum"`   //当前页码
+	PageSize  int    `p:"pageSize"`  //每页数
+}
+
+//待操作按钮的信息
+type InfoBtn struct {
+	*Entity
+	ActionBtn g.MapStrAny `json:"action_btn"`
+}
+
+// GetPlugAdByID 根据ID查询记录
+func GetByID(id int64) (*Entity, error) {
+	entity, err := Model.FindOne(id)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("根据ID查询记录出错")
+	}
+	if entity == nil {
+		err = gerror.New("根据ID未能查询到记录")
+	}
+	return entity, nil
+}
+
+// AddSave 添加
+func AddSave(req *AddReq) error {
+	entity := new(Entity)
+	entity.Title = req.Title
+	entity.Uid = req.Uid
+	result, err := entity.Insert()
+	if err != nil {
+		return err
+	}
+	_, err = result.LastInsertId()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 删除
+func DeleteByIds(Ids []int, tx *gdb.TX) error {
+	_, err := Model.TX(tx).Delete("id in(?)", Ids)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("删除失败")
+	}
+	return nil
+}
+
+// 根据ID来修改信息
+func EditSave(req *EditReq) error {
+	// 先根据ID来查询要修改的记录
+	entity, err := GetByID(req.Id)
+	if err != nil {
+		return err
+	}
+	// 修改实体
+	entity.Title = req.Title
+	entity.UpTime = gconv.Uint64(gtime.Timestamp())
+	_, err = Model.Save(entity)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("修改失败")
+	}
+	return nil
+}
+
+// 分页查询,返回值total总记录数,page当前页
+func SelectListByPage(req *SelectPageReq) (total int, page int64, list []*Entity, err error) {
+	model := Model
+	if req != nil {
+		if req.Title != "" {
+			model = model.Where("title like ?", "%"+req.Title+"%")
+		}
+		if req.Status != "" {
+			model = model.Where("status", gconv.Int(req.Status))
+		}
+	}
+	// 查询总记录数(总行数)
+	total, err = model.Count()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取总记录数失败")
+		return
+	}
+	if req.PageNum == 0 {
+		req.PageNum = 1
+	}
+	page = req.PageNum
+	if req.PageSize == 0 {
+		req.PageSize = 10
+	}
+	// 分页排序查询
+	list, err = model.Page(int(page), int(req.PageSize)).Order("id asc").All()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("分页查询失败")
+		return
+	}
+	return
+}
+
+// 获取所有数据
+func SelectListAll(req *SelectPageReq) (list []*Entity, err error) {
+	model := Model
+	if req != nil {
+		if req.Title != "" {
+			model.Where("title like ?", "%"+req.Title+"%")
+		}
+	}
+	// 查询
+	list, err = model.Order("id asc").All()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("查询失败")
+		return
+	}
+	return
+}

+ 61 - 0
app/model/admin/wf_news/news_entity.go

@@ -0,0 +1,61 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_news
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+)
+
+// Entity is the golang structure for table wf_news.
+type Entity struct {
+	Id     uint   `orm:"id,primary" json:"id"`     // 主键
+	Title  string `orm:"title"      json:"title"`  // 标题
+	Status int    `orm:"status"     json:"status"` // -1回退修改0 保存中1流程中 2通过
+	UpTime uint64 `orm:"up_time" json:"up_time"`   //更新日期
+	Uid    uint64 `orm:"uid" json:"uid"`           //添加人
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 351 - 0
app/model/admin/wf_news/news_model.go

@@ -0,0 +1,351 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_news
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table wf_news operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of wf_news.
+	Table = "wf_news"
+	// Model is the model object of wf_news.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table wf_news.
+	Columns = struct {
+		Id     string // 主键
+		Title  string // 标题
+		Status string // -1回退修改0 保存中1流程中 2通过
+		UpTime string //更新日期
+		Uid    string //添加人
+	}{
+		Id:     "id",
+		Title:  "title",
+		Status: "status",
+		UpTime: "up_time",
+		Uid:    "uid",
+	}
+)
+
+// FindOne is a convenience method for Model.FindOne.
+// See Model.FindOne.
+func FindOne(where ...interface{}) (*Entity, error) {
+	return Model.FindOne(where...)
+}
+
+// FindAll is a convenience method for Model.FindAll.
+// See Model.FindAll.
+func FindAll(where ...interface{}) ([]*Entity, error) {
+	return Model.FindAll(where...)
+}
+
+// FindValue is a convenience method for Model.FindValue.
+// See Model.FindValue.
+func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
+	return Model.FindValue(fieldsAndWhere...)
+}
+
+// FindArray is a convenience method for Model.FindArray.
+// See Model.FindArray.
+func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) {
+	return Model.FindArray(fieldsAndWhere...)
+}
+
+// FindCount is a convenience method for Model.FindCount.
+// See Model.FindCount.
+func FindCount(where ...interface{}) (int, error) {
+	return Model.FindCount(where...)
+}
+
+// Insert is a convenience method for Model.Insert.
+func Insert(data ...interface{}) (result sql.Result, err error) {
+	return Model.Insert(data...)
+}
+
+// InsertIgnore is a convenience method for Model.InsertIgnore.
+func InsertIgnore(data ...interface{}) (result sql.Result, err error) {
+	return Model.InsertIgnore(data...)
+}
+
+// Replace is a convenience method for Model.Replace.
+func Replace(data ...interface{}) (result sql.Result, err error) {
+	return Model.Replace(data...)
+}
+
+// Save is a convenience method for Model.Save.
+func Save(data ...interface{}) (result sql.Result, err error) {
+	return Model.Save(data...)
+}
+
+// Update is a convenience method for Model.Update.
+func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
+	return Model.Update(dataAndWhere...)
+}
+
+// Delete is a convenience method for Model.Delete.
+func Delete(where ...interface{}) (result sql.Result, err error) {
+	return Model.Delete(where...)
+}
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 80 - 0
app/model/admin/wf_run/run.go

@@ -0,0 +1,80 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package wf_run
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+)
+
+type RunSearch struct {
+	FromId    uint   `json:"from_id"`
+	FromTable string `json:"from_table"`
+	IsDel     uint   `json:"is_del"`
+	Status    uint   `json:"status"`
+}
+
+type RunAddData struct {
+	Pid            uint   `p:"pid"              json:"pid"`              // work_run父流转公文ID 值大于0则这个是子流程,完成后或者要返回父流程
+	Uid            uint   `p:"uid"              json:"uid"`              //
+	FlowId         uint   `p:"flow_id"          json:"flow_id"`          // 流程id 正常流程
+	FromTable      string `p:"from_table"       json:"from_table"`       // 单据表,不带前缀
+	FromTitle      string `p:"from_title"       json:"from_title"`       // 业务表标题字段
+	FromStatus     string `p:"from_status"       json:"from_status"`     // 业务表审批状态字段
+	FromId         int    `p:"from_id"          json:"from_id"`          //
+	RunName        string `p:"run_name"         json:"run_name"`         // 公文名称
+	RunFlowId      uint   `p:"run_flow_id"      json:"run_flow_id"`      // 流转到什么流程 最新流程,查询优化,进入子流程时将简化查询,子流程与父流程同步
+	RunFlowProcess string `p:"run_flow_process" json:"run_flow_process"` // 流转到第几步
+	Dateline       uint   `p:"dateline"         json:"dateline"`         //
+}
+
+//获取流程运行信息
+func GetRunning(where *RunSearch) (entity *Entity, err error) {
+	entity, err = Model.Where(where).Order("id desc").FindOne()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("判断运行状态出错")
+	}
+	return
+}
+
+func GetRuns(where interface{}, args ...interface{}) (entities []*Entity, err error) {
+	entities, err = Model.Where(where, args).Order("id asc").FindAll()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取流程运行状态失败")
+	}
+	return
+}
+
+//获取流程运行信息
+func GetRunById(id uint) (entity *Entity, err error) {
+	entity, err = Model.FindOne(id)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取运行数据失败")
+		return
+	}
+	return
+}
+
+func Add(data *RunAddData, tx *gdb.TX) (id int64, err error) {
+	var res sql.Result
+	res, err = Model.TX(tx).Save(data)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("保存业务流程运行信息出错")
+		return
+	}
+	id, err = res.LastInsertId()
+	return
+}
+
+func UpdateRun(runId uint, data g.Map, tx *gdb.TX) error {
+	_, err := Model.TX(tx).Where("id", runId).Update(data)
+	return err
+}

+ 78 - 0
app/model/admin/wf_run/run_entity.go

@@ -0,0 +1,78 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_run
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+)
+
+// Entity is the golang structure for table wf_run.
+type Entity struct {
+	Id             uint   `orm:"id,primary"       json:"id"`               //
+	Pid            uint   `orm:"pid"              json:"pid"`              // work_run父流转公文ID 值大于0则这个是子流程,完成后或者要返回父流程
+	FromTable      string `orm:"from_table"       json:"from_table"`       // 单据表,不带前缀
+	FromId         int    `orm:"from_id"          json:"from_id"`          //
+	FromTitle      string `orm:"from_title"       json:"from_title"`       // 业务表标题字段
+	FromStatus     string `orm:"from_status"       json:"from_status"`     // 业务表审批状态字段
+	PidFlowStep    uint   `orm:"pid_flow_step"    json:"pid_flow_step"`    // 父pid的flow_id中的第几步骤进入的,取回这个work_flow_step的child_over决定结束子流程的动作
+	CacheRunId     uint   `orm:"cache_run_id"     json:"cache_run_id"`     // 多个子流程时pid无法识别cache所以加这个字段pid>0
+	Uid            uint   `orm:"uid"              json:"uid"`              //
+	FlowId         uint   `orm:"flow_id"          json:"flow_id"`          // 流程id 正常流程
+	CatId          uint   `orm:"cat_id"           json:"cat_id"`           // 流程分类ID即公文分类ID
+	RunName        string `orm:"run_name"         json:"run_name"`         // 公文名称
+	RunFlowId      uint   `orm:"run_flow_id"      json:"run_flow_id"`      // 流转到什么流程 最新流程,查询优化,进入子流程时将简化查询,子流程与父流程同步
+	RunFlowProcess string `orm:"run_flow_process" json:"run_flow_process"` // 流转到第几步
+	AttIds         string `orm:"att_ids"          json:"att_ids"`          // 公文附件ids
+	Endtime        uint   `orm:"endtime"          json:"endtime"`          // 结束时间
+	Status         uint   `orm:"status"           json:"status"`           // 状态,0流程中,1通过,2回退
+	IsDel          uint   `orm:"is_del"           json:"is_del"`           //
+	Updatetime     uint   `orm:"updatetime"       json:"updatetime"`       //
+	Dateline       uint   `orm:"dateline"         json:"dateline"`         //
+	IsSing         int    `orm:"is_sing"          json:"is_sing"`          //
+	SingId         int    `orm:"sing_id"          json:"sing_id"`          //
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 385 - 0
app/model/admin/wf_run/run_model.go

@@ -0,0 +1,385 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_run
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table wf_run operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of wf_run.
+	Table = "wf_run"
+	// Model is the model object of wf_run.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table wf_run.
+	Columns = struct {
+		Id             string //
+		Pid            string // work_run父流转公文ID 值大于0则这个是子流程,完成后或者要返回父流程
+		FromTable      string // 单据表,不带前缀
+		FromId         string //
+		FromTitle      string //业务表审批状态字段
+		FromStatus     string //业务表审批状态字段
+		PidFlowStep    string // 父pid的flow_id中的第几步骤进入的,取回这个work_flow_step的child_over决定结束子流程的动作
+		CacheRunId     string // 多个子流程时pid无法识别cache所以加这个字段pid>0
+		Uid            string //
+		FlowId         string // 流程id 正常流程
+		CatId          string // 流程分类ID即公文分类ID
+		RunName        string // 公文名称
+		RunFlowId      string // 流转到什么流程 最新流程,查询优化,进入子流程时将简化查询,子流程与父流程同步
+		RunFlowProcess string // 流转到第几步
+		AttIds         string // 公文附件ids
+		Endtime        string // 结束时间
+		Status         string // 状态,0流程中,1通过,2回退
+		IsDel          string //
+		Updatetime     string //
+		Dateline       string //
+		IsSing         string //
+		SingId         string //
+	}{
+		Id:             "id",
+		Pid:            "pid",
+		FromTable:      "from_table",
+		FromId:         "from_id",
+		FromTitle:      "from_title",
+		FromStatus:     "from_status",
+		PidFlowStep:    "pid_flow_step",
+		CacheRunId:     "cache_run_id",
+		Uid:            "uid",
+		FlowId:         "flow_id",
+		CatId:          "cat_id",
+		RunName:        "run_name",
+		RunFlowId:      "run_flow_id",
+		RunFlowProcess: "run_flow_process",
+		AttIds:         "att_ids",
+		Endtime:        "endtime",
+		Status:         "status",
+		IsDel:          "is_del",
+		Updatetime:     "updatetime",
+		Dateline:       "dateline",
+		IsSing:         "is_sing",
+		SingId:         "sing_id",
+	}
+)
+
+// FindOne is a convenience method for Model.FindOne.
+// See Model.FindOne.
+func FindOne(where ...interface{}) (*Entity, error) {
+	return Model.FindOne(where...)
+}
+
+// FindAll is a convenience method for Model.FindAll.
+// See Model.FindAll.
+func FindAll(where ...interface{}) ([]*Entity, error) {
+	return Model.FindAll(where...)
+}
+
+// FindValue is a convenience method for Model.FindValue.
+// See Model.FindValue.
+func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
+	return Model.FindValue(fieldsAndWhere...)
+}
+
+// FindArray is a convenience method for Model.FindArray.
+// See Model.FindArray.
+func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) {
+	return Model.FindArray(fieldsAndWhere...)
+}
+
+// FindCount is a convenience method for Model.FindCount.
+// See Model.FindCount.
+func FindCount(where ...interface{}) (int, error) {
+	return Model.FindCount(where...)
+}
+
+// Insert is a convenience method for Model.Insert.
+func Insert(data ...interface{}) (result sql.Result, err error) {
+	return Model.Insert(data...)
+}
+
+// InsertIgnore is a convenience method for Model.InsertIgnore.
+func InsertIgnore(data ...interface{}) (result sql.Result, err error) {
+	return Model.InsertIgnore(data...)
+}
+
+// Replace is a convenience method for Model.Replace.
+func Replace(data ...interface{}) (result sql.Result, err error) {
+	return Model.Replace(data...)
+}
+
+// Save is a convenience method for Model.Save.
+func Save(data ...interface{}) (result sql.Result, err error) {
+	return Model.Save(data...)
+}
+
+// Update is a convenience method for Model.Update.
+func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
+	return Model.Update(dataAndWhere...)
+}
+
+// Delete is a convenience method for Model.Delete.
+func Delete(where ...interface{}) (result sql.Result, err error) {
+	return Model.Delete(where...)
+}
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 30 - 0
app/model/admin/wf_run_cache/wf_run_cache.go

@@ -0,0 +1,30 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package wf_run_cache
+
+import (
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+)
+
+type AddData struct {
+	RunId          uint   `p:"run_id"           json:"run_id"`           // 缓存run工作的全部流程模板步骤等信息,确保修改流程后工作依然不变
+	FormId         uint   `p:"form_id"          json:"form_id"`          //
+	FlowId         uint   `p:"flow_id"          json:"flow_id"`          // 流程ID
+	RunForm        string `p:"run_form"         json:"run_form"`         // 模板信息
+	RunFlow        string `p:"run_flow"         json:"run_flow"`         // 流程信息
+	RunFlowProcess string `p:"run_flow_process" json:"run_flow_process"` // 流程步骤信息
+	Dateline       uint   `p:"dateline"         json:"dateline"`         //
+}
+
+func Add(data *AddData, tx *gdb.TX) error {
+	_, err := Model.TX(tx).Save(data)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("保存流程日志信息失败")
+	}
+	return nil
+}

+ 66 - 0
app/model/admin/wf_run_cache/wf_run_cache_entity.go

@@ -0,0 +1,66 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_run_cache
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+)
+
+// Entity is the golang structure for table wf_run_cache.
+type Entity struct {
+	Id             uint   `orm:"id,primary"       json:"id"`               //
+	RunId          uint   `orm:"run_id"           json:"run_id"`           // 缓存run工作的全部流程模板步骤等信息,确保修改流程后工作依然不变
+	FormId         uint   `orm:"form_id"          json:"form_id"`          //
+	FlowId         uint   `orm:"flow_id"          json:"flow_id"`          // 流程ID
+	RunForm        string `orm:"run_form"         json:"run_form"`         // 模板信息
+	RunFlow        string `orm:"run_flow"         json:"run_flow"`         // 流程信息
+	RunFlowProcess string `orm:"run_flow_process" json:"run_flow_process"` // 流程步骤信息
+	IsDel          uint   `orm:"is_del"           json:"is_del"`           //
+	Updatetime     uint   `orm:"updatetime"       json:"updatetime"`       //
+	Dateline       uint   `orm:"dateline"         json:"dateline"`         //
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 361 - 0
app/model/admin/wf_run_cache/wf_run_cache_model.go

@@ -0,0 +1,361 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_run_cache
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table wf_run_cache operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of wf_run_cache.
+	Table = "wf_run_cache"
+	// Model is the model object of wf_run_cache.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table wf_run_cache.
+	Columns = struct {
+		Id             string //
+		RunId          string // 缓存run工作的全部流程模板步骤等信息,确保修改流程后工作依然不变
+		FormId         string //
+		FlowId         string // 流程ID
+		RunForm        string // 模板信息
+		RunFlow        string // 流程信息
+		RunFlowProcess string // 流程步骤信息
+		IsDel          string //
+		Updatetime     string //
+		Dateline       string //
+	}{
+		Id:             "id",
+		RunId:          "run_id",
+		FormId:         "form_id",
+		FlowId:         "flow_id",
+		RunForm:        "run_form",
+		RunFlow:        "run_flow",
+		RunFlowProcess: "run_flow_process",
+		IsDel:          "is_del",
+		Updatetime:     "updatetime",
+		Dateline:       "dateline",
+	}
+)
+
+// FindOne is a convenience method for Model.FindOne.
+// See Model.FindOne.
+func FindOne(where ...interface{}) (*Entity, error) {
+	return Model.FindOne(where...)
+}
+
+// FindAll is a convenience method for Model.FindAll.
+// See Model.FindAll.
+func FindAll(where ...interface{}) ([]*Entity, error) {
+	return Model.FindAll(where...)
+}
+
+// FindValue is a convenience method for Model.FindValue.
+// See Model.FindValue.
+func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
+	return Model.FindValue(fieldsAndWhere...)
+}
+
+// FindArray is a convenience method for Model.FindArray.
+// See Model.FindArray.
+func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) {
+	return Model.FindArray(fieldsAndWhere...)
+}
+
+// FindCount is a convenience method for Model.FindCount.
+// See Model.FindCount.
+func FindCount(where ...interface{}) (int, error) {
+	return Model.FindCount(where...)
+}
+
+// Insert is a convenience method for Model.Insert.
+func Insert(data ...interface{}) (result sql.Result, err error) {
+	return Model.Insert(data...)
+}
+
+// InsertIgnore is a convenience method for Model.InsertIgnore.
+func InsertIgnore(data ...interface{}) (result sql.Result, err error) {
+	return Model.InsertIgnore(data...)
+}
+
+// Replace is a convenience method for Model.Replace.
+func Replace(data ...interface{}) (result sql.Result, err error) {
+	return Model.Replace(data...)
+}
+
+// Save is a convenience method for Model.Save.
+func Save(data ...interface{}) (result sql.Result, err error) {
+	return Model.Save(data...)
+}
+
+// Update is a convenience method for Model.Update.
+func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
+	return Model.Update(dataAndWhere...)
+}
+
+// Delete is a convenience method for Model.Delete.
+func Delete(where ...interface{}) (result sql.Result, err error) {
+	return Model.Delete(where...)
+}
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 42 - 0
app/model/admin/wf_run_log/wf_run_log.go

@@ -0,0 +1,42 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package wf_run_log
+
+import (
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+)
+
+type AddData struct {
+	Uid       uint   `p:"uid"        json:"uid"`        // 用户ID
+	FromId    int    `p:"from_id"    json:"from_id"`    // 单据ID
+	FromTable string `p:"from_table" json:"from_table"` // 单据表
+	RunId     uint   `p:"run_id"     json:"run_id"`     // 流转id
+	RunFlow   uint   `p:"run_flow"   json:"run_flow"`   // 流程ID
+	Content   string `p:"content"    json:"content"`    // 日志内容
+	Dateline  uint   `p:"dateline"   json:"dateline"`   // 添加时间
+	Btn       string `p:"btn"        json:"btn"`        // 提交操作信息
+	Art       string `p:"art"        json:"art"`        // 附件日志
+	WorkInfo  string `p:"work_info"  json:"work_info"`  // 事务日志
+}
+
+func Add(data *AddData, tx *gdb.TX) (err error) {
+	_, err = Model.TX(tx).Save(data)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("工作流审批日志记录失败")
+	}
+	return
+}
+
+func GetRunLog(where g.Map) (entities []*Entity, err error) {
+	entities, err = Model.FindAll(where)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取日志信息失败")
+	}
+	return
+}

+ 67 - 0
app/model/admin/wf_run_log/wf_run_log_entity.go

@@ -0,0 +1,67 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_run_log
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+)
+
+// Entity is the golang structure for table wf_run_log.
+type Entity struct {
+	Id        uint   `orm:"id,primary" json:"id"`         //
+	Uid       uint   `orm:"uid"        json:"uid"`        // 用户ID
+	FromId    int    `orm:"from_id"    json:"from_id"`    // 单据ID
+	FromTable string `orm:"from_table" json:"from_table"` // 单据表
+	RunId     uint   `orm:"run_id"     json:"run_id"`     // 流转id
+	RunFlow   uint   `orm:"run_flow"   json:"run_flow"`   // 流程ID
+	Content   string `orm:"content"    json:"content"`    // 日志内容
+	Dateline  uint   `orm:"dateline"   json:"dateline"`   // 添加时间
+	Btn       string `orm:"btn"        json:"btn"`        // 提交操作信息
+	Art       string `orm:"art"        json:"art"`        // 附件日志
+	WorkInfo  string `orm:"work_info"  json:"work_info"`  // 事务日志
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 363 - 0
app/model/admin/wf_run_log/wf_run_log_model.go

@@ -0,0 +1,363 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_run_log
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table wf_run_log operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of wf_run_log.
+	Table = "wf_run_log"
+	// Model is the model object of wf_run_log.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table wf_run_log.
+	Columns = struct {
+		Id        string //
+		Uid       string // 用户ID
+		FromId    string // 单据ID
+		FromTable string // 单据表
+		RunId     string // 流转id
+		RunFlow   string // 流程ID
+		Content   string // 日志内容
+		Dateline  string // 添加时间
+		Btn       string // 提交操作信息
+		Art       string // 附件日志
+		WorkInfo  string // 事务日志
+	}{
+		Id:        "id",
+		Uid:       "uid",
+		FromId:    "from_id",
+		FromTable: "from_table",
+		RunId:     "run_id",
+		RunFlow:   "run_flow",
+		Content:   "content",
+		Dateline:  "dateline",
+		Btn:       "btn",
+		Art:       "art",
+		WorkInfo:  "work_info",
+	}
+)
+
+// FindOne is a convenience method for Model.FindOne.
+// See Model.FindOne.
+func FindOne(where ...interface{}) (*Entity, error) {
+	return Model.FindOne(where...)
+}
+
+// FindAll is a convenience method for Model.FindAll.
+// See Model.FindAll.
+func FindAll(where ...interface{}) ([]*Entity, error) {
+	return Model.FindAll(where...)
+}
+
+// FindValue is a convenience method for Model.FindValue.
+// See Model.FindValue.
+func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
+	return Model.FindValue(fieldsAndWhere...)
+}
+
+// FindArray is a convenience method for Model.FindArray.
+// See Model.FindArray.
+func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) {
+	return Model.FindArray(fieldsAndWhere...)
+}
+
+// FindCount is a convenience method for Model.FindCount.
+// See Model.FindCount.
+func FindCount(where ...interface{}) (int, error) {
+	return Model.FindCount(where...)
+}
+
+// Insert is a convenience method for Model.Insert.
+func Insert(data ...interface{}) (result sql.Result, err error) {
+	return Model.Insert(data...)
+}
+
+// InsertIgnore is a convenience method for Model.InsertIgnore.
+func InsertIgnore(data ...interface{}) (result sql.Result, err error) {
+	return Model.InsertIgnore(data...)
+}
+
+// Replace is a convenience method for Model.Replace.
+func Replace(data ...interface{}) (result sql.Result, err error) {
+	return Model.Replace(data...)
+}
+
+// Save is a convenience method for Model.Save.
+func Save(data ...interface{}) (result sql.Result, err error) {
+	return Model.Save(data...)
+}
+
+// Update is a convenience method for Model.Update.
+func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
+	return Model.Update(dataAndWhere...)
+}
+
+// Delete is a convenience method for Model.Delete.
+func Delete(where ...interface{}) (result sql.Result, err error) {
+	return Model.Delete(where...)
+}
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 116 - 0
app/model/admin/wf_run_process/run_process.go

@@ -0,0 +1,116 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package wf_run_process
+
+import (
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/util/gconv"
+)
+
+type SearchRunProcess struct {
+	RunId          uint   `json:"run_id"`
+	RunFlow        uint   `json:"run_flow"`
+	RunFlowProcess string `json:"run_flow_process"`
+	Status         string `json:"status"`
+}
+
+type SaveRunProcessData struct {
+	Uid               uint   `p:"uid"                 json:"uid"`                 //
+	RunId             uint   `p:"run_id"              json:"run_id"`              // 当前流转id
+	RunFlow           uint   `p:"run_flow"            json:"run_flow"`            // 属于那个流程的id
+	RunFlowProcess    uint   `p:"run_flow_process"    json:"run_flow_process"`    // 当前步骤编号
+	ParentFlow        uint   `p:"parent_flow"         json:"parent_flow"`         // 上一步流程
+	ParentFlowProcess uint   `p:"parent_flow_process" json:"parent_flow_process"` // 上一步骤号
+	RunChild          uint   `p:"run_child"           json:"run_child"`           // 开始转入子流程run_id 如果转入子流程,则在这里也记录
+	Remark            string `p:"remark"              json:"remark"`              // 备注
+	IsSponsor         uint   `p:"is_sponsor"          json:"is_sponsor"`          // 是否步骤主办人 0否(默认) 1是
+	Status            uint   `p:"status"              json:"status"`              // 状态 0为未接收(默认),1为办理中 ,2为已转交,3为已结束4为已打回
+	SponsorIds        string `p:"sponsor_ids"         json:"sponsor_ids"`         //
+	SponsorText       string `p:"sponsor_text"        json:"sponsor_text"`        //
+	AutoPerson        int    `p:"auto_person"         json:"auto_person"`         //
+	JsTime            uint   `p:"js_time"             json:"js_time"`             // 接收时间
+	Dateline          uint   `p:"dateline"            json:"dateline"`            //
+	WfMode            int    `p:"wf_mode"             json:"wf_mode"`             //
+	WfAction          string `p:"wf_action"           json:"wf_action"`           //
+}
+
+//获取流程运行步骤信息列表
+func GetProcessList(where *SearchRunProcess) (list []*Entity, err error) {
+	model := Model.Where(Columns.RunId, where.RunId).
+		And(Columns.RunFlowProcess+" in (?)  OR FIND_IN_SET("+Columns.RunFlowProcess+",?)", where.RunFlowProcess, where.RunFlowProcess)
+	if where.RunFlow != 0 {
+		model = model.And(Columns.RunFlow, where.RunFlow)
+	}
+	if where.Status != "" {
+		model = model.And(Columns.Status, gconv.Int(where.Status))
+	}
+	list, err = model.FindAll()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取流程运行步骤列表信息失败")
+		return
+	}
+	return
+}
+
+//获取流程运行步骤信息
+func GetProcess(where *SearchRunProcess) (info *Entity, err error) {
+	model := Model.Where(Columns.RunId, where.RunId).
+		And(Columns.RunFlowProcess+" in (?)  OR FIND_IN_SET("+Columns.RunFlowProcess+",?)", where.RunFlowProcess, where.RunFlowProcess)
+	if where.RunFlow != 0 {
+		model = model.And(Columns.RunFlow, where.RunFlow)
+	}
+	if where.Status != "" {
+		model = model.And(Columns.Status, gconv.Int(where.Status))
+	}
+	info, err = model.FindOne()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取流程运行步骤信息失败")
+		return
+	}
+	return
+}
+
+func GetProcessByMap(where g.Map) (list []*Entity, err error) {
+	list, err = Model.FindAll(where)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取流程运行步骤失败")
+	}
+	return
+}
+
+//通过id获取步骤运行信息
+func GetProcessById(id uint) (info *Entity, err error) {
+	info, err = Model.FindOne(id)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取流程运转信息失败")
+	}
+	return
+}
+
+//获取本流程中小于ID的步骤信息
+func GetProcessLtId(where *Entity) (list []*Entity, err error) {
+	list, err = Model.Where("id < ?", where.Id).Where("run_flow", where.RunFlow).
+		Where("run_id", where.RunId).FindAll()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取小于指定步骤流程运转信息失败")
+	}
+	return
+}
+
+func Add(data *SaveRunProcessData, tx *gdb.TX) error {
+	_, err := Model.TX(tx).Save(data)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("保存流程步骤日志失败")
+	}
+	return nil
+}

+ 81 - 0
app/model/admin/wf_run_process/run_process_entity.go

@@ -0,0 +1,81 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_run_process
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+)
+
+// Entity is the golang structure for table wf_run_process.
+type Entity struct {
+	Id                uint   `orm:"id,primary"          json:"id"`                  //
+	Uid               uint   `orm:"uid"                 json:"uid"`                 //
+	RunId             uint   `orm:"run_id"              json:"run_id"`              // 当前流转id
+	RunFlow           uint   `orm:"run_flow"            json:"run_flow"`            // 属于那个流程的id
+	RunFlowProcess    uint   `orm:"run_flow_process"    json:"run_flow_process"`    // 当前步骤编号
+	ParentFlow        uint   `orm:"parent_flow"         json:"parent_flow"`         // 上一步流程
+	ParentFlowProcess uint   `orm:"parent_flow_process" json:"parent_flow_process"` // 上一步骤号
+	RunChild          uint   `orm:"run_child"           json:"run_child"`           // 开始转入子流程run_id 如果转入子流程,则在这里也记录
+	Remark            string `orm:"remark"              json:"remark"`              // 备注
+	IsReceiveType     uint   `orm:"is_receive_type"     json:"is_receive_type"`     // 是否先接收人为主办人
+	AutoPerson        int    `orm:"auto_person"         json:"auto_person"`         //
+	SponsorText       string `orm:"sponsor_text"        json:"sponsor_text"`        //
+	SponsorIds        string `orm:"sponsor_ids"         json:"sponsor_ids"`         //
+	IsSponsor         uint   `orm:"is_sponsor"          json:"is_sponsor"`          // 是否步骤主办人 0否(默认) 1是
+	IsSingpost        uint   `orm:"is_singpost"         json:"is_singpost"`         // 是否已会签过
+	IsBack            uint   `orm:"is_back"             json:"is_back"`             // 被退回的 0否(默认) 1是
+	Status            uint   `orm:"status"              json:"status"`              // 状态 0为未接收(默认),1为办理中 ,2为已转交,3为已结束4为已打回
+	JsTime            uint   `orm:"js_time"             json:"js_time"`             // 接收时间
+	BlTime            uint   `orm:"bl_time"             json:"bl_time"`             // 办理时间
+	JjTime            uint   `orm:"jj_time"             json:"jj_time"`             // 转交时间,最后一步等同办结时间
+	IsDel             uint   `orm:"is_del"              json:"is_del"`              //
+	Updatetime        uint   `orm:"updatetime"          json:"updatetime"`          //
+	Dateline          uint   `orm:"dateline"            json:"dateline"`            //
+	WfMode            int    `orm:"wf_mode"             json:"wf_mode"`             //
+	WfAction          string `orm:"wf_action"           json:"wf_action"`           //
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 391 - 0
app/model/admin/wf_run_process/run_process_model.go

@@ -0,0 +1,391 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_run_process
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table wf_run_process operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of wf_run_process.
+	Table = "wf_run_process"
+	// Model is the model object of wf_run_process.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table wf_run_process.
+	Columns = struct {
+		Id                string //
+		Uid               string //
+		RunId             string // 当前流转id
+		RunFlow           string // 属于那个流程的id
+		RunFlowProcess    string // 当前步骤编号
+		ParentFlow        string // 上一步流程
+		ParentFlowProcess string // 上一步骤号
+		RunChild          string // 开始转入子流程run_id 如果转入子流程,则在这里也记录
+		Remark            string // 备注
+		IsReceiveType     string // 是否先接收人为主办人
+		AutoPerson        string //
+		SponsorText       string //
+		SponsorIds        string //
+		IsSponsor         string // 是否步骤主办人 0否(默认) 1是
+		IsSingpost        string // 是否已会签过
+		IsBack            string // 被退回的 0否(默认) 1是
+		Status            string // 状态 0为未接收(默认),1为办理中 ,2为已转交,3为已结束4为已打回
+		JsTime            string // 接收时间
+		BlTime            string // 办理时间
+		JjTime            string // 转交时间,最后一步等同办结时间
+		IsDel             string //
+		Updatetime        string //
+		Dateline          string //
+		WfMode            string //
+		WfAction          string //
+	}{
+		Id:                "id",
+		Uid:               "uid",
+		RunId:             "run_id",
+		RunFlow:           "run_flow",
+		RunFlowProcess:    "run_flow_process",
+		ParentFlow:        "parent_flow",
+		ParentFlowProcess: "parent_flow_process",
+		RunChild:          "run_child",
+		Remark:            "remark",
+		IsReceiveType:     "is_receive_type",
+		AutoPerson:        "auto_person",
+		SponsorText:       "sponsor_text",
+		SponsorIds:        "sponsor_ids",
+		IsSponsor:         "is_sponsor",
+		IsSingpost:        "is_singpost",
+		IsBack:            "is_back",
+		Status:            "status",
+		JsTime:            "js_time",
+		BlTime:            "bl_time",
+		JjTime:            "jj_time",
+		IsDel:             "is_del",
+		Updatetime:        "updatetime",
+		Dateline:          "dateline",
+		WfMode:            "wf_mode",
+		WfAction:          "wf_action",
+	}
+)
+
+// FindOne is a convenience method for Model.FindOne.
+// See Model.FindOne.
+func FindOne(where ...interface{}) (*Entity, error) {
+	return Model.FindOne(where...)
+}
+
+// FindAll is a convenience method for Model.FindAll.
+// See Model.FindAll.
+func FindAll(where ...interface{}) ([]*Entity, error) {
+	return Model.FindAll(where...)
+}
+
+// FindValue is a convenience method for Model.FindValue.
+// See Model.FindValue.
+func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
+	return Model.FindValue(fieldsAndWhere...)
+}
+
+// FindArray is a convenience method for Model.FindArray.
+// See Model.FindArray.
+func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) {
+	return Model.FindArray(fieldsAndWhere...)
+}
+
+// FindCount is a convenience method for Model.FindCount.
+// See Model.FindCount.
+func FindCount(where ...interface{}) (int, error) {
+	return Model.FindCount(where...)
+}
+
+// Insert is a convenience method for Model.Insert.
+func Insert(data ...interface{}) (result sql.Result, err error) {
+	return Model.Insert(data...)
+}
+
+// InsertIgnore is a convenience method for Model.InsertIgnore.
+func InsertIgnore(data ...interface{}) (result sql.Result, err error) {
+	return Model.InsertIgnore(data...)
+}
+
+// Replace is a convenience method for Model.Replace.
+func Replace(data ...interface{}) (result sql.Result, err error) {
+	return Model.Replace(data...)
+}
+
+// Save is a convenience method for Model.Save.
+func Save(data ...interface{}) (result sql.Result, err error) {
+	return Model.Save(data...)
+}
+
+// Update is a convenience method for Model.Update.
+func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
+	return Model.Update(dataAndWhere...)
+}
+
+// Delete is a convenience method for Model.Delete.
+func Delete(where ...interface{}) (result sql.Result, err error) {
+	return Model.Delete(where...)
+}
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 43 - 0
app/model/admin/wf_run_sign/run_sign.go

@@ -0,0 +1,43 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package wf_run_sign
+
+import (
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+)
+
+func GetSignInfoById(id uint) (entity *Entity, err error) {
+	entity, err = Model.FindOne(id)
+	if err != nil {
+		g.Log().Debug(err)
+		err = gerror.New("获取会签信息失败")
+	}
+	return
+}
+
+func AddSing(data g.Map, tx *gdb.TX) (int64, error) {
+	res, err := Model.TX(tx).Insert(data)
+	if err != nil {
+		g.Log().Error(err)
+		return 0, gerror.New("报错会签信息失败")
+	}
+	id, err := res.LastInsertId()
+	if err != nil {
+		g.Log().Error(err)
+		return 0, gerror.New("获取插入的会签ID失败")
+	}
+	return id, err
+}
+
+func UpdateSing(id int, data g.Map, tx *gdb.TX) error {
+	_, err := Model.TX(tx).Where(Columns.Id, id).Update(data)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("更新会签信息失败")
+	}
+	return nil
+}

+ 66 - 0
app/model/admin/wf_run_sign/run_sign_entity.go

@@ -0,0 +1,66 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_run_sign
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+)
+
+// Entity is the golang structure for table wf_run_sign.
+type Entity struct {
+	Id             uint   `orm:"id,primary"       json:"id"`               //
+	Uid            uint   `orm:"uid"              json:"uid"`              //
+	RunId          uint   `orm:"run_id"           json:"run_id"`           //
+	RunFlow        uint   `orm:"run_flow"         json:"run_flow"`         // 流程ID,子流程时区分run step
+	RunFlowProcess uint   `orm:"run_flow_process" json:"run_flow_process"` // 当前步骤编号
+	Content        string `orm:"content"          json:"content"`          // 会签内容
+	IsAgree        uint   `orm:"is_agree"         json:"is_agree"`         // 审核意见:1同意;2不同意
+	SignAttId      uint   `orm:"sign_att_id"      json:"sign_att_id"`      //
+	SignLook       uint   `orm:"sign_look"        json:"sign_look"`        // 步骤设置的会签可见性,0总是可见(默认),1本步骤经办人之间不可见2针对其他步骤不可见
+	Dateline       uint   `orm:"dateline"         json:"dateline"`         // 添加时间
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 361 - 0
app/model/admin/wf_run_sign/run_sign_model.go

@@ -0,0 +1,361 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_run_sign
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table wf_run_sign operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of wf_run_sign.
+	Table = "wf_run_sign"
+	// Model is the model object of wf_run_sign.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table wf_run_sign.
+	Columns = struct {
+		Id             string //
+		Uid            string //
+		RunId          string //
+		RunFlow        string // 流程ID,子流程时区分run step
+		RunFlowProcess string // 当前步骤编号
+		Content        string // 会签内容
+		IsAgree        string // 审核意见:1同意;2不同意
+		SignAttId      string //
+		SignLook       string // 步骤设置的会签可见性,0总是可见(默认),1本步骤经办人之间不可见2针对其他步骤不可见
+		Dateline       string // 添加时间
+	}{
+		Id:             "id",
+		Uid:            "uid",
+		RunId:          "run_id",
+		RunFlow:        "run_flow",
+		RunFlowProcess: "run_flow_process",
+		Content:        "content",
+		IsAgree:        "is_agree",
+		SignAttId:      "sign_att_id",
+		SignLook:       "sign_look",
+		Dateline:       "dateline",
+	}
+)
+
+// FindOne is a convenience method for Model.FindOne.
+// See Model.FindOne.
+func FindOne(where ...interface{}) (*Entity, error) {
+	return Model.FindOne(where...)
+}
+
+// FindAll is a convenience method for Model.FindAll.
+// See Model.FindAll.
+func FindAll(where ...interface{}) ([]*Entity, error) {
+	return Model.FindAll(where...)
+}
+
+// FindValue is a convenience method for Model.FindValue.
+// See Model.FindValue.
+func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
+	return Model.FindValue(fieldsAndWhere...)
+}
+
+// FindArray is a convenience method for Model.FindArray.
+// See Model.FindArray.
+func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) {
+	return Model.FindArray(fieldsAndWhere...)
+}
+
+// FindCount is a convenience method for Model.FindCount.
+// See Model.FindCount.
+func FindCount(where ...interface{}) (int, error) {
+	return Model.FindCount(where...)
+}
+
+// Insert is a convenience method for Model.Insert.
+func Insert(data ...interface{}) (result sql.Result, err error) {
+	return Model.Insert(data...)
+}
+
+// InsertIgnore is a convenience method for Model.InsertIgnore.
+func InsertIgnore(data ...interface{}) (result sql.Result, err error) {
+	return Model.InsertIgnore(data...)
+}
+
+// Replace is a convenience method for Model.Replace.
+func Replace(data ...interface{}) (result sql.Result, err error) {
+	return Model.Replace(data...)
+}
+
+// Save is a convenience method for Model.Save.
+func Save(data ...interface{}) (result sql.Result, err error) {
+	return Model.Save(data...)
+}
+
+// Update is a convenience method for Model.Update.
+func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
+	return Model.Update(dataAndWhere...)
+}
+
+// Delete is a convenience method for Model.Delete.
+func Delete(where ...interface{}) (result sql.Result, err error) {
+	return Model.Delete(where...)
+}
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 33 - 0
app/model/admin/wf_workinfo/wf_workinfo.go

@@ -0,0 +1,33 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package wf_workinfo
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gtime"
+)
+
+type AddData struct {
+	BillInfo string      `p:"bill_info"  json:"bill_info"` // 单据JSON
+	Data     string      `p:"data"       json:"data"`      // 处理数据
+	Info     string      `p:"info"       json:"info"`      // 处理结果
+	Datetime *gtime.Time `p:"datetime"   json:"datetime"`  //
+	Type     string      `p:"type"       json:"type"`      // 类型
+}
+
+func Add(data *AddData, tx *gdb.TX) (insId int64, err error) {
+	var res sql.Result
+	res, err = Model.TX(tx).Save(data)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("写入工作流信息失败")
+		return
+	}
+	insId, err = res.LastInsertId()
+	return
+}

+ 63 - 0
app/model/admin/wf_workinfo/wf_workinfo_entity.go

@@ -0,0 +1,63 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_workinfo
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/os/gtime"
+)
+
+// Entity is the golang structure for table wf_workinfo.
+type Entity struct {
+	Id       int         `orm:"id,primary" json:"id"`        //
+	BillInfo string      `orm:"bill_info"  json:"bill_info"` // 单据JSON
+	Data     string      `orm:"data"       json:"data"`      // 处理数据
+	Info     string      `orm:"info"       json:"info"`      // 处理结果
+	Datetime *gtime.Time `orm:"datetime"   json:"datetime"`  //
+	Type     string      `orm:"type"       json:"type"`      // 类型
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 353 - 0
app/model/admin/wf_workinfo/wf_workinfo_model.go

@@ -0,0 +1,353 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package wf_workinfo
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table wf_workinfo operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of wf_workinfo.
+	Table = "wf_workinfo"
+	// Model is the model object of wf_workinfo.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table wf_workinfo.
+	Columns = struct {
+		Id       string //
+		BillInfo string // 单据JSON
+		Data     string // 处理数据
+		Info     string // 处理结果
+		Datetime string //
+		Type     string // 类型
+	}{
+		Id:       "id",
+		BillInfo: "bill_info",
+		Data:     "data",
+		Info:     "info",
+		Datetime: "datetime",
+		Type:     "type",
+	}
+)
+
+// FindOne is a convenience method for Model.FindOne.
+// See Model.FindOne.
+func FindOne(where ...interface{}) (*Entity, error) {
+	return Model.FindOne(where...)
+}
+
+// FindAll is a convenience method for Model.FindAll.
+// See Model.FindAll.
+func FindAll(where ...interface{}) ([]*Entity, error) {
+	return Model.FindAll(where...)
+}
+
+// FindValue is a convenience method for Model.FindValue.
+// See Model.FindValue.
+func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
+	return Model.FindValue(fieldsAndWhere...)
+}
+
+// FindArray is a convenience method for Model.FindArray.
+// See Model.FindArray.
+func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) {
+	return Model.FindArray(fieldsAndWhere...)
+}
+
+// FindCount is a convenience method for Model.FindCount.
+// See Model.FindCount.
+func FindCount(where ...interface{}) (int, error) {
+	return Model.FindCount(where...)
+}
+
+// Insert is a convenience method for Model.Insert.
+func Insert(data ...interface{}) (result sql.Result, err error) {
+	return Model.Insert(data...)
+}
+
+// InsertIgnore is a convenience method for Model.InsertIgnore.
+func InsertIgnore(data ...interface{}) (result sql.Result, err error) {
+	return Model.InsertIgnore(data...)
+}
+
+// Replace is a convenience method for Model.Replace.
+func Replace(data ...interface{}) (result sql.Result, err error) {
+	return Model.Replace(data...)
+}
+
+// Save is a convenience method for Model.Save.
+func Save(data ...interface{}) (result sql.Result, err error) {
+	return Model.Save(data...)
+}
+
+// Update is a convenience method for Model.Update.
+func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
+	return Model.Update(dataAndWhere...)
+}
+
+// Delete is a convenience method for Model.Delete.
+func Delete(where ...interface{}) (result sql.Result, err error) {
+	return Model.Delete(where...)
+}
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 89 - 0
app/service/admin/flow_news_service/news.go

@@ -0,0 +1,89 @@
+// ==========================================================================
+// 生成日期:2020-09-17 10:13:16
+// 生成人:gfast
+// ==========================================================================
+package wf_news_service
+
+import (
+	wfNewsModel "gfast/app/model/admin/wf_news"
+	"gfast/app/model/admin/wf_run"
+	"gfast/app/service/common/work_flow_service"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+)
+
+// 添加
+func AddSave(req *wfNewsModel.AddReq) error {
+	return wfNewsModel.AddSave(req)
+}
+
+// 删除
+func DeleteByIds(Ids []int) error {
+	tx, err := g.DB().Begin()
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("事务开启失败")
+	}
+	//删除业务
+	err = wfNewsModel.DeleteByIds(Ids, tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	//删除运行流程信息
+	runList, err := wf_run.Model.Fields("id").Where("from_id in (?) and from_table=?", Ids, "wf_news").FindAll()
+	if err != nil {
+		g.Log().Error(err)
+		tx.Rollback()
+		return gerror.New("获取流程信息失败")
+	}
+	runIds := make([]uint, len(runList))
+	for key, run := range runList {
+		runIds[key] = run.Id
+	}
+	_, err = wf_run.Model.TX(tx).Delete("id in (?)", runIds)
+	if err != nil {
+		tx.Rollback()
+		g.Log().Error(err)
+		return gerror.New("删除流程运行信息失败")
+	}
+	tx.Commit()
+	return nil
+}
+
+//修改
+func EditSave(editReq *wfNewsModel.EditReq) error {
+	return wfNewsModel.EditSave(editReq)
+}
+
+// 根据ID查询
+func GetByID(id int64) (*wfNewsModel.Entity, error) {
+	return wfNewsModel.GetByID(id)
+}
+
+// 分页查询
+func SelectListByPage(req *wfNewsModel.SelectPageReq, userId uint64, departmentId uint64) (total int,
+	page int64, listBtn []*wfNewsModel.InfoBtn, err error) {
+	var list []*wfNewsModel.Entity
+	total, page, list, err = wfNewsModel.SelectListByPage(req)
+	if err != nil {
+		return
+	}
+	listBtn = make([]*wfNewsModel.InfoBtn, len(list))
+	//获取处理按钮
+	for k, v := range list {
+		var btn g.MapStrAny
+		btn, err = work_flow_service.SetBtn(v.Id, "wf_news", "title", "status", v.Status, userId, departmentId)
+		if err != nil {
+			btn = g.MapStrAny{
+				"title": "提示:" + err.Error(),
+				"type":  "alert",
+			}
+		}
+		listBtn[k] = &wfNewsModel.InfoBtn{
+			Entity:    v,
+			ActionBtn: btn,
+		}
+	}
+	return
+}

+ 64 - 0
app/service/admin/flow_service/flow.go

@@ -0,0 +1,64 @@
+// ==========================================================================
+// 生成日期:2020-08-24 17:13:46
+// 生成人:gfast
+// ==========================================================================
+package flow_service
+
+import (
+	flowModel "gfast/app/model/admin/wf_flow"
+	"gfast/app/model/admin/wf_run"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/util/gconv"
+)
+
+// 添加
+func AddSave(req *flowModel.AddReq) error {
+	return flowModel.AddSave(req)
+}
+
+// 删除
+func DeleteByIds(Ids []int) error {
+	return flowModel.DeleteByIds(Ids)
+}
+
+func SetStatus(req *flowModel.StatusReq) error {
+	return flowModel.SetStatus(req)
+}
+
+//修改
+func EditSave(editReq *flowModel.EditReq) error {
+	return flowModel.EditSave(editReq)
+}
+
+// 根据ID查询
+func GetByID(id int64) (*flowModel.Entity, error) {
+	return flowModel.GetByID(id)
+}
+
+// 分页查询
+func SelectListByPage(req *flowModel.SelectPageReq) (total int, page int64, list []*flowModel.Entity, err error) {
+	return flowModel.SelectListByPage(req)
+}
+
+func GetRunningStatus(flowList []*flowModel.Entity) (g.List, error) {
+	//获取流程Id
+	flowIds := make([]uint, len(flowList))
+	for k, v := range flowList {
+		flowIds[k] = v.Id
+	}
+	runs, err := wf_run.GetRuns("flow_id in(?) and status = ?", flowIds, 0)
+	if err != nil {
+		return nil, err
+	}
+	rdata := make(g.List, len(flowList))
+	for k, v := range flowList {
+		rdata[k] = gconv.Map(v)
+		for _, run := range runs {
+			if run.FlowId == v.Id {
+				rdata[k]["running"] = true
+				break
+			}
+		}
+	}
+	return rdata, nil
+}

+ 10 - 0
app/service/common/work_flow_service/back.go

@@ -0,0 +1,10 @@
+package work_flow_service
+
+import (
+	flowModel "gfast/app/model/admin/wf_flow"
+	"github.com/gogf/gf/errors/gerror"
+)
+
+func DoBack(req *flowModel.CheckWfSaveReq) error {
+	return gerror.New(msgNoAction)
+}

+ 107 - 0
app/service/common/work_flow_service/business_checker.go

@@ -0,0 +1,107 @@
+package work_flow_service
+
+import (
+	"gfast/app/model/admin/user"
+	"gfast/app/model/admin/wf_business_checker"
+	"gfast/app/model/admin/wf_flow_process"
+	"gfast/app/model/admin/wf_run"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/text/gstr"
+	"github.com/gogf/gf/util/gconv"
+)
+
+func SetBusinessChecker(fid int, wfType string, runId int64, process *wf_flow_process.Entity, todo string, tx *gdb.TX) error {
+	data, err := SetUserDept(fid, wfType, runId, process, todo)
+	if err != nil {
+		return err
+	}
+	return wf_business_checker.SaveInfo(data, tx)
+}
+
+func SetBusinessCheckers(fid int, wfType string, runId int64, processes []*wf_flow_process.Entity, todo string, tx *gdb.TX) error {
+	data := &wf_business_checker.SaveParams{
+		FromTable: wfType,
+		FromId:    gconv.Uint64(fid),
+	}
+	for _, process := range processes {
+		d, err := SetUserDept(fid, wfType, runId, process, todo)
+		if err != nil {
+			return err
+		}
+		if d != nil {
+			if data.UserId != "" {
+				data.UserId = data.UserId + "," + d.UserId
+			} else {
+				data.UserId = d.UserId
+			}
+			if data.DepartmentId != "" {
+				data.DepartmentId = data.DepartmentId + "," + d.DepartmentId
+			} else {
+				data.DepartmentId = d.DepartmentId
+			}
+		}
+	}
+	return wf_business_checker.SaveInfo(data, tx)
+}
+
+func SetUserDept(fid int, wfType string, runId int64, process *wf_flow_process.Entity, todo string) (*wf_business_checker.SaveParams, error) {
+	processData := &ProcessData{
+		Entity: process,
+	}
+	if process.AutoPerson == 6 && process.ProcessType == "is_one" {
+		//事务处理人员
+		wf, err := wf_run.GetRunById(gconv.Uint(runId))
+		if err != nil {
+			return nil, err
+		}
+		userIdBil, err := GetBilValue(wf.FromTable, wf.FromId, process.WorkText)
+		if err != nil {
+			return nil, err
+		}
+		userInfo, err := user.GetUserById(gconv.Uint64(userIdBil))
+		if err != nil {
+			return nil, err
+		}
+		processData.UserInfo = userInfo
+		processData.Todo = g.MapStrAny{"text": userInfo.UserNickname}
+	}
+	sponsorIds := ""   //用户id
+	sponsorDepts := "" //部门id
+	//非自由选择
+	if todo == "" {
+		if process.AutoPerson == 3 {
+			//办理人员
+			sponsorIds = process.RangeUserIds
+		} else if process.AutoPerson == 4 {
+			//办理人员
+			sponsorIds = process.AutoSponsorIds
+		} else if process.AutoPerson == 5 {
+			//办理部门
+			sponsorDepts = process.AutoRoleIds
+		} else if process.AutoPerson == 6 {
+			//事务接收
+			sponsorIds = gconv.String(processData.UserInfo.Id)
+		}
+	} else {
+		todoArr := gstr.Split(todo, "*%*")
+		sponsorIds = todoArr[0]
+	}
+	data := &wf_business_checker.SaveParams{
+		FromTable:    wfType,
+		FromId:       gconv.Uint64(fid),
+		UserId:       sponsorIds,
+		DepartmentId: sponsorDepts,
+	}
+	return data, nil
+}
+
+func DeleteBusinessChecker(fid uint, wfType string, tx *gdb.TX) error {
+	_, err := wf_business_checker.Model.TX(tx).Where(g.Map{"from_table": wfType, "from_id": fid}).Delete()
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("删除业务审批人员/部门信息失败")
+	}
+	return nil
+}

+ 474 - 0
app/service/common/work_flow_service/flow.go

@@ -0,0 +1,474 @@
+package work_flow_service
+
+import (
+	"fmt"
+	"gfast/app/model/admin/user"
+	flowModel "gfast/app/model/admin/wf_flow"
+	"gfast/app/model/admin/wf_flow_process"
+	"gfast/app/model/admin/wf_run"
+	"gfast/app/model/admin/wf_run_process"
+	"gfast/app/model/admin/wf_run_sign"
+	"gfast/library/utils"
+	"github.com/gogf/gf/container/garray"
+	"github.com/gogf/gf/encoding/gjson"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/text/gstr"
+	"github.com/gogf/gf/util/gconv"
+)
+
+//工作流数据
+type WorkFlow struct {
+	WfModel     int                    `json:"wf_model"` //模式
+	SingSt      int                    `json:"sing_st"`
+	FlowId      uint                   `json:"flow_id"`
+	Status      *wf_run_process.Entity `json:"status"`
+	FlowProcess uint                   `json:"flow_process"`
+	RunId       uint                   `json:"run_id"`
+	RunProcess  uint                   `json:"run_process"`
+	FlowName    string                 `json:"flow_name"`
+	Process     *ProcessData           `json:"process"`
+	NextProcess []*ProcessData         `json:"next_process"`
+	Preprocess  map[int]string         `json:"preprocess"`
+	SingUser    []uint64               `json:"sing_user"`
+	SignInfo    *wf_run_sign.Entity    `json:"sign_info"`
+	BillCheck   string                 `json:"bill_check"`
+	BillTime    string                 `json:"bill_time"`
+}
+
+var msgNoAction = "该功能仅对捐赠用户开放,捐赠299即可获得完整工作流功能及配套视频。"
+
+//获取流程信息
+func GetFlowInfoById(flowId int64) (entity *flowModel.Entity, err error) {
+	return flowModel.GetByID(flowId)
+}
+
+//获取流程名称
+func GetFlowName(flowId int64) (name string, err error) {
+	var info *flowModel.Entity
+	info, err = GetFlowInfoById(flowId)
+	if err != nil || info == nil {
+		return
+	}
+	name = info.FlowName
+	return
+}
+
+//检查流程逻辑
+func CheckFlow(flowId int64) error {
+	_, processList, err := ProcessAll(flowId)
+	if err != nil {
+		return err
+	}
+	if processList == nil {
+		return gerror.New("没有找到步骤信息!")
+	}
+	oneStep := 0
+	for _, process := range processList {
+		if process.Process.ProcessType == "is_one" {
+			oneStep++
+		}
+	}
+	if oneStep == 0 {
+		return gerror.New("没有设置第一步骤,请修改!")
+	}
+	if oneStep > 1 {
+		return gerror.New(fmt.Sprintf("有 %d 个起始步骤,请修改!", oneStep))
+	}
+	return nil
+}
+
+//获取发起流程按钮及状态
+func SetBtn(wfFid uint, wfType, wfTitle, wfStatusField string, status int, userId uint64, departmentId uint64) (g.MapStrAny, error) {
+	switch status {
+	case 0:
+		return g.MapStrAny{
+			"title":           "发起审批",
+			"api":             "wfStart",
+			"wf_type":         wfType,
+			"wf_status_field": wfStatusField,
+			"wf_title":        wfTitle,
+			"wf_fid":          wfFid,
+			"type":            "link",
+		}, nil
+	case 1:
+		st := 0
+		userName := ""
+		flowInfo, err := WorkFlowInfo(wfFid, wfType, userId, departmentId)
+		if err != nil {
+			return nil, err
+		}
+		if flowInfo != nil && flowInfo.NextProcess != nil {
+			if flowInfo.Status == nil {
+				return g.MapStrAny{
+					"title": "提示:当前流程故障,请联系管理员重置流程!",
+					"type":  "alert",
+				}, nil
+			}
+			if flowInfo.SingSt == 0 {
+				user := garray.NewStrArrayFrom(gstr.Split(flowInfo.Status.SponsorIds, ","))
+				userName = flowInfo.Status.SponsorText
+				if flowInfo.Status.AutoPerson == 3 || flowInfo.Status.AutoPerson == 4 || flowInfo.Status.AutoPerson == 6 {
+					if user.Contains(gconv.String(userId)) {
+						st = 1
+					}
+				} else if flowInfo.Status.AutoPerson == 5 {
+					if user.Contains(gconv.String(departmentId)) {
+						st = 1
+					}
+				}
+			} else {
+				userInfo, err := user.GetUserById(gconv.Uint64(flowInfo.SignInfo.Uid))
+				if err != nil {
+					return nil, err
+				}
+				userName = userInfo.UserNickname
+				if gconv.Uint64(flowInfo.SignInfo.Uid) == userId {
+					st = 1
+				}
+			}
+		} else {
+			return nil, nil
+		}
+		if st == 1 {
+			return g.MapStrAny{
+				"title":           "审批(" + userName + ")",
+				"api":             "wfCheck",
+				"wf_type":         wfType,
+				"wf_status_field": wfStatusField,
+				"wf_title":        wfTitle,
+				"wf_fid":          wfFid,
+				"type":            "link",
+			}, nil
+		} else {
+			return g.MapStrAny{
+				"title": "无审批权限(" + userName + ")",
+				"type":  "text",
+			}, nil
+		}
+	case 100:
+		return g.MapStrAny{
+			"title":           "代审",
+			"api":             "wfCheck",
+			"wf_type":         wfType,
+			"wf_status_field": wfStatusField,
+			"wf_title":        wfTitle,
+			"wf_fid":          wfFid,
+			"type":            "link",
+			"sup":             "1",
+		}, nil
+	}
+	return nil, nil
+}
+
+//流程状态查询
+func WorkFlowInfo(wfFid uint, wfType string, userId uint64, departmentId uint64) (*WorkFlow, error) {
+	workFlow := &WorkFlow{}
+	if wfFid == 0 || wfType == "" {
+		return nil, gerror.New("单据编号,单据表不可为空!")
+	}
+	//根据表信息,判断当前流程是否还在运行
+	runInfo, err := wf_run.GetRunning(&wf_run.RunSearch{
+		FromId:    wfFid,
+		FromTable: wfType,
+		IsDel:     0,
+		Status:    0,
+	})
+	if err != nil {
+		return nil, err
+	}
+	if runInfo != nil {
+		info := new(wf_run_process.Entity)
+		//获取当前运行的信息
+		where := &wf_run_process.SearchRunProcess{
+			RunId:          runInfo.Id,
+			RunFlow:        runInfo.FlowId,
+			RunFlowProcess: runInfo.RunFlowProcess,
+			Status:         "0",
+		}
+		processList, err := wf_run_process.GetProcessList(where)
+		if err != nil {
+			return nil, err
+		}
+		if processList == nil || len(processList) == 0 {
+			process, err := wf_run_process.GetProcess(where)
+			if err != nil {
+				return nil, err
+			}
+			processList = append(processList, process)
+		}
+		//如果有两个以上的运行步骤,则认定为同步模式
+		if len(processList) < 2 {
+			workFlow.WfModel = 0
+			info = processList[0]
+		} else {
+			workFlow.WfModel = 2 //同步模式
+			for _, process := range processList {
+				uIds := garray.NewStrArrayFrom(gstr.Split(process.SponsorIds, ","))
+				if process.AutoPerson == 4 || process.AutoPerson == 3 {
+					if uIds.Contains(gconv.String(userId)) {
+						info = process
+						break
+					}
+				} else {
+					if uIds.Contains(gconv.String(departmentId)) {
+						info = process
+						break
+					}
+				}
+			}
+			if info == nil || info.Id == 0 {
+				return nil, gerror.New("无权限")
+			}
+		}
+		//设置运行信息数据
+		workFlow.SingSt = 0
+		workFlow.FlowId = runInfo.FlowId
+		if info == nil {
+			info = new(wf_run_process.Entity)
+		}
+		workFlow.Status = info
+		workFlow.FlowProcess = info.RunFlowProcess
+		workFlow.RunId = runInfo.Id
+		workFlow.RunProcess = info.Id
+		//获取流程名称
+		flowName, err := GetFlowName(gconv.Int64(runInfo.FlowId))
+		if err != nil {
+			return nil, err
+		}
+		workFlow.FlowName = flowName
+		//获取流程步骤信息
+		var processData *ProcessData
+		processData, err = GetProcessInfo(info.RunFlowProcess, runInfo.Id)
+		if err != nil {
+			return nil, err
+		}
+		workFlow.Process = processData
+		//获取下一个步骤信息
+		var nextProcess []*ProcessData
+		nextProcess, err = GetNexProcessInfo(wfType, wfFid, info.RunFlowProcess, runInfo.Id)
+		if err != nil {
+			return nil, err
+		}
+		workFlow.NextProcess = nextProcess
+		//获取前几个步骤信息,用于步骤回退
+		var preProcess map[int]string
+		preProcess, err = GetPreProcessInfo(info.Id)
+		workFlow.Preprocess = preProcess
+		//获取所有会签人员 todo...
+		if runInfo.IsSing == 1 {
+			info, err = wf_run_process.GetProcess(&wf_run_process.SearchRunProcess{
+				RunId:          runInfo.Id,
+				RunFlow:        runInfo.FlowId,
+				RunFlowProcess: runInfo.RunFlowProcess,
+				Status:         "0",
+			})
+			if err != nil {
+				return nil, err
+			}
+			workFlow.SingSt = 1
+			workFlow.FlowProcess = info.RunFlowProcess
+			process, err := GetProcessInfo(info.RunFlowProcess, runInfo.Id)
+			if err != nil {
+				return nil, err
+			}
+			workFlow.Status = &wf_run_process.Entity{
+				WfMode:   gconv.Int(process.WfMode),
+				WfAction: process.WfAction,
+			}
+			nextProcess, err = GetNexProcessInfo(wfType, wfFid, info.RunFlowProcess, runInfo.Id)
+			if err != nil {
+				return nil, err
+			}
+			workFlow.NextProcess = nextProcess
+			workFlow.Process = process
+			workFlow.RunProcess = info.Id
+			workFlow.SignInfo, err = GetSignInfoById(gconv.Uint(runInfo.SingId))
+		} else {
+			workFlow.BillCheck = ""
+			workFlow.BillTime = ""
+		}
+	} else {
+		workFlow.BillCheck = ""
+		workFlow.BillTime = ""
+	}
+	return workFlow, nil
+}
+
+//获取对应类型的工作流
+func GetWorkFlowByType(wfType string) (flows []*flowModel.Entity, err error) {
+	return flowModel.GetWorkFlowByType(wfType)
+}
+
+func StartWorkFlow(req *flowModel.SaveWfFlowReq) error {
+	//判断流程是否存在--获取所选工作流信息
+	flowInfo, err := flowModel.GetByID(req.WfId)
+	if err != nil {
+		return err
+	}
+	if flowInfo == nil {
+		return gerror.New("未找到工作流信息")
+	}
+	//判断单据(业务表信息)是否存在
+	bill, err := GetBill(req.WfFid, req.WfType)
+	if err != nil {
+		return err
+	}
+	if bill == nil {
+		return gerror.New("单据不存在")
+	}
+	//根据流程获取流程第一个步骤
+	wfProcesses, err := wf_flow_process.GetProcessesByMap(g.Map{"flow_id": req.WfId})
+	if err != nil {
+		return err
+	}
+	if wfProcesses == nil {
+		gerror.New("没有流程信息。")
+	}
+	firstProcess := getFirstProcess(wfProcesses)
+	if firstProcess == nil {
+		return gerror.New("流程设计出错,未找到第一步流程,请联系管理员!")
+	}
+	tx, err := g.DB().Begin()
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("开启事务处理失败")
+	}
+	//保存流程数据
+	runId, err := AddWfRun(req.WfId, firstProcess.Id, req.WfFid, req.WfType, req.WfTitle, req.WfStatusField, req.UserId, tx)
+	if err != nil {
+		tx.Rollback()
+		return gerror.New("流程设计出错,未找到第一步流程,请联系管理员!")
+	}
+	//添加流程运行步骤
+	err = AddWorkflowProcess(req.WfId, firstProcess, runId, req.UserId, "", tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	//设置第一步审批人员/部门信息
+	err = SetBusinessChecker(req.WfFid, req.WfType, runId, firstProcess, "", tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	//添加流程日志
+	err = AddWorkflowCache(runId, flowInfo, firstProcess, req.WfFid, tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	//更新单据数据
+	err = UpdateBill(req.WfFid, req.WfType, req.WfStatusField, 1, tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	err = AddRunLog(runId, req, "Send", tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	tx.Commit()
+	return nil
+}
+
+//获取审批日志
+func FlowLog(logType string, wfFid uint, wfType string) ([]*RunLogInfo, error) {
+	if logType == "logs" {
+		infos, err := RunLog(wfFid, wfType)
+		if err != nil {
+			return nil, err
+		}
+		return infos, nil
+	}
+	return nil, gerror.New("参数出错!")
+}
+
+func WorkCheckAction(req *flowModel.CheckWfSaveReq) error {
+	if req.Art != "" {
+		art := gjson.New(req.Art)
+		url := art.GetString("0.url")
+		if url != "" {
+			url, err := utils.GetFilesPath(url)
+			if err != nil {
+				return err
+			}
+			art.Set("0.url", url)
+		}
+		req.Art, _ = art.ToJsonString()
+	}
+
+	if req.SingSt == 0 {
+		runCheck, err := RunCheck(req.RunProcess)
+		if err != nil {
+			return err
+		}
+		if runCheck == 2 {
+			return gerror.New("该业务已办理,请勿重复提交!")
+		}
+		if req.SubmitToSave == "ok" {
+			//提交处理
+			err = DoTask(req)
+			if err != nil {
+				return err
+			}
+		} else if req.SubmitToSave == "back" {
+			//退回处理
+			err = DoBack(req)
+			if err != nil {
+				return err
+			}
+		} else if req.SubmitToSave == "sing" {
+			//会签处理
+			err = DoSing(req)
+			if err != nil {
+				return err
+			}
+		} else {
+			return gerror.New("参数出错")
+		}
+	} else {
+		err := DoSingEnt(req)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+type Running struct {
+	*wf_run.Entity
+	FlowName string `json:"flow_name"`
+	User     string `json:"user"`
+}
+
+func GetRunningFlow() ([]*Running, error) {
+	run, err := wf_run.GetRuns("status", 0)
+	if err != nil {
+		return nil, err
+	}
+	running := make([]*Running, len(run))
+	for k, v := range run {
+		rn := &Running{Entity: v}
+		flow, _ := flowModel.GetByID(gconv.Int64(v.FlowId))
+		if flow != nil {
+			rn.FlowName = flow.FlowName
+		}
+		process, err := wf_run_process.GetProcessByMap(g.Map{
+			"run_id":           v.Id,
+			"run_flow_process": v.RunFlowProcess,
+		})
+		if err != nil {
+			return nil, err
+		}
+		sponsorText := ""
+		for _, s := range process {
+			sponsorText += s.SponsorText + ","
+		}
+		sponsorText = gstr.TrimRightStr(sponsorText, ",")
+		rn.User = sponsorText
+		running[k] = rn
+	}
+	return running, nil
+}

+ 417 - 0
app/service/common/work_flow_service/process.go

@@ -0,0 +1,417 @@
+package work_flow_service
+
+import (
+	"encoding/json"
+	"gfast/app/model/admin/user"
+	"gfast/app/model/admin/wf_flow_process"
+	"gfast/app/model/admin/wf_run"
+	"gfast/app/model/admin/wf_run_process"
+	"github.com/gogf/gf/container/garray"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/encoding/gjson"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/text/gstr"
+	"github.com/gogf/gf/util/gconv"
+)
+
+//流程运行数据
+type ProcessData struct {
+	*wf_flow_process.Entity
+	Todo     g.MapStrAny  `json:"todo"`
+	UserInfo *user.Entity `json:"user_info"`
+}
+
+//获取流程下所有步骤信息
+func ProcessAll(flowId int64) (total int64, list []*wf_flow_process.ProcessData, err error) {
+	return wf_flow_process.ProcessAll(flowId)
+}
+
+//获取流程转出信息
+func GetProcessStep(processInfo *wf_flow_process.ProcessData, processList []*wf_flow_process.ProcessData) (
+	list []*wf_flow_process.ProcessStepData, err error) {
+	if processInfo == nil || processInfo.ProcessTo == "" || processList == nil {
+		return
+	}
+	processArr := garray.NewStrArrayFrom(gstr.Split(processInfo.ProcessTo, ","))
+	for _, process := range processList {
+		if processArr.Contains(gconv.String(process.Id)) {
+			var con []string
+			con, err = getProcessCondition(processInfo.Process.OutCondition, process.Id)
+			list = append(list, &wf_flow_process.ProcessStepData{
+				PrevId:      processInfo.Id,
+				Id:          process.Id,
+				ProcessName: process.ProcessName,
+				ProcessType: process.Process.ProcessType,
+				Condition:   con,
+			})
+		}
+	}
+	return
+}
+
+//获取转出条件
+func getProcessCondition(condition string, id interface{}) ([]string, error) {
+	if condition == "" {
+		return nil, nil
+	}
+	j, err := gjson.DecodeToJson(condition)
+	if err != nil {
+		g.Log().Error(err)
+		return nil, gerror.New("转换转出条件失败")
+	}
+	return j.GetStrings(gconv.String(id) + ".condition"), nil
+}
+
+//添加流程步骤
+func ProcessAdd(flowId int64) error {
+	return wf_flow_process.ProcessAdd(flowId)
+}
+
+//保存流程设计
+func SaveProcess(req *wf_flow_process.SaveProcessReqBatch) error {
+	return wf_flow_process.SaveProcess(req)
+}
+
+//获取流程管理表字段信息
+func GetFlowTableFields(tableName string) (fields []*wf_flow_process.FlowTableField, err error) {
+	//表前缀
+	dbPrefix := g.DB().GetPrefix()
+	//数据库
+	database := g.Cfg().GetString("database.name")
+	return wf_flow_process.GetFlowTableFields(dbPrefix, database, tableName)
+}
+
+//pid 步骤ID wf_flow_process 主键
+//runId 运行步骤ID wf_run 主键
+func GetProcessInfo(pid, runId uint) (data *ProcessData, err error) {
+	var flowProcessInfo *wf_flow_process.Entity
+	flowProcessInfo, err = wf_flow_process.GetProcessInfoById(pid)
+	if err != nil {
+		return
+	}
+	if flowProcessInfo == nil {
+		err = gerror.New("不存在流程信息")
+		return
+	}
+	data = &ProcessData{
+		Entity: flowProcessInfo,
+	}
+	if flowProcessInfo.AutoPerson == 3 { //自由选择
+		data.Todo = g.MapStrAny{"ids": gstr.Split(flowProcessInfo.RangeUserIds, ","), "text": gstr.Split(flowProcessInfo.RangeUserText, ",")}
+	} else if flowProcessInfo.AutoPerson == 4 { //办理人员
+		data.Todo = g.MapStrAny{"text": flowProcessInfo.AutoSponsorText}
+	} else if flowProcessInfo.AutoPerson == 5 { //办理部门
+		data.Todo = g.MapStrAny{"text": flowProcessInfo.AutoRoleText}
+	} else if flowProcessInfo.AutoPerson == 6 { //事务接受
+		var runInfo *wf_run.Entity
+		runInfo, err = wf_run.GetRunById(runId)
+		if err != nil {
+			return
+		}
+		if runInfo == nil {
+			err = gerror.New("不存在运行状态信息")
+			return
+		}
+		var userId interface{}
+		userId, err = GetBilValue(runInfo.FromTable, runInfo.FromId, flowProcessInfo.WorkText)
+		if err != nil {
+			return
+		}
+		if userId == nil {
+			err = gerror.New("获取事务接受用户ID失败,请检测流程设计是否设置的是用户ID字段")
+			return
+		}
+		var userInfo *user.Entity
+		userInfo, err = user.GetUserById(gconv.Uint64(userId))
+		if err != nil {
+			return
+		}
+		data.Todo = g.MapStrAny{"text": userInfo.UserNickname}
+		data.UserInfo = userInfo
+	}
+	return
+}
+
+//同步步骤信息
+func GetProcessInfos(ids []uint, runId uint) (datas []*ProcessData, err error) {
+	var list []*wf_flow_process.Entity
+	list, err = wf_flow_process.GetProcessInfosByIds(ids)
+	if err != nil {
+		return
+	}
+	datas = make([]*ProcessData, len(list))
+	for k, v := range list {
+		var todo g.MapStrAny
+		var userInfo *user.Entity
+		if v.AutoPerson == 3 { //自由选择 办理人员
+			todo = g.MapStrAny{
+				"ids":  gstr.Split(v.AutoSponsorIds, ","),
+				"text": gstr.Split(v.AutoSponsorText, ","),
+			}
+		} else if v.AutoPerson == 4 { //办理人员
+			todo = g.MapStrAny{"text": v.AutoSponsorText}
+		} else if v.AutoPerson == 5 { //办理部门
+			todo = g.MapStrAny{"text": v.AutoRoleText}
+		} else if v.AutoPerson == 6 { //事务接受
+			var runInfo *wf_run.Entity
+			runInfo, err = wf_run.GetRunById(runId)
+			if err != nil {
+				return
+			}
+			if runInfo == nil {
+				err = gerror.New("不存在运行状态信息")
+				return
+			}
+			var userId interface{}
+			userId, err = GetBilValue(runInfo.FromTable, runInfo.FromId, v.WorkText)
+			if err != nil {
+				return
+			}
+			if userId == nil {
+				err = gerror.New("获取事务接受用户ID失败,请检测流程设计是否设置的是用户ID字段")
+				return
+			}
+			userInfo, err = user.GetUserById(gconv.Uint64(userId))
+			if err != nil {
+				return
+			}
+			todo = g.MapStrAny{"text": userInfo.UserNickname}
+		}
+		datas[k] = &ProcessData{
+			Entity:   v,
+			Todo:     todo,
+			UserInfo: userInfo,
+		}
+	}
+	return
+}
+
+//获取事务接受信息
+func GetBilValue(fromTable string, fromId int, workText string) (value interface{}, err error) {
+	var rec gdb.Record
+	rec, err = GetBill(fromId, fromTable)
+	if err != nil {
+		return
+	}
+	value = rec[workText]
+	return
+}
+
+//获取工作流业务表信息
+func GetBill(fromId int, fromTable string) (rec gdb.Record, err error) {
+	rec, err = g.DB().Table(fromTable).FindOne(fromId)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取工作里业务数据失败")
+		return
+	}
+	return
+}
+
+//更新业务表信息
+func UpdateBill(wfFid int, wfType string, statusField string, status int, tx *gdb.TX) error {
+	_, err := tx.Table(wfType).WherePri(wfFid).Update(g.Map{
+		statusField: status,
+	})
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("更新单据信息失败")
+	}
+	return nil
+}
+
+//删除流程节点
+func ProcessDelete(req *wf_flow_process.DeleteProcessReq) error {
+	return wf_flow_process.ProcessDelete(req)
+}
+
+//清空节点
+func ProcessDeleteAll(flowId int) error {
+	_, err := wf_flow_process.Model.Where("flow_id", flowId).Delete()
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("删除失败")
+	}
+	return nil
+}
+
+//获取下个审批流信息
+//wfType 单据表
+//wfFid 单据id
+//pid 流程id
+//runId 运行id
+func GetNexProcessInfo(wfType string, wfFid uint, pid uint, runId uint) ([]*ProcessData, error) {
+	if pid == 0 {
+		return nil, nil
+	}
+	nex, err := wf_flow_process.GetProcessInfoById(pid)
+	if err != nil {
+		return nil, err
+	}
+	var process *ProcessData
+	var processes []*ProcessData
+	//先判断下上一个流程是什么模式
+	if nex.ProcessTo != "" {
+		//下一个流程的下一步
+		nexPidStr := gstr.Split(nex.ProcessTo, ",")
+		nexPid := make([]uint, len(nexPidStr))
+		for k, v := range nexPidStr {
+			nexPid[k] = gconv.Uint(v)
+		}
+		//下一个流程转出条件
+		var outCondition g.MapStrAny
+		err = json.Unmarshal([]byte(nex.OutCondition), &outCondition)
+		if err != nil {
+			outCondition = g.MapStrAny{}
+		}
+		switch nex.WfMode {
+		case 0: //单一模式
+			process, err = GetProcessInfo(gconv.Uint(nex.ProcessTo), runId)
+			if err != nil {
+				return nil, err
+			}
+			processes = append(processes, process)
+		case 1: //多个审批流
+			var nextProcessId uint
+			for key, val := range outCondition {
+				mapVal := gconv.Map(val)
+				condition := gstr.Join(gconv.SliceStr(mapVal["condition"]), " ")
+				//根据条件寻找匹配符合的工作流id
+				if gstr.ContainsI(condition, "include") {
+					condition = gstr.ReplaceI(condition, "include", " REGEXP ")
+				}
+				info, err := g.DB().Table(wfType).Where(condition).WherePri(wfFid).FindOne()
+				if err != nil {
+					g.Log().Error(err)
+					return nil, gerror.New("获取转出条件对应的审批信息失败")
+				}
+				if info != nil {
+					nextProcessId = gconv.Uint(key)
+					break
+				}
+			}
+			if nextProcessId == 0 { //没有权限
+				return nil, nil
+			}
+			process, err = GetProcessInfo(nextProcessId, runId)
+			if err != nil {
+				return nil, err
+			}
+			processes = append(processes, process)
+		case 2: //同步模式
+			processes, err = GetProcessInfos(nexPid, runId)
+			if err != nil {
+				return nil, err
+			}
+		}
+	} else {
+		process = &ProcessData{
+			Entity: &wf_flow_process.Entity{
+				Id:          0,
+				AutoPerson:  0,
+				ProcessName: "END",
+			},
+			Todo: g.MapStrAny{"text": "结束"},
+		}
+		processes = append(processes, process)
+	}
+	return processes, nil
+}
+
+func GetPreProcessInfo(runId uint) (map[int]string, error) {
+	var pre []*wf_flow_process.Entity
+	preRun, err := wf_run_process.GetProcessById(runId)
+	if err != nil {
+		return nil, err
+	}
+	//获取本流程中小于本次ID的步骤信息
+	var preP []*wf_run_process.Entity
+	preP, err = wf_run_process.GetProcessLtId(preRun)
+	if err != nil {
+		return nil, err
+	}
+	//遍历获取小于本次ID中的相关步骤
+	for _, v := range preP {
+		wfp, err := wf_flow_process.GetProcessInfoById(v.RunFlowProcess)
+		if err != nil {
+			return nil, err
+		}
+		pre = append(pre, wfp)
+	}
+	preMap := make(map[int]string, 5)
+	preMap[0] = "退回制单人修改"
+	if len(pre) > 0 {
+		for _, v := range pre {
+			todo := ""
+			if v.AutoPerson == 4 { //办理人员
+				todo = v.AutoSponsorText
+			} else if v.AutoPerson == 5 { //办理部门
+				todo = v.AutoRoleText
+			}
+			preMap[v.Id] = v.ProcessName + "(" + todo + ")"
+		}
+	}
+	return preMap, nil
+}
+
+//保存流程节点属性
+func SaveProcessAttr(req *wf_flow_process.SaveAttrReq) error {
+	if req == nil {
+		return gerror.New("参数错误")
+	}
+	processCondition := gstr.Split(req.ProcessCondition, ",")
+	outCondition := make(g.MapStrAny, 10)
+	if len(processCondition) > 1 && req.WfMode == "1" {
+		return gerror.New(msgNoAction)
+	}
+	ocbt, _ := json.Marshal(outCondition)
+	entity, err := wf_flow_process.FindOne(req.ProcessId)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("获取流程信息失败")
+	}
+	style := gjson.New(nil)
+	style.Set("width", req.StyleWidth)
+	style.Set("height", req.StyleHeight)
+	style.Set("color", "#0e76a8")
+
+	entity.ProcessName = req.ProcessName
+	entity.ProcessType = req.ProcessType
+	entity.AutoPerson = gconv.Uint(req.AutoPerson)
+	entity.WfMode = gconv.Uint(req.WfMode)
+	entity.WfAction = req.WfAction
+	entity.AutoSponsorIds = req.AutoSponsorIds
+	entity.AutoSponsorText = req.AutoSponsorText
+	entity.AutoRoleIds = req.AutoRoleIds
+	entity.AutoRoleText = req.AutoRoleText
+	entity.RangeUserIds = req.RangeUserIds
+	entity.RangeUserText = req.RangeUserText
+	entity.WorkText = req.WorkText
+	entity.WorkIds = req.WorkIds
+	entity.WorkMsg = req.WorkMsg
+	entity.WorkSql = req.WorkSql
+	entity.IsSing = gconv.Uint(req.IsSing)
+	entity.IsBack = gconv.Uint(req.IsBack)
+	entity.OutCondition = string(ocbt)
+	entity.Style = style.MustToJsonString()
+	_, err = wf_flow_process.Model.Save(entity)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("保存失败")
+	}
+	return nil
+}
+
+//获取流程步骤第一步
+func getFirstProcess(processes []*wf_flow_process.Entity) *wf_flow_process.Entity {
+	//找到 流程第一步
+	var firstProcess *wf_flow_process.Entity
+	for _, process := range processes {
+		if process.ProcessType == "is_one" {
+			firstProcess = process
+			break
+		}
+	}
+	return firstProcess
+}

+ 140 - 0
app/service/common/work_flow_service/run.go

@@ -0,0 +1,140 @@
+package work_flow_service
+
+import (
+	"fmt"
+	flowModel "gfast/app/model/admin/wf_flow"
+	"gfast/app/model/admin/wf_run"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/util/gconv"
+)
+
+func AddWfRun(wfId int64, processId int, wfFid int, wfType, wfTitle, wfStatusField string, userId uint64, tx *gdb.TX) (runId int64, err error) {
+	//删除旧的流程数据
+	_, err = wf_run.Model.TX(tx).Delete(g.Map{
+		"from_table": wfType,
+		"from_id":    wfFid,
+	})
+	if err != nil {
+		g.Log().Error(err)
+		return 0, gerror.New("删除旧流程信息失败")
+	}
+	data := &wf_run.RunAddData{
+		Pid:            0,
+		Uid:            gconv.Uint(userId),
+		FlowId:         gconv.Uint(wfId),
+		FromTable:      wfType,
+		FromId:         wfFid,
+		FromTitle:      wfTitle,
+		FromStatus:     wfStatusField,
+		RunName:        gconv.String(wfFid),
+		RunFlowId:      gconv.Uint(wfId),
+		RunFlowProcess: gconv.String(processId),
+		Dateline:       gconv.Uint(gtime.Timestamp()),
+	}
+	return wf_run.Add(data, tx)
+}
+
+func Up(runId uint, flowProcess string, tx *gdb.TX) (err error) {
+	err = wf_run.UpdateRun(runId, g.Map{
+		"run_flow_process": flowProcess,
+	}, tx)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("更新流程运行信息失败")
+	}
+	return
+}
+
+func UpFlowPress(id uint, runProcess string, tx *gdb.TX) error {
+	return Up(id, runProcess, tx)
+}
+
+func EndFlow(runId uint, tx *gdb.TX) error {
+	err := wf_run.UpdateRun(runId, g.Map{
+		"status":  1,
+		"endtime": gtime.Timestamp(),
+	}, tx)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("结束流程主状态失败")
+	}
+	return err
+}
+
+func UpFlow(runId uint, sid int64, tx *gdb.TX) error {
+	err := wf_run.UpdateRun(runId, g.Map{
+		"is_sing": 1,
+		"sing_id": sid,
+		"endtime": gtime.Timestamp(),
+	}, tx)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("设置会签状态失败")
+	}
+	return err
+}
+
+func UpRun(id uint, tx *gdb.TX) error {
+	err := wf_run.UpdateRun(id, g.Map{
+		"is_sing": 0,
+	}, tx)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("更新流程状态失败")
+	}
+	return err
+}
+
+//终止流程操作
+func DoSupEnd(runId uint, userId uint64, wfStatusField string) error {
+	//获取流程运行信息
+	runInfo, err := wf_run.GetRunById(runId)
+	if err != nil {
+		return err
+	}
+	//日志信息
+	fm := &flowModel.SaveWfFlowReq{
+		WfType:     runInfo.FromTable,
+		WfFid:      runInfo.FromId,
+		WfId:       gconv.Int64(runInfo.FlowId),
+		CheckCon:   fmt.Sprintf("编号:%d的管理员终止了本流程!", userId),
+		RunProcess: gconv.Uint(runInfo.RunFlowProcess),
+		RunId:      runId,
+		Art:        "",
+		UserId:     userId,
+	}
+	tx, err := g.DB().Begin()
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("开启事务失败")
+	}
+	//结束流程
+	err = EndFlow(runId, tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	err = EndProcess(gconv.Uint(runInfo.RunFlowProcess), fm.CheckCon, 2, tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+
+	//日志记录
+	err = AddRunLog(gconv.Int64(runId), fm, "SupEnd", tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	//更新单据状态
+	err = UpdateBill(fm.WfFid, fm.WfType, wfStatusField, 2, tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	tx.Commit()
+	return nil
+}

+ 23 - 0
app/service/common/work_flow_service/run_cache.go

@@ -0,0 +1,23 @@
+package work_flow_service
+
+import (
+	flowModel "gfast/app/model/admin/wf_flow"
+	"gfast/app/model/admin/wf_flow_process"
+	"gfast/app/model/admin/wf_run_cache"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/util/gconv"
+)
+
+func AddWorkflowCache(runId int64, flowInfo *flowModel.Entity, process *wf_flow_process.Entity, wfFid int, tx *gdb.TX) error {
+	data := &wf_run_cache.AddData{
+		RunId:          gconv.Uint(runId),
+		FormId:         gconv.Uint(wfFid),
+		FlowId:         flowInfo.Id,
+		RunForm:        "",
+		RunFlow:        gconv.String(flowInfo),
+		RunFlowProcess: gconv.String(process),
+		Dateline:       gconv.Uint(gtime.Timestamp()),
+	}
+	return wf_run_cache.Add(data, tx)
+}

+ 208 - 0
app/service/common/work_flow_service/run_log.go

@@ -0,0 +1,208 @@
+package work_flow_service
+
+import (
+	"gfast/app/model/admin/user"
+	flowModel "gfast/app/model/admin/wf_flow"
+	"gfast/app/model/admin/wf_flow_process"
+	"gfast/app/model/admin/wf_run_log"
+	"gfast/app/model/admin/wf_run_process"
+	"gfast/app/model/admin/wf_workinfo"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/text/gstr"
+	"github.com/gogf/gf/util/gconv"
+)
+
+//工作流审批日志记录
+func AddRunLog(runId int64, req *flowModel.SaveWfFlowReq, btn string, tx *gdb.TX) error {
+	workReturn := ""
+	if btn != "Send" && btn != "SupEnd" {
+		//workReturn =
+		//在日志记录前加载节点钩子
+		var err error
+		workReturn, err = WorkApi(req, tx)
+		if err != nil {
+			return err
+		}
+	}
+	err := wf_run_log.Add(&wf_run_log.AddData{
+		Uid:       gconv.Uint(req.UserId),
+		FromId:    req.WfFid,
+		FromTable: req.WfType,
+		RunId:     gconv.Uint(runId),
+		Content:   req.CheckCon,
+		WorkInfo:  workReturn,
+		Art:       req.Art,
+		Btn:       btn,
+		Dateline:  gconv.Uint(gtime.Timestamp()),
+	}, tx)
+	return err
+}
+
+func WorkApi(req *flowModel.SaveWfFlowReq, tx *gdb.TX) (string, error) {
+	sqlReturn := "null"
+	msgReturn := "null"
+	//去除当前运行的步骤ID
+	runProcess, err := wf_run_process.GetProcessById(req.RunProcess)
+	if err != nil {
+		return "", err
+	}
+	runFlowProcess := runProcess.RunFlowProcess
+	//获取当前步骤版本ID,对应的所有信息
+	flowProcessInfo, err := wf_flow_process.GetProcessInfoById(runFlowProcess)
+	if err != nil {
+		return "", err
+	}
+	if flowProcessInfo == nil {
+		return "flow_process_info err!", nil
+	}
+	if flowProcessInfo.WorkSql != "" {
+		workInfoId, err := workSql(req, flowProcessInfo, tx)
+		if err != nil {
+			return "", err
+		}
+		sqlReturn = gconv.String(workInfoId)
+		if flowProcessInfo.WorkMsg != "" {
+			workInfoId, err := workMsg(req, flowProcessInfo, tx)
+			if err != nil {
+				return "", err
+			}
+			msgReturn = gconv.String(workInfoId)
+		}
+	}
+	return "work_sql:" + sqlReturn + "|work_msg:" + msgReturn, nil
+}
+
+func workMsg(req *flowModel.SaveWfFlowReq, flowProcessInfo *wf_flow_process.Entity, tx *gdb.TX) (int64, error) {
+	newWorkMsg := gstr.ReplaceIByArray(flowProcessInfo.WorkMsg, []string{
+		"@from_id", gconv.String(req.WfFid),
+		"@run_id", gconv.String(req.RunId),
+		"@check_con", req.CheckCon,
+	})
+	workInfoId, err := wf_workinfo.Add(&wf_workinfo.AddData{
+		Datetime: gtime.Now(),
+		Type:     "work_msg",
+		BillInfo: gconv.String(req),
+		Data:     newWorkMsg,
+		Info:     "success",
+	}, tx)
+	if err != nil {
+		return -1, err
+	}
+	return workInfoId, nil
+}
+
+func workSql(req *flowModel.SaveWfFlowReq, flowProcessInfo *wf_flow_process.Entity, tx *gdb.TX) (int64, error) {
+	newWorkSql := gstr.ReplaceIByArray(flowProcessInfo.WorkSql, []string{
+		"@from_id", gconv.String(req.WfFid),
+		"@run_id", gconv.String(req.RunId),
+		"@check_con", req.CheckCon,
+	})
+	workReturn := ""
+	_, err := g.DB().Query(newWorkSql)
+	if err != nil {
+		g.Log().Error()
+		workReturn = "SQL_Err:" + newWorkSql
+	}
+	workInfoId, err := wf_workinfo.Add(&wf_workinfo.AddData{
+		Datetime: gtime.Now(),
+		Type:     "work_sql",
+		BillInfo: gconv.String(req),
+		Data:     newWorkSql,
+		Info:     workReturn,
+	}, tx)
+	if err != nil {
+		return -1, err
+	}
+	return workInfoId, nil
+}
+
+type RunLogInfo struct {
+	*wf_run_log.Entity
+	User string `json:"user"`
+}
+
+//获取审批日志
+func RunLog(wfFid uint, wfType string) ([]*RunLogInfo, error) {
+	types := g.MapStrStr{
+		"Send":     "流程发起",
+		"ok":       "同意提交",
+		"Back":     "退回修改",
+		"SupEnd":   "终止流程",
+		"Sing":     "会签提交",
+		"sok":      "会签同意",
+		"SingBack": "会签退回",
+		"SingSing": "会签再会签",
+	}
+	runLog, err := wf_run_log.GetRunLog(g.Map{
+		"from_id":    wfFid,
+		"from_table": wfType,
+	})
+	if err != nil {
+		return nil, err
+	}
+	logs := make([]*RunLogInfo, len(runLog))
+	for k, v := range runLog {
+		logs[k] = &RunLogInfo{
+			Entity: v,
+		}
+		logs[k].Btn = types[v.Btn]
+		userInfo, err := user.GetUserById(gconv.Uint64(v.Uid))
+		if err != nil {
+			return nil, err
+		}
+		logs[k].User = userInfo.UserNickname
+	}
+	return logs, nil
+}
+
+//运行记录
+func Run(req *flowModel.CheckWfSaveReq, btn string, tx *gdb.TX) error {
+	if req.Npid != "" && req.Npid != "0" {
+		nexPid := gstr.Split(req.Npid, ",")
+		var wps []*wf_flow_process.Entity
+		for _, v := range nexPid {
+			wfProcess, err := GetProcessInfo(gconv.Uint(v), req.RunId)
+			if err != nil {
+				return err
+			}
+			//添加流程步骤日志
+			wp := new(wf_flow_process.Entity)
+			err = gconv.Struct(wfProcess.Entity, wp)
+			if err != nil {
+				g.Log().Error(err)
+				return gerror.New("转换流程步骤数据失败")
+			}
+			err = AddWorkflowProcess(req.FlowId, wp, gconv.Int64(req.RunId), req.UserId, req.Todo, tx)
+			if err != nil {
+				return err
+			}
+			wps = append(wps, wp)
+		}
+		if wps != nil {
+			//设置下一步审批人员/部门信息
+			err := SetBusinessCheckers(gconv.Int(req.WfFid), req.WfType, gconv.Int64(req.RunId), wps, req.Todo, tx)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	//日志记录
+	fm := &flowModel.SaveWfFlowReq{
+		WfType:     req.WfType,
+		WfFid:      gconv.Int(req.WfFid),
+		WfId:       req.FlowId,
+		CheckCon:   req.CheckCon,
+		RunProcess: req.RunProcess,
+		RunId:      req.RunId,
+		Art:        req.Art,
+		UserId:     req.UserId,
+	}
+	err := AddRunLog(gconv.Int64(req.RunId), fm, btn, tx)
+	if err != nil {
+		return err
+	}
+	return nil
+}

+ 143 - 0
app/service/common/work_flow_service/run_process.go

@@ -0,0 +1,143 @@
+package work_flow_service
+
+import (
+	"gfast/app/model/admin/user"
+	"gfast/app/model/admin/wf_flow"
+	"gfast/app/model/admin/wf_flow_process"
+	"gfast/app/model/admin/wf_run"
+	"gfast/app/model/admin/wf_run_process"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/text/gstr"
+	"github.com/gogf/gf/util/gconv"
+)
+
+//添加流程步骤日志
+func AddWorkflowProcess(wfId int64, process *wf_flow_process.Entity, runId int64, userId uint64, todo string, tx *gdb.TX) error {
+	processData := &ProcessData{
+		Entity: process,
+	}
+	if process.AutoPerson == 6 && process.ProcessType == "is_one" {
+		//事务处理人员
+		wf, err := wf_run.GetRunById(gconv.Uint(runId))
+		if err != nil {
+			return err
+		}
+		userIdBil, err := GetBilValue(wf.FromTable, wf.FromId, process.WorkText)
+		if err != nil {
+			return err
+		}
+		userInfo, err := user.GetUserById(gconv.Uint64(userIdBil))
+		if err != nil {
+			return err
+		}
+		processData.UserInfo = userInfo
+		processData.Todo = g.MapStrAny{"text": userInfo.UserNickname}
+	}
+	sponsorIds := ""
+	sponsorText := ""
+	//非自由选择
+	if todo == "" {
+		if process.AutoPerson == 3 {
+			//办理人员
+			sponsorIds = process.RangeUserIds
+			sponsorText = process.RangeUserText
+		} else if process.AutoPerson == 4 {
+			//办理人员
+			sponsorIds = process.AutoSponsorIds
+			sponsorText = process.AutoSponsorText
+		} else if process.AutoPerson == 5 {
+			//办理部门
+			sponsorIds = process.AutoRoleIds
+			sponsorText = process.AutoRoleText
+		} else if process.AutoPerson == 6 {
+			sponsorText = processData.UserInfo.UserNickname
+			sponsorIds = gconv.String(processData.UserInfo.Id)
+		}
+	} else {
+		todoArr := gstr.Split(todo, "*%*")
+		sponsorText = todoArr[1]
+		sponsorIds = todoArr[0]
+	}
+	timeNow := gconv.Uint(gtime.Timestamp())
+	//判断如果运行步骤已经存在则置为转交状态
+	err := UpdateRunProcess(g.Map{
+		"run_id":           runId,
+		"run_flow":         wfId,
+		"run_flow_process": processData.Id,
+	}, g.Map{"status": 2}, tx)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	//添加新的运行状态
+	data := &wf_run_process.SaveRunProcessData{
+		Uid:               gconv.Uint(userId),
+		RunId:             gconv.Uint(runId),
+		RunFlow:           gconv.Uint(wfId),
+		RunFlowProcess:    gconv.Uint(processData.Id),
+		ParentFlow:        0,
+		ParentFlowProcess: 0,
+		RunChild:          0, //未处理,第一步不能进入子流程
+		Remark:            "",
+		IsSponsor:         0,
+		Status:            0,
+		SponsorIds:        sponsorIds,                        //办理者id
+		SponsorText:       sponsorText,                       //办理者信息
+		AutoPerson:        gconv.Int(processData.AutoPerson), //办理类别
+		JsTime:            timeNow,
+		Dateline:          timeNow,
+		WfMode:            gconv.Int(processData.WfMode),
+		WfAction:          processData.WfAction,
+	}
+	return wf_run_process.Add(data, tx)
+}
+
+func RunCheck(process uint) (status uint, err error) {
+	var entity *wf_run_process.Entity
+	entity, err = wf_run_process.GetProcessById(process)
+	if err != nil {
+		return
+	}
+	status = entity.Status
+	return
+}
+
+func EndProcess(runProcess uint, con string, status int, tx *gdb.TX) (err error) {
+	_, err = wf_run_process.Model.TX(tx).Where("id", runProcess).Update(g.Map{"status": status, "remark": con, "bl_time": gtime.Timestamp()})
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("结束流程错误")
+	}
+	return
+}
+
+func UpdateRunProcess(where g.Map, data g.Map, tx *gdb.TX) (err error) {
+	_, err = wf_run_process.Model.TX(tx).Where(where).Update(data)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("设置运行流程信息失败")
+	}
+	return
+}
+
+func GetBackTodo(req *wf_flow.BackTodoReq) (string, error) {
+	where := &wf_run_process.SearchRunProcess{
+		RunId:          req.RunId,
+		RunFlowProcess: req.Pid,
+	}
+	runProcess, err := wf_run_process.GetProcess(where)
+	if err != nil {
+		return "", err
+	}
+	todo := ""
+	if runProcess == nil {
+		return todo, nil
+	}
+	if runProcess.AutoPerson == 3 {
+		todo = runProcess.SponsorIds + "*%*" + runProcess.SponsorText
+	}
+	return todo, nil
+}

+ 7 - 0
app/service/common/work_flow_service/sign.go

@@ -0,0 +1,7 @@
+package work_flow_service
+
+import "gfast/app/model/admin/wf_run_sign"
+
+func GetSignInfoById(id uint) (sign *wf_run_sign.Entity, err error) {
+	return wf_run_sign.GetSignInfoById(id)
+}

+ 39 - 0
app/service/common/work_flow_service/sing.go

@@ -0,0 +1,39 @@
+package work_flow_service
+
+import (
+	flowModel "gfast/app/model/admin/wf_flow"
+	"gfast/app/model/admin/wf_run_sign"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gtime"
+)
+
+//会签操作
+func DoSing(req *flowModel.CheckWfSaveReq) error {
+	return gerror.New(msgNoAction)
+}
+
+//再会签,会签提交,会签回退 操作
+func DoSingEnt(req *flowModel.CheckWfSaveReq) error {
+	return gerror.New(msgNoAction)
+}
+
+func EndSing(id int, con string, tx *gdb.TX) error {
+	return wf_run_sign.UpdateSing(id, g.Map{
+		"is_agree": 1,
+		"content":  con,
+		"dateline": gtime.Timestamp(),
+	}, tx)
+}
+
+func AddSing(req *flowModel.CheckWfSaveReq, tx *gdb.TX) (int64, error) {
+	data := g.Map{
+		"run_id":           req.RunId,
+		"run_flow":         req.FlowId,
+		"run_flow_process": req.RunProcess,
+		"uid":              req.WfSingFlow,
+		"dateline":         gtime.Timestamp(),
+	}
+	return wf_run_sign.AddSing(data, tx)
+}

+ 125 - 0
app/service/common/work_flow_service/task.go

@@ -0,0 +1,125 @@
+package work_flow_service
+
+import (
+	flowModel "gfast/app/model/admin/wf_flow"
+	"gfast/app/model/admin/wf_run_process"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/text/gstr"
+	"github.com/gogf/gf/util/gconv"
+)
+
+func DoTask(req *flowModel.CheckWfSaveReq) error {
+	npid := req.Npid             //下一步骤流程id
+	runId := req.RunId           //运行中的id
+	runProcess := req.RunProcess //运行中的process
+	if req.Sup == 1 {
+		req.CheckCon = "[管理员代办]" + req.CheckCon
+	}
+	var runProcessList []*wf_run_process.Entity
+	if req.WfMode == 2 {
+		var err error
+		runProcessList, err = wf_run_process.GetProcessByMap(g.Map{
+			"run_id": req.RunId,
+			"status": 0,
+			"id <> ": req.RunProcess,
+		})
+		if err != nil {
+			return err
+		}
+		if runProcessList != nil {
+			npids := make([]uint, len(runProcessList))
+			for k, v := range runProcessList {
+				npids[k] = v.RunFlowProcess
+			}
+			npid = gstr.JoinAny(npids, ",")
+		}
+	}
+	tx, err := g.DB().Begin()
+	if err != nil {
+		g.Log().Error()
+		return gerror.New("开启事务失败")
+	}
+	if npid != "0" && npid != "" { //判断是否为最后一步
+		//不是最后一步
+		//结束流程
+		err = EndProcess(runProcess, req.CheckCon, 2, tx)
+		if err != nil {
+			tx.Rollback()
+			return err
+		}
+		//同步模式下只写入记录
+		if req.WfMode != 2 { //非同步模式
+			//更新单据信息
+			err = Up(runId, npid, tx)
+			if err != nil {
+				tx.Rollback()
+				return err
+			}
+			//记录下一个流程(消息记录)
+			err = Run(req, "ok", tx)
+			if err != nil {
+				tx.Rollback()
+				return err
+			}
+		} else {
+			fm := &flowModel.SaveWfFlowReq{
+				WfType:     req.WfType,
+				WfFid:      gconv.Int(req.WfFid),
+				WfId:       req.FlowId,
+				CheckCon:   req.CheckCon,
+				RunProcess: req.RunProcess,
+				RunId:      req.RunId,
+				Art:        req.Art,
+				UserId:     req.UserId,
+			}
+			err = AddRunLog(gconv.Int64(req.RunId), fm, "ok", tx)
+			if err != nil {
+				tx.Rollback()
+				return err
+			}
+		}
+	} else {
+		//是最后一步
+		//结束流程
+		err = EndFlow(runId, tx)
+		if err != nil {
+			tx.Rollback()
+			return err
+		}
+		err = EndProcess(runProcess, req.CheckCon, 2, tx)
+		if err != nil {
+			tx.Rollback()
+			return err
+		}
+		//删除审批表中的用户/部门信息
+		err = DeleteBusinessChecker(req.WfFid, req.WfType, tx)
+		if err != nil {
+			tx.Rollback()
+			return err
+		}
+		fm := &flowModel.SaveWfFlowReq{
+			WfType:     req.WfType,
+			WfFid:      gconv.Int(req.WfFid),
+			WfId:       req.FlowId,
+			CheckCon:   req.CheckCon,
+			RunProcess: req.RunProcess,
+			RunId:      req.RunId,
+			Art:        req.Art,
+			UserId:     req.UserId,
+		}
+		err = AddRunLog(gconv.Int64(runId), fm, "ok", tx)
+		if err != nil {
+			tx.Rollback()
+			return err
+		}
+		//更新单据状态
+		err = UpdateBill(gconv.Int(req.WfFid), req.WfType, req.WfStatusField, 2, tx)
+		if err != nil {
+			tx.Rollback()
+			return err
+		}
+	}
+	tx.Commit()
+	return nil
+}

+ 2 - 2
go.mod

@@ -7,8 +7,8 @@ require (
 	github.com/go-ole/go-ole v1.2.4 // indirect
 	github.com/go-openapi/spec v0.19.11 // indirect
 	github.com/go-openapi/swag v0.19.11 // indirect
-	github.com/goflyfox/gtoken v1.3.21
-	github.com/gogf/gf v1.14.4
+	github.com/goflyfox/gtoken v1.4.1
+	github.com/gogf/gf v1.14.5
 	github.com/mailru/easyjson v0.7.6 // indirect
 	github.com/mojocn/base64Captcha v1.3.1
 	github.com/mssola/user_agent v0.5.1

+ 4 - 8
go.sum

@@ -59,13 +59,11 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
-github.com/goflyfox/gtoken v1.3.21 h1:62k9aqcqGJ17axDw5dsIgJzhESIaSohNC/DVhvwzx6E=
-github.com/goflyfox/gtoken v1.3.21/go.mod h1:/p/vWt/TjHB22+sWnr+W/nLB0RR9akp7B+iUT+dMd3I=
+github.com/goflyfox/gtoken v1.4.1 h1:mlnu4ZHZVZHvAlUzMNTI3ZO916D1WySvYerimbOOiKY=
+github.com/goflyfox/gtoken v1.4.1/go.mod h1:ylHVvQ4/nTstw81iElcDWFht4LAKL4Pqhw7Ja2UaGAk=
 github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
-github.com/gogf/gf v1.14.2 h1:xIgfq2YcMYAm1ZcbVD/u0y5qZDDdARY4u7261w8QlHA=
-github.com/gogf/gf v1.14.2/go.mod h1:7b21qQKDyIwJO4PkBCxVci5C62tm89MANGV2wJgAf50=
-github.com/gogf/gf v1.14.4 h1:44EfQ7mluq/qEkppzk6Pg4I7QVP+9DBu/JOLaR5nXkQ=
-github.com/gogf/gf v1.14.4/go.mod h1:s4b0tkBqHyEWAk/Hwm4hzUCbCbdIPeERxB2wmeBg11g=
+github.com/gogf/gf v1.14.5 h1:y/4q8rfFtiuIL7PwNUhG8RyBWLlvR1pl44x7/EJeDMI=
+github.com/gogf/gf v1.14.5/go.mod h1:s4b0tkBqHyEWAk/Hwm4hzUCbCbdIPeERxB2wmeBg11g=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -76,8 +74,6 @@ github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
 github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/gqcn/structs v1.1.1 h1:cyzGRwfmn3d1d54fwW3KUNyG9QxR0ldIeqwFGeBt638=
-github.com/gqcn/structs v1.1.1/go.mod h1:/aBhTBSsKQ2Ec9pbnYdGphtdWXHFn4KrCL0fXM/Adok=
 github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf h1:wIOAyJMMen0ELGiFzlmqxdcV1yGbkyHBAB6PolcNbLA=
 github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=

+ 1 - 1
plugin/blog/controller/home/index.go

@@ -180,7 +180,7 @@ func (c *Index) Content(r *ghttp.Request) {
 	}
 	// 更新点击数
 	log.LogHits += 1
-	blog_log.Model.Save(log)
+	blog_log.Model.Save(log.Entity)
 	if log.LogUrl != "" {
 		//跳转连接
 		r.Response.RedirectTo(log.LogUrl)

+ 7 - 0
router/routerAdmin.go

@@ -83,5 +83,12 @@ func init() {
 				group.ALL("/info", new(admin.PlugLink))
 			})
 		})
+
+		//工作流
+		group.Group("/wf", func(group *ghttp.RouterGroup) {
+			group.ALL("/flow", new(admin.Flow))
+			group.ALL("/news", new(admin.WfNews))
+		})
+
 	})
 }

+ 16 - 0
swagger/docs.go

@@ -3770,9 +3770,22 @@ var doc = `{
                         "type": "integer"
                     }
                 },
+                "isSlide": {
+                    "type": "string"
+                },
+                "isTop": {
+                    "type": "string"
+                },
                 "keyWords": {
                     "type": "string"
                 },
+                "newsStatus": {
+                    "type": "string"
+                },
+                "orderBy": {
+                    "description": "排序字段",
+                    "type": "string"
+                },
                 "pageNum": {
                     "description": "当前页码",
                     "type": "integer"
@@ -3786,6 +3799,9 @@ var doc = `{
                 },
                 "publishedTimeStart": {
                     "type": "string"
+                },
+                "recommended": {
+                    "type": "string"
                 }
             }
         },

+ 16 - 0
swagger/swagger.json

@@ -3755,9 +3755,22 @@
                         "type": "integer"
                     }
                 },
+                "isSlide": {
+                    "type": "string"
+                },
+                "isTop": {
+                    "type": "string"
+                },
                 "keyWords": {
                     "type": "string"
                 },
+                "newsStatus": {
+                    "type": "string"
+                },
+                "orderBy": {
+                    "description": "排序字段",
+                    "type": "string"
+                },
                 "pageNum": {
                     "description": "当前页码",
                     "type": "integer"
@@ -3771,6 +3784,9 @@
                 },
                 "publishedTimeStart": {
                     "type": "string"
+                },
+                "recommended": {
+                    "type": "string"
                 }
             }
         },

+ 11 - 0
swagger/swagger.yaml

@@ -195,8 +195,17 @@ definitions:
         items:
           type: integer
         type: array
+      isSlide:
+        type: string
+      isTop:
+        type: string
       keyWords:
         type: string
+      newsStatus:
+        type: string
+      orderBy:
+        description: 排序字段
+        type: string
       pageNum:
         description: 当前页码
         type: integer
@@ -207,6 +216,8 @@ definitions:
         type: string
       publishedTimeStart:
         type: string
+      recommended:
+        type: string
     type: object
   gen_table.EditReq:
     properties: