commit d110ca4c4b6293e2ab01723553d6a0e5ad1a59b7
Author: wanggeng <450292408@qq.com>
Date: Wed May 25 18:33:19 2022 +0800
fc
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..496ee2c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.DS_Store
\ No newline at end of file
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
new file mode 100644
index 0000000..14ebc68
--- /dev/null
+++ b/docs/.vuepress/config.js
@@ -0,0 +1,42 @@
+module.exports = {
+ base: '/',
+ lang: 'zh-CN',
+ title: 'WGINK',
+ description: '',
+ themeConfig: {
+ sidebar: [
+ {
+ title: '首页',
+ path: '/',
+ sidebarDepth: 1,
+ children: []
+ },
+ {
+ title: '业务',
+ collapsable: true,
+ sidebarDepth: 5,
+ children: [
+ {
+ title: 'OA',
+ collapsable: true,
+ sidebarDepth: 5,
+ children: [
+ '/service/oa/backend.md',
+ '/service/oa/app.md',
+ ],
+ },
+ ],
+ },
+ {
+ title: '代码模板',
+ collapsable: true,
+ sidebarDepth: 5,
+ children: [
+ '/code-template/area-select',
+ '/code-template/excel-upload',
+ '/code-template/select'
+ ],
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/code-template/area-select.md b/docs/code-template/area-select.md
new file mode 100644
index 0000000..1e226eb
--- /dev/null
+++ b/docs/code-template/area-select.md
@@ -0,0 +1,92 @@
+# 地区选择
+
+> 注意匹配ID
+
+## 表单
+
+css
+
+```css
+.select-area {position: relative;}
+.select-area #areaName {width: 64%}
+.select-area .select-btn {position: absolute; top: 0px; right: 0px; width: 36%; border-color: #e6e6e6;}
+.select-area .select-btn button {height: 38px; width: 50%;}
+```
+
+html
+
+```html
+
+```
+
+## 列表代码
+
+css
+
+```css
+.table-select-area {position: relative; width: 200px;}
+.table-select-area #areaName {width: 64%}
+.table-select-area .select-btn {position: absolute; top: 0px; right: 0px; width: 36%; border-color: #e6e6e6;}
+```
+
+```html
+
+```
+
+## 事件
+
+```javascript
+$('#areaSelectBtn').on('click', function() {
+ top.dialog.open({
+ title: '选择地区',
+ url: top.restAjax.path('route/area/get-select?areaName={areaName}', [encodeURI($('#areaName').val())]),
+ width: '600px',
+ height: '225px',
+ onClose: function() {
+ var selectedAreaArray = top.dialog.dialogData.selectedAreaArray;
+ if(selectedAreaArray.length > 0) {
+ var areaCode = '';
+ var areaName = '';
+ areaCode = selectedAreaArray[selectedAreaArray.length - 1].areaCode;
+ for(var i = 0, item; item = selectedAreaArray[i++];) {
+ if(areaName) {
+ areaName += ' / ';
+ }
+ areaName += item.areaName;
+ }
+ $('#areaCode').val(areaCode);
+ $('#areaName').val(areaName);
+ }
+ }
+ })
+});
+$('#areaCleanBtn').on('click', function () {
+ $('#areaCode').val('');
+ $('#areaName').val('');
+})
+```
\ No newline at end of file
diff --git a/docs/code-template/excel-upload.md b/docs/code-template/excel-upload.md
new file mode 100644
index 0000000..a5a9788
--- /dev/null
+++ b/docs/code-template/excel-upload.md
@@ -0,0 +1,284 @@
+# EasyExcel 上传(导入)
+
+以人口(Population)导入为例:
+
+1. 包含 **Excel实体类**、**错误实体类**、**导入监听器**,其中 **错误实体类** 非必须。
+
+2. 如果不需要返回错误,可忽略输出错误文件的代码。
+
+## Excel实体类
+
+```java
+import com.alibaba.excel.annotation.ExcelProperty;
+
+public class PopulationExcel {
+
+ @ExcelProperty(index = 0)
+ private String name;
+ @ExcelProperty(index = 1)
+ private String idCard;
+ @ExcelProperty(index = 2)
+ private String areaCode;
+ @ExcelProperty(index = 3)
+ private String homeAddress;
+
+ // get set
+}
+
+```
+
+## 错误试题类
+
+```java
+import com.alibaba.excel.annotation.ExcelProperty;
+
+public class PopulationExcelError {
+
+ @ExcelProperty(index = 0)
+ private String name;
+ @ExcelProperty(index = 1)
+ private String idCard;
+ @ExcelProperty(index = 2)
+ private String areaCode;
+ @ExcelProperty(index = 3)
+ private String homeAddress;
+ @ExcelProperty(index = 4)
+ private String reason;
+
+ // get set
+}
+
+```
+
+## 监听器
+
+```java
+import com.alibaba.excel.context.AnalysisContext;
+import com.alibaba.excel.event.AnalysisEventListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class PopulationExcelListener extends AnalysisEventListener {
+
+ private static final Integer MAX_READ_COUNT = 100;
+ private List populationExcels = new ArrayList<>();
+
+ @Override
+ public void invoke(PopulationExcel populationExcel, AnalysisContext analysisContext) {
+ if (populationExcels.size() > MAX_READ_COUNT) {
+ // 超过读取最大量,执行保存,
+ handle(populationExcels);
+ populationExcels.clear();
+ } else {
+ // 未达到读取最大量,继续读取
+ populationExcels.add(populationExcel);
+ }
+ }
+
+ @Override
+ public void doAfterAllAnalysed(AnalysisContext analysisContext) {
+ handle(populationExcels);
+ populationExcels.clear();
+ }
+
+ /**
+ * 处理
+ *
+ * @param rows
+ */
+ public abstract void handle(List rows);
+
+}
+```
+
+## 导入代码
+
+### controller 层
+
+```java
+@ApiOperation(value = "试题Excel", notes = "试题Excel接口")
+@ApiImplicitParams({
+ @ApiImplicitParam(name = "excel", value = "文件名称", paramType = "form"),
+})
+@ApiResponses({@ApiResponse(code = 400, message = "请求失败", response = ErrorResult.class)})
+@PostMapping("import-excel")
+public UploadExcelResultDTO importExcel(MultipartFile excel) throws IOException {
+ if (Objects.isNull(excel)) {
+ throw new ParamsException("Excel不能为空");
+ }
+ // 判断后缀
+ if (!excel.getOriginalFilename().endsWith(IFileConstant.EXCEL_SUFFIX_XLS)
+ && !excel.getOriginalFilename().endsWith(IFileConstant.EXCEL_SUFFIX_XLSX)) {
+ throw new ParamsException("文件格式为Excel");
+ }
+ return populationService.importExcel(excel);
+}
+```
+
+### service 层
+
+```java
+@Override
+public UploadExcelResultDTO importExcel(MultipartFile excel) throws IOException {
+
+ // 要输出的错误内容
+ List populationExcelErrors = new ArrayList<>();
+
+ long startTime = System.currentTimeMillis();
+ // 读取 Excel
+ EasyExcel.read(excel.getInputStream(), PopulationExcel.class, new PopulationExcelListener() {
+ @Override
+ public void handle(List populationExcels) {
+ // 这里处理数据,一般是入库
+
+ }
+ }).headRowNumber(2).sheet().doRead();
+ long endTime = System.currentTimeMillis();
+
+ // 生成的错误文件ID,下载时使用
+ String excelFileId = null;
+ if (populationExcelErrors.size() > 0) {
+ excelFileId = new AbstractErrorExcelHandler(fileService) {
+ @Override
+ public List> excelHeaderNames() {
+ // 构建错误 Excel 标题,与错误类对应
+ return simpleExcelHeader(new String[]{"序号", "姓名", "身份证", "错误原因"});
+ }
+ }.saveErrorExcel(populationExcelErrors);
+ }
+ // 返回实体类,
+ return new UploadExcelResultDTO(populationExcelErrors.size(), endTime - startTime, excelFileId);
+}
+```
+
+## 模板下载接口
+```java
+@GetMapping("upload/upload-excel-template")
+public void excelTemplate(HttpServletResponse response) throws IOException {
+ InputStream inputStream = PopulationRouteController.class.getClassLoader().getResourceAsStream("templates/population/upload/upload-excel-template.xls");
+ RequestUtil.download(response, inputStream, "人口导入模板.xls");
+}
+```
+
+## 列表导入按钮
+
+```html
+
+```
+
+## 列表导入事件
+
+```javascript
+$(document).on('click', '#uploadExcel', function() {
+ top.dialog.open({
+ url: top.restAjax.path('route/population/upload/upload-excel', []),
+ title: '导入人口数据',
+ width: '300px',
+ height: '196px',
+ onClose: function() {
+ reloadTable();
+ }
+ });
+});
+```
+
+
+## 导入页面代码
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
下载“下载模板”整理数据,点击“导入数据”上传,格式为xls或xlsx
+
+
+
+
+
+
+
+
+
+
+```
\ No newline at end of file
diff --git a/docs/code-template/select.md b/docs/code-template/select.md
new file mode 100644
index 0000000..4a36922
--- /dev/null
+++ b/docs/code-template/select.md
@@ -0,0 +1,98 @@
+# 下拉选择
+
+## 基于JQuery的下拉选择
+
+### 直接选择
+
+html
+
+```html
+
+```
+
+js
+
+```javascript
+// 初始化
+function initSelect(callback) {
+ top.restAjax.get(top.restAjax.path('url', ['']), {}, null, function(code, data) {
+ $('#selector').append('');
+ $.each(data, function (index, item) {
+ $('#selector').append('');
+ });
+ // 渲染表单下拉框
+ form.render(null, 'dataForm');
+ callback ? callback() : null;
+ }, function(code, data) {
+ top.dialog.msg(data.msg);
+ })
+}
+```
+
+### 选择并获取其他属性
+
+以部门选择为例,选择部门既要获取部门名称,又要获取部门ID
+
+html
+
+```html
+
+```
+
+js
+
+```javascript
+// 初始化
+function initDepartmentSelect(callback) {
+ top.restAjax.get(top.restAjax.path('api/department/list/0', []), {}, null, function(code, data) {
+ $('#departmentName').append('');
+ $.each(data, function (index, item) {
+ $('#departmentName').append('');
+ });
+ // 渲染表单下拉框
+ form.render(null, 'dataForm');
+ callback ? callback() : null;
+ }, function(code, data) {
+ top.dialog.msg(data.msg);
+ })
+
+ // 添加监听事件
+ form.on('select(departmentNameFilter)', function(data) {
+ var value = data.value;
+ // 遍历 option 获取 option 上的属性
+ for(var i = 0, item; item = $(data.elem).children()[i++];) {
+ if(item.value == value) {
+ $('#departmentId').val(item.dataset.departmentId);
+ break;
+ }
+ }
+ });
+}
+```
+
+
+
+
+
+### 列表上的选择
+
+```html
+
+
+
+```
\ No newline at end of file
diff --git a/docs/service-expand/user-expand.md b/docs/service-expand/user-expand.md
new file mode 100644
index 0000000..cf754d7
--- /dev/null
+++ b/docs/service-expand/user-expand.md
@@ -0,0 +1,441 @@
+# 用户拓展
+
+在用户现有字段基础上,自定义 **拓展属性字段**,新增拓展表,通过 **user_id** 字段关联用户。
+
+## 创建实体类
+
+实体类继承自 `ink.wgink.pojo.dtos.user.UserDTO`
+
+```java
+
+import ink.wgink.pojo.dtos.user.UserDTO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+public class UserExpandDTO extends UserDTO {
+
+ private String userExpandId;
+ private String title;
+ private String company;
+ private String summary;
+
+ public UserExpandDTO() {
+
+ }
+
+ public UserExpandDTO(UserDTO userDTO) {
+ setUserDTO(userDTO);
+ }
+
+ public void setUserDTO(UserDTO userDTO) {
+ super.setUserId(userDTO.getUserId());
+ super.setUserUsername(userDTO.getUserUsername());
+ super.setUserName(userDTO.getUserName());
+ super.setUserAvatar(userDTO.getUserAvatar());
+ super.setUserEmail(userDTO.getUserEmail());
+ super.setUserLatitude(userDTO.getUserLatitude());
+ super.setUserExpiredDate(userDTO.getUserExpiredDate());
+ super.setUserLongitude(userDTO.getUserLongitude());
+ super.setUserPhone(userDTO.getUserPhone());
+ super.setUserState(userDTO.getUserState());
+ super.setUserType(userDTO.getUserType());
+ super.setUserUKey(userDTO.getUserUKey());
+ super.setDepartmentIds(userDTO.getDepartmentIds());
+ super.setDepartmentNames(userDTO.getDepartmentNames());
+ super.setPositionIds(userDTO.getPositionIds());
+ super.setPositionNames(userDTO.getPositionNames());
+ super.setRoleIds(userDTO.getRoleIds());
+ super.setRoleNames(userDTO.getRoleNames());
+ super.setGmtCreate(userDTO.getGmtCreate());
+ super.setLastLoginAddress(userDTO.getLastLoginAddress());
+ super.setLastLoginTime(userDTO.getLastLoginTime());
+ super.setLoginType(userDTO.getLoginType());
+ }
+}
+```
+
+## 新建接口
+
+实现或继承接口 `ink.wgink.interfaces.user.IUserExpandBaseService`
+
+可以直接实现,也可以通过接口继承
+
+```java
+import ink.wgink.interfaces.user.IUserExpandBaseService;
+
+import java.util.List;
+import java.util.Map;
+
+public interface IUserExpandService extends IUserExpandBaseService {
+
+ /**
+ * 修改
+ *
+ * @param userId 用户ID
+ * @param userExpandVO 拓展属性
+ */
+ void update(String userId, UserExpandVO userExpandVO);
+
+ /**
+ * 修改
+ *
+ * @param token
+ * @param userExpandVO
+ * @throws Exception
+ */
+ void updateByToken(String token, UserExpandVO userExpandVO) throws Exception;
+
+ /**
+ * 用户拓展详情
+ *
+ * @param params
+ * @return
+ */
+ UserExpandPO getPO(Map params);
+
+ /**
+ * 用户拓展详情
+ *
+ * @param userId
+ * @return
+ */
+ UserExpandPO getPO(String userId);
+
+ /**
+ * 用户拓展列表
+ *
+ * @param params
+ * @return
+ */
+ List listPO(Map params);
+
+ /**
+ * 详情
+ *
+ * @param token
+ * @return
+ */
+ UserExpandDTO getByToken(String token) throws Exception;
+}
+```
+
+## 实现接口
+
+当方法 `getRoute` 有返回值时,用户列表中会出现 拓展属性 按钮,用户通过点击按钮打开拓展属性的编辑页面。在编辑页面打开时,在 `window.location.href` 上会携带 `userId` 字段。
+
+为了保证唯一,在提交时,需判断用户是否已经有值。如果有值则修改,反之新增。
+
+示例代码中,没有完善的方法可根据情况自行处理。
+
+```java
+import ink.wgink.interfaces.user.IUserBaseService;
+import ink.wgink.pojo.ListPage;
+import ink.wgink.pojo.dtos.user.UserDTO;
+import ink.wgink.pojo.result.SuccessResultList;
+import ink.wgink.util.map.HashMapUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class UserExpandServiceImpl implements IUserExpandService {
+
+ @Autowired
+ private IUserExpandDao userExpandDao;
+ @Autowired
+ private IUserBaseService userBaseService;
+
+ @Override
+ public void update(String userId, UserExpandVO userExpandVO) {
+ UserExpandPO userExpandPO = getPO(userId);
+ if (userExpandPO != null) {
+ updateInfo(null, userId, userExpandVO);
+ } else {
+ saveInfo(null, userId, userExpandVO);
+ }
+ }
+
+ @Override
+ public void updateByToken(String token, UserExpandVO userExpandVO) throws Exception {
+ String userId = securityComponent.getAppTokenUser(token).getId();
+ UserExpandPO userExpandPO = getPO(userId);
+ if (userExpandPO != null) {
+ updateInfo(token, userId, userExpandVO);
+ } else {
+ saveInfo(token, userId, userExpandVO);
+ }
+ }
+
+ /**
+ * 保存
+ *
+ * @param token
+ * @param userId
+ * @param userExpandVO
+ */
+ private void saveInfo(String token, String userId, UserExpandVO userExpandVO) {
+ Map params = HashMapUtil.beanToMap(userExpandVO);
+ params.put("userId", userId);
+ if (StringUtils.isNotBlank(token)) {
+ setAppSaveInfo(token, params);
+ } else {
+ setSaveInfo(params);
+ }
+ userExpandDao.save(params);
+ }
+
+ /**
+ * 修改
+ *
+ * @param token
+ * @param userId
+ * @param userExpandVO
+ */
+ private void updateInfo(String token, String userId, UserExpandVO userExpandVO) {
+ Map params = HashMapUtil.beanToMap(userExpandVO);
+ params.put("userId", userId);
+ if (StringUtils.isNotBlank(token)) {
+ setAppUpdateInfo(token, params);
+ } else {
+ setUpdateInfo(params);
+ }
+ userExpandDao.update(params);
+ }
+
+ @Override
+ public UserExpandPO getPO(Map params) {
+ return userExpandDao.getPO(params);
+ }
+
+ @Override
+ public UserExpandPO getPO(String userId) {
+ Map params = getHashMap(2);
+ params.put("userId", userId);
+ return getPO(params);
+ }
+
+ @Override
+ public List listPO(Map params) {
+ return userExpandDao.listPO(params);
+ }
+
+ @Override
+ public UserExpandDTO getByToken(String token) throws Exception {
+ String userId = securityComponent.getAppTokenUser(token).getId();
+ return get(userId);
+ }
+
+ @Override
+ public String getRoute() {
+ // 编辑页面的路径,只有详情页
+ return "route/userexpand/get.html";
+ }
+
+ private UserExpandDTO getDTO(Map params) {
+ return userExpandDao.get(params);
+ }
+
+ private UserExpandDTO getDTO(String userId) {
+ Map params = getHashMap(2);
+ params.put("userId", userId);
+ return getDTO(params);
+ }
+
+ @Override
+ public UserExpandDTO get(String userId) {
+ UserDTO userDTO = userBaseService.get(userId);
+ if (userDTO == null) {
+ return null;
+ }
+ UserExpandDTO userExpandDTO = getDTO(userId);
+ if (userExpandDTO == null) {
+ return new UserExpandDTO(userDTO);
+ }
+ userExpandDTO.setUserDTO(userDTO);
+ return userExpandDTO;
+ }
+
+ @Override
+ public UserExpandDTO getByUsername(String username) {
+ return null;
+ }
+
+ @Override
+ public List listByUserIds(List userIds) {
+ return null;
+ }
+
+ @Override
+ public List listByUsernames(List usernames) {
+ return null;
+ }
+
+ @Override
+ public List list(Map params) {
+ return null;
+ }
+
+ @Override
+ public SuccessResultList> listPage(ListPage page) {
+ return null;
+ }
+
+ @Override
+ public SuccessResultList> listPageByIds(List userIds, ListPage page) {
+ return null;
+ }
+
+ @Override
+ public SuccessResultList> listPageByExcludeIds(List excludeUserIds, ListPage page) {
+ return null;
+ }
+
+ @Override
+ public int countDateRange(String startDate, String endDate) {
+ return 0;
+ }
+
+ @Override
+ public int count() {
+ return 0;
+ }
+
+ @Override
+ public List listByKeywords(String keywords) {
+ return null;
+ }
+}
+
+```
+
+## 页面
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
\ No newline at end of file
diff --git a/docs/service/oa/app.md b/docs/service/oa/app.md
new file mode 100644
index 0000000..72650a7
--- /dev/null
+++ b/docs/service/oa/app.md
@@ -0,0 +1,319 @@
+# APP业务
+
+js依赖
+
+```html
+
+```
+
+## 初始化
+
+页面需要隐藏 `oa-form-footer-tool-bar`,由原生调用方法触发
+
+> 页面加载 -> APP初始化
+
+```js
+appOaFormUtil.setForm(layui.form);
+appOaFormUtil.initAppFormData(initObj);
+```
+
+参数
+
+|名称|类型|说明|
+|-|-|-|
+|initObj|object|初始化对象|
+
+initObj
+
+|名称|类型|说明|
+|-|-|-|
+|confirmAssignees|array|[确认代理人](./app.md#代理人)列表|
+|fields|array|[字段](./app.md#字段)列表|
+|formButton|object|[表单按钮](./app.md#表单按钮)|
+|currentUser|object|[当前用户](./app.md#当前用户)|
+
+## 方法
+
+### 字段赋值
+
+> APP -> 页面字段
+
+方法
+
+```js
+appOaFormUtil.setFieldValue(fieldName, fieldValue)
+```
+
+参数
+
+|名称|类型|说明|参数值|
+|-|-|-|-|
+|fieldName|string|字段名|
+|fieldValue|string|字段值|
+
+### 附件赋值
+
+方法
+
+```js
+appOaFormUtil.setAttachments(attachments)
+```
+
+参数
+
+|名称|类型|说明|参数值|
+|-|-|-|-|
+|attachments|string|附件列表|格式:附件1,附件2。附件格式:ID:名称。英文冒号分割ID与名称,英文逗号分割组。名称不能有英文冒号与逗号|
+
+### 抄送人赋值
+
+方法
+
+```js
+appOaFormUtil.setCcs(ccs)
+```
+
+参数
+
+|名称|类型|说明|参数值|
+|-|-|-|-|
+|ccs|string|抄送人列表|格式:抄送人ID1,抄送人ID2。英文逗号分割。|
+
+### 提交表单
+
+```js
+appOaFormUtil.submitFormReport(btnIndex);
+```
+
+参数
+
+|名称|类型|说明|参数值|
+|-|-|-|-|
+|btnIndex|int|按钮的下标||
+
+## 事件
+
+### 回退
+
+- Method: **PUT**
+
+- Content-Type: `application/json`
+
+- URL:`app/oa-form-report/update-go-back/process-instance-id/{processInstanceId}/task-id/{taskId}/node-id/{nodeId}`
+
+- PathParams:
+
+|参数|说明|
+|-|-|
+|processInstanceId|流程实例ID|
+|taskId|当前任务ID|
+|nodeId|回退到的节点ID|
+
+- Headers:
+
+```json
+{
+ token: token
+}
+```
+
+- RequestBody:
+
+```json
+{
+ reason: "原因"
+}
+```
+
+- Response
+
+状态码
+
+|名称|值|
+|-|-|
+|200|请求成功|
+|400|请求错误|
+|401|权限不足|
+|403|访问禁止|
+|500|系统错误|
+
+Body:
+
+```json
+{
+ code: "错误编码",
+ msg: "错误原因"
+}
+```
+
+### 强制结束
+
+- Method: **PUT**
+
+- Content-Type: `application/json`
+
+- URL:`app/oa-form-report/update-forced-end/process-instance-id/{processInstanceId}/task-id/{taskId}`
+
+- PathParams:
+
+|参数|说明|
+|-|-|
+|processInstanceId|流程实例ID|
+|taskId|当前任务ID|
+
+- Headers:
+
+```json
+{
+ token: token
+}
+```
+
+- RequestBody:
+
+```json
+{
+ reason: "原因"
+}
+```
+- Response
+
+状态码
+
+|名称|值|
+|-|-|
+|200|请求成功|
+|400|请求错误|
+|401|权限不足|
+|403|访问禁止|
+|500|系统错误|
+
+Body:
+
+```json
+{
+ code: "错误编码",
+ msg: "错误原因"
+}
+```
+
+### 转交
+
+- Method: **PUT**
+
+- Content-Type: `application/json`
+
+- URL:`app/oa-form-report/update-assignee/process-instance-id/{processInstanceId}/task-id/{taskId}`
+
+- PathParams:
+
+|参数|说明|
+|-|-|
+|processInstanceId|流程实例ID|
+|taskId|当前任务ID|
+
+- Headers:
+
+```json
+{
+ token: token
+}
+```
+
+- RequestBody:
+
+```json
+{
+ assignee: "接收人ID",
+ reason: "原因"
+}
+```
+
+- Response
+
+状态码
+
+|名称|值|
+|-|-|
+|200|请求成功|
+|400|请求错误|
+|401|权限不足|
+|403|访问禁止|
+|500|系统错误|
+
+Body:
+
+```json
+{
+ code: "错误编码",
+ msg: "错误原因"
+}
+```
+
+## 对象说明
+
+### 代理人
+
+|属性|类型|说明|参数值|
+|-|-|-|-|
+|assignees|Array|[代理人用户](./app.md#代理人用户)列表||
+|btnExc|string|按钮条件||
+|btnText|string|按钮名称||
+|nextEndEvent|boolean|下节点是否为结束节点|true:是,false:否|
+|nodeType|string|节点类型|normal:普通(单实例)节点,multiple:多实例节点|
+
+### 代理人用户
+
+|属性|类型|说明|参数值|
+|-|-|-|-|
+|userId|string|用户ID||
+|userName|string|用户昵称||
+|userUsername|string|用户名||
+|avatar|string|头像ID||
+
+### 字段
+
+|属性|类型|说明|参数值|
+|-|-|-|-|
+|fieldName|string|字段名称||
+|isEditable|int|可编辑|0:否,1:是|
+|isVisible|int|可显示|0:否,1:是|
+|editHistory|string|编辑历史|noRecord:不记录(默认),latest:记录最新(签批),all:记录全部(会签)
+|autoBackFill|string|自动回填|noBackFill:不回填(默认),currentUserName:当前用户名,currentUserDepartment:当前用户部门|
+
+### 表单按钮
+
+|属性|类型|说明|参数值|
+|-|-|-|-|
+|btnAttachment|int|附件按钮|0:未激活,1:已激活|
+|btnForcedEnd|int|强制结束按钮|0:未激活,1:已激活|
+|btnPrint|int|打印按钮|0:未激活,1:已激活
+|btnCc|int|抄送按钮|0:未激活,1:已激活|
+|btnGoBack|int|回退按钮|0:未激活,1:已激活|
+|btnTransfer|int|转交按钮|0:未激活,1:已激活|
+|goBackUserTasks|array|[历史任务](./app.md#历史任务)列表(按执行顺序),当 btnGoBack 激活时有效||
+
+### 历史任务
+
+|属性|类型|说明|参数值|
+|-|-|-|-|
+|nodeId|string|节点ID||
+|taskName|string|任务名称||
+
+### 当前用户
+
+|属性|类型|说明|参数值|
+|-|-|-|-|
+|userId|string|用户ID||
+|userName|string|用户名||
+|departments|array|[用户部门](./app.md#用户部门)列表||
+
+### 用户部门
+
+|属性|类型|说明|参数值|
+|-|-|-|-|
+|departmentId|string|部门ID||
+|departmentParentId|string|部门上级ID||
+|departmentName|string|部门名称||
+|departmentCode|string|部门编码||
+|departmentNo|string|部门编号||
\ No newline at end of file
diff --git a/docs/service/oa/backend.md b/docs/service/oa/backend.md
new file mode 100644
index 0000000..7f74746
--- /dev/null
+++ b/docs/service/oa/backend.md
@@ -0,0 +1,58 @@
+# 后台业务
+
+## 访问页面
+
+### 新增页面
+
+1. 设置下个节点的代理人列表
+
+2. 设置新增页面模型
+
+- 设置页面字段:绘制流程时每个节点的[字段](./backend.md#页面字段)设置
+
+- 设置表单按钮以和历史任务
+
+ - 表单按钮:绘制流程时每个节点的[按钮](./backend.md#表单按钮)
+
+ - 历史任务:当前任务在流程中走过的[历史任务](./backend.md#历史任务)列表
+
+3. 设置新增页面当前用户
+
+4. 返回页面
+
+## 对象说明
+
+### 页面字段
+
+> FieldVO
+
+|属性|类型|说明|参数值|
+|-|-|-|-|
+|fieldName|string|字段名称||
+|isEditable|int|可编辑|0:否,1:是|
+|isVisible|int|可显示|0:否,1:是|
+|editHistory|string|编辑历史|noRecord:不记录(默认),latest:记录最新(签批),all:记录全部(会签)
+|autoBackFill|string|自动回填|noBackFill:不回填(默认),currentUserName:当前用户名,currentUserDepartment:当前用户部门|
+
+### 表单按钮
+
+> FormButtonVO
+
+|属性|类型|说明|参数值|
+|-|-|-|-|
+|btnAttachment|int|附件按钮|0:未激活,1:已激活|
+|btnForcedEnd|int|强制结束按钮|0:未激活,1:已激活|
+|btnPrint|int|打印按钮|0:未激活,1:已激活
+|btnCc|int|抄送按钮|0:未激活,1:已激活|
+|btnGoBack|int|回退按钮|0:未激活,1:已激活|
+|btnTransfer|int|转交按钮|0:未激活,1:已激活|
+|goBackUserTasks|List|[历史任务](./backend.md#历史任务)列表(按执行顺序),当 btnGoBack 激活时有效||
+
+### 历史任务
+
+> GoBackUserTaskVO
+
+|属性|类型|说明|参数值|
+|-|-|-|-|
+|nodeId|string|节点ID||
+|taskName|string|任务名称||
\ No newline at end of file
diff --git a/docs/standard/backend.md b/docs/standard/backend.md
new file mode 100644
index 0000000..d079d5e
--- /dev/null
+++ b/docs/standard/backend.md
@@ -0,0 +1,2 @@
+# 后端规范
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..fbde846
--- /dev/null
+++ b/package.json
@@ -0,0 +1,13 @@
+{
+ "devDependencies": {
+ "@vuepress/plugin-active-header-links": "^1.9.7",
+ "vuepress": "^2.0.0-beta.43"
+ },
+ "scripts": {
+ "docs:dev": "vuepress dev docs",
+ "docs:build": "vuepress build docs"
+ },
+ "dependencies": {
+ "@vuepress/theme-default": "^2.0.0-beta.45"
+ }
+}