From ee6317e9060a4276767926dfd959ffab819a38e8 Mon Sep 17 00:00:00 2001
From: xingyu <xingyu4j@vip.qq.com>
Date: Thu, 28 Jul 2022 12:18:38 +0800
Subject: [PATCH] feat: bpm api

---
 yudao-ui-admin-vue3/src/api/bpm/form/index.ts |  48 ++++
 yudao-ui-admin-vue3/src/api/bpm/form/types.ts |   9 +
 .../src/api/bpm/leave/index.ts                |  18 ++
 .../src/api/bpm/leave/types.ts                |  10 +
 .../src/api/bpm/model/index.ts                |  36 +++
 .../src/api/bpm/model/types.ts                |  15 ++
 .../src/api/bpm/processInstance/index.ts      |  23 ++
 .../src/api/bpm/processInstance/types.ts      |  18 ++
 yudao-ui-admin-vue3/src/api/bpm/task/index.ts |  36 +++
 yudao-ui-admin-vue3/src/api/bpm/task/types.ts |   9 +
 .../src/api/bpm/taskAssignRule/index.ts       |  21 ++
 .../src/api/bpm/taskAssignRule/types.ts       |   9 +
 .../src/api/bpm/userGroup/index.ts            |  39 +++
 .../src/api/bpm/userGroup/types.ts            |   9 +
 .../src/api/system/user/index.ts              |   5 +
 .../src/views/bpm/definition/index.vue        |   7 -
 .../src/views/bpm/form/form.data.ts           |  61 +++++
 .../src/views/bpm/form/index.vue              | 170 ++++++++++++-
 .../src/views/bpm/group/group.data.ts         |  69 ++++++
 .../src/views/bpm/group/index.vue             | 230 +++++++++++++++++-
 20 files changed, 827 insertions(+), 15 deletions(-)
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/form/index.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/form/types.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/leave/index.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/leave/types.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/model/index.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/model/types.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/processInstance/index.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/processInstance/types.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/task/index.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/task/types.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/taskAssignRule/index.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/taskAssignRule/types.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/userGroup/index.ts
 create mode 100644 yudao-ui-admin-vue3/src/api/bpm/userGroup/types.ts
 delete mode 100644 yudao-ui-admin-vue3/src/views/bpm/definition/index.vue
 create mode 100644 yudao-ui-admin-vue3/src/views/bpm/form/form.data.ts
 create mode 100644 yudao-ui-admin-vue3/src/views/bpm/group/group.data.ts

diff --git a/yudao-ui-admin-vue3/src/api/bpm/form/index.ts b/yudao-ui-admin-vue3/src/api/bpm/form/index.ts
new file mode 100644
index 000000000..23f2fce0b
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/form/index.ts
@@ -0,0 +1,48 @@
+import { useAxios } from '@/hooks/web/useAxios'
+import { FormVO } from './types'
+const request = useAxios()
+
+// 创建工作流的表单定义
+export const createFormApi = async (data: FormVO) => {
+  return await request.post({
+    url: '/bpm/form/create',
+    data: data
+  })
+}
+
+// 更新工作流的表单定义
+export const updateFormApi = async (data: FormVO) => {
+  return await request.put({
+    url: '/bpm/form/update',
+    data: data
+  })
+}
+
+// 删除工作流的表单定义
+export const deleteFormApi = async (id: number) => {
+  return await request.delete({
+    url: '/bpm/form/delete?id=' + id
+  })
+}
+
+// 获得工作流的表单定义
+export const getFormApi = async (id: number) => {
+  return await request.get({
+    url: '/bpm/form/get?id=' + id
+  })
+}
+
+// 获得工作流的表单定义分页
+export const getFormPageApi = async (params) => {
+  return await request.get({
+    url: '/bpm/form/page',
+    params
+  })
+}
+
+// 获得动态表单的精简列表
+export const getSimpleFormsApi = async () => {
+  return await request.get({
+    url: '/bpm/form/list-all-simple'
+  })
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/form/types.ts b/yudao-ui-admin-vue3/src/api/bpm/form/types.ts
new file mode 100644
index 000000000..2bc8b6912
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/form/types.ts
@@ -0,0 +1,9 @@
+export type FormVO = {
+  id: number
+  name: string
+  conf: string
+  fields: string[]
+  status: number
+  remark: string
+  createTime: string
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/leave/index.ts b/yudao-ui-admin-vue3/src/api/bpm/leave/index.ts
new file mode 100644
index 000000000..7f266cdf4
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/leave/index.ts
@@ -0,0 +1,18 @@
+import { useAxios } from '@/hooks/web/useAxios'
+import { LeaveVO } from './types'
+const request = useAxios()
+
+// 创建请假申请
+export const createLeaveApi = async (data: LeaveVO) => {
+  return await request.post({ url: '/bpm/oa/leave/create', data: data })
+}
+
+// 获得请假申请
+export const getLeaveApi = async (id: number) => {
+  return await request.get({ url: '/bpm/oa/leave/get?id=' + id })
+}
+
+// 获得请假申请分页
+export const getLeavePageApi = async (params) => {
+  return await request.get({ url: '/bpm/oa/leave/page', params })
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/leave/types.ts b/yudao-ui-admin-vue3/src/api/bpm/leave/types.ts
new file mode 100644
index 000000000..60b4bf831
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/leave/types.ts
@@ -0,0 +1,10 @@
+export type LeaveVO = {
+  id: number
+  result: number
+  type: number
+  reason: string
+  processInstanceId: string
+  startTime: string
+  endTime: string
+  createTime: string
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/model/index.ts b/yudao-ui-admin-vue3/src/api/bpm/model/index.ts
new file mode 100644
index 000000000..cc577a7fc
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/model/index.ts
@@ -0,0 +1,36 @@
+import { useAxios } from '@/hooks/web/useAxios'
+import { ModelVO } from './types'
+const request = useAxios()
+
+export const getModelPage = async (params) => {
+  return await request.get({ url: '/bpm/model/page', params })
+}
+
+export const getModel = async (id: number) => {
+  return await request.get({ url: '/bpm/model/get?id=' + id })
+}
+
+export const updateModel = async (data: ModelVO) => {
+  return await request.put({ url: '/bpm/model/update', data: data })
+}
+
+// 任务状态修改
+export const updateModelState = async (id: number, state: string) => {
+  const data = {
+    id: id,
+    state: state
+  }
+  return await request.put({ url: '/bpm/model/update-state', data: data })
+}
+
+export const createModel = async (data: ModelVO) => {
+  return await request.post({ url: '/bpm/model/create', data: data })
+}
+
+export const deleteModel = async (id: number) => {
+  return await request.delete({ url: '/bpm/model/delete?id=' + id })
+}
+
+export const deployModel = async (id: number) => {
+  return await request.post({ url: '/bpm/model/deploy?id=' + id })
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/model/types.ts b/yudao-ui-admin-vue3/src/api/bpm/model/types.ts
new file mode 100644
index 000000000..96266fab3
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/model/types.ts
@@ -0,0 +1,15 @@
+export type ModelVO = {
+  id: number
+  formName: string
+  key: string
+  name: string
+  description: string
+  category: string
+  formType: number
+  formId: number
+  formCustomCreatePath: string
+  formCustomViewPath: string
+  status: number
+  remark: string
+  createTime: string
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/processInstance/index.ts b/yudao-ui-admin-vue3/src/api/bpm/processInstance/index.ts
new file mode 100644
index 000000000..b49dfa615
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/processInstance/index.ts
@@ -0,0 +1,23 @@
+import { useAxios } from '@/hooks/web/useAxios'
+import { ProcessInstanceVO } from './types'
+const request = useAxios()
+
+export const getMyProcessInstancePage = async (params) => {
+  return await request.get({ url: '/bpm/process-instance/my-page', params })
+}
+
+export const createProcessInstance = async (data: ProcessInstanceVO) => {
+  return await request.post({ url: '/bpm/process-instance/create', data: data })
+}
+
+export const cancelProcessInstance = async (id: number, reason: string) => {
+  const data = {
+    id: id,
+    reason: reason
+  }
+  return await request.delete({ url: '/bpm/process-instance/cancel', data: data })
+}
+
+export const getProcessInstance = async (id: number) => {
+  return await request.get({ url: '/bpm/process-instance/get?id=' + id })
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/processInstance/types.ts b/yudao-ui-admin-vue3/src/api/bpm/processInstance/types.ts
new file mode 100644
index 000000000..3ab506389
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/processInstance/types.ts
@@ -0,0 +1,18 @@
+export type task = {
+  id: string
+  name: string
+}
+export type ProcessInstanceVO = {
+  id: number
+  name: string
+  processDefinitionId: string
+  category: string
+  result: number
+  tasks: task[]
+  fields: string[]
+  status: number
+  remark: string
+  businessKey: string
+  createTime: string
+  endTime: string
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/task/index.ts b/yudao-ui-admin-vue3/src/api/bpm/task/index.ts
new file mode 100644
index 000000000..7967f5e37
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/task/index.ts
@@ -0,0 +1,36 @@
+import { useAxios } from '@/hooks/web/useAxios'
+
+const request = useAxios()
+
+export const getTodoTaskPage = async (params) => {
+  return await request.get({ url: '/bpm/task/todo-page', params })
+}
+
+export const getDoneTaskPage = async (params) => {
+  return await request.get({ url: '/bpm/task/done-page', params })
+}
+
+export const completeTask = async (data) => {
+  return await request.put({ url: '/bpm/task/complete', data: data })
+}
+
+export const approveTask = async (data) => {
+  return await request.put({ url: '/bpm/task/approve', data: data })
+}
+
+export const rejectTask = async (data) => {
+  return await request.put({ url: '/bpm/task/reject', data: data })
+}
+export const backTask = async (data) => {
+  return await request.put({ url: '/bpm/task/back', data: data })
+}
+
+export const updateTaskAssignee = async (data) => {
+  return await request.put({ url: '/bpm/task/update-assignee', data: data })
+}
+
+export const getTaskListByProcessInstanceId = async (processInstanceId) => {
+  return await request.get({
+    url: '/bpm/task/list-by-process-instance-id?processInstanceId=' + processInstanceId
+  })
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/task/types.ts b/yudao-ui-admin-vue3/src/api/bpm/task/types.ts
new file mode 100644
index 000000000..2bc8b6912
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/task/types.ts
@@ -0,0 +1,9 @@
+export type FormVO = {
+  id: number
+  name: string
+  conf: string
+  fields: string[]
+  status: number
+  remark: string
+  createTime: string
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/taskAssignRule/index.ts b/yudao-ui-admin-vue3/src/api/bpm/taskAssignRule/index.ts
new file mode 100644
index 000000000..f6105293d
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/taskAssignRule/index.ts
@@ -0,0 +1,21 @@
+import { useAxios } from '@/hooks/web/useAxios'
+import { TaskAssignVO } from './types'
+const request = useAxios()
+
+export const getTaskAssignRuleList = async (params) => {
+  return await request.get({ url: '/bpm/task-assign-rule/list', params })
+}
+
+export const createTaskAssignRule = async (data: TaskAssignVO) => {
+  return await request.post({
+    url: '/bpm/task-assign-rule/create',
+    data: data
+  })
+}
+
+export const updateTaskAssignRule = async (data: TaskAssignVO) => {
+  return await request.put({
+    url: '/bpm/task-assign-rule/update',
+    data: data
+  })
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/taskAssignRule/types.ts b/yudao-ui-admin-vue3/src/api/bpm/taskAssignRule/types.ts
new file mode 100644
index 000000000..e9340e7ea
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/taskAssignRule/types.ts
@@ -0,0 +1,9 @@
+export type TaskAssignVO = {
+  id: number
+  modelId: string
+  processDefinitionId: string
+  taskDefinitionKey: string
+  taskDefinitionName: string
+  options: string[]
+  type: number
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/userGroup/index.ts b/yudao-ui-admin-vue3/src/api/bpm/userGroup/index.ts
new file mode 100644
index 000000000..e7cd3f507
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/userGroup/index.ts
@@ -0,0 +1,39 @@
+import { useAxios } from '@/hooks/web/useAxios'
+import { UserGroupVO } from './types'
+const request = useAxios()
+
+// 创建用户组
+export const createUserGroupApi = async (data: UserGroupVO) => {
+  return await request.post({
+    url: '/bpm/user-group/create',
+    data: data
+  })
+}
+
+// 更新用户组
+export const updateUserGroupApi = async (data: UserGroupVO) => {
+  return await request.put({
+    url: '/bpm/user-group/update',
+    data: data
+  })
+}
+
+// 删除用户组
+export const deleteUserGroupApi = async (id: number) => {
+  return await request.delete({ url: '/bpm/user-group/delete?id=' + id })
+}
+
+// 获得用户组
+export const getUserGroupApi = async (id: number) => {
+  return await request.get({ url: '/bpm/user-group/get?id=' + id })
+}
+
+// 获得用户组分页
+export const getUserGroupPageApi = async (params) => {
+  return await request.get({ url: '/bpm/user-group/page', params })
+}
+
+// 获取用户组精简信息列表
+export const listSimpleUserGroupsApi = async () => {
+  return await request.get({ url: '/bpm/user-group/list-all-simple' })
+}
diff --git a/yudao-ui-admin-vue3/src/api/bpm/userGroup/types.ts b/yudao-ui-admin-vue3/src/api/bpm/userGroup/types.ts
new file mode 100644
index 000000000..d0d67ad43
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/bpm/userGroup/types.ts
@@ -0,0 +1,9 @@
+export type UserGroupVO = {
+  id: number
+  name: string
+  description: string
+  memberUserIds: number[]
+  status: number
+  remark: string
+  createTime: string
+}
diff --git a/yudao-ui-admin-vue3/src/api/system/user/index.ts b/yudao-ui-admin-vue3/src/api/system/user/index.ts
index f4a484eaf..5085d5c11 100644
--- a/yudao-ui-admin-vue3/src/api/system/user/index.ts
+++ b/yudao-ui-admin-vue3/src/api/system/user/index.ts
@@ -55,3 +55,8 @@ export const updateUserStatusApi = (id: number, status: number) => {
   }
   return request.put({ url: '/system/user/update-status', data: data })
 }
+
+// 获取用户精简信息列表
+export const getListSimpleUsersApi = () => {
+  return request.get({ url: '/system/user/list-all-simple' })
+}
diff --git a/yudao-ui-admin-vue3/src/views/bpm/definition/index.vue b/yudao-ui-admin-vue3/src/views/bpm/definition/index.vue
deleted file mode 100644
index b03cb75c8..000000000
--- a/yudao-ui-admin-vue3/src/views/bpm/definition/index.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-<script setup lang="ts"></script>
-
-<template>
-  <div>index</div>
-</template>
-
-<style scoped></style>
diff --git a/yudao-ui-admin-vue3/src/views/bpm/form/form.data.ts b/yudao-ui-admin-vue3/src/views/bpm/form/form.data.ts
new file mode 100644
index 000000000..32de7da57
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/views/bpm/form/form.data.ts
@@ -0,0 +1,61 @@
+import { reactive } from 'vue'
+import { useI18n } from '@/hooks/web/useI18n'
+import { required } from '@/utils/formRules'
+import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
+import { DICT_TYPE } from '@/utils/dict'
+const { t } = useI18n() // 国际化
+
+// 表单校验
+export const rules = reactive({
+  name: [required]
+})
+
+// CrudSchema
+const crudSchemas = reactive<CrudSchema[]>([
+  {
+    label: t('common.index'),
+    field: 'id',
+    type: 'index',
+    form: {
+      show: false
+    },
+    detail: {
+      show: false
+    }
+  },
+  {
+    label: '表单名',
+    field: 'name',
+    search: {
+      show: true
+    }
+  },
+  {
+    label: t('common.status'),
+    field: 'status',
+    dictType: DICT_TYPE.COMMON_STATUS
+  },
+  {
+    label: '备注',
+    field: 'remark'
+  },
+  {
+    label: t('common.createTime'),
+    field: 'createTime',
+    form: {
+      show: false
+    }
+  },
+  {
+    label: t('table.action'),
+    field: 'action',
+    width: '240px',
+    form: {
+      show: false
+    },
+    detail: {
+      show: false
+    }
+  }
+])
+export const { allSchemas } = useCrudSchemas(crudSchemas)
diff --git a/yudao-ui-admin-vue3/src/views/bpm/form/index.vue b/yudao-ui-admin-vue3/src/views/bpm/form/index.vue
index b03cb75c8..edd99c84b 100644
--- a/yudao-ui-admin-vue3/src/views/bpm/form/index.vue
+++ b/yudao-ui-admin-vue3/src/views/bpm/form/index.vue
@@ -1,7 +1,169 @@
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { ref, unref } from 'vue'
+import dayjs from 'dayjs'
+import { ElMessage } from 'element-plus'
+import { DICT_TYPE } from '@/utils/dict'
+import { useTable } from '@/hooks/web/useTable'
+import { useI18n } from '@/hooks/web/useI18n'
+import { FormExpose } from '@/components/Form'
+import type { FormVO } from '@/api/bpm/form/types'
+import { rules, allSchemas } from './form.data'
+import * as FormApi from '@/api/bpm/form'
+const { t } = useI18n() // 国际化
+
+// ========== 列表相关 ==========
+const { register, tableObject, methods } = useTable<FormVO>({
+  getListApi: FormApi.getFormPageApi,
+  delListApi: FormApi.deleteFormApi
+})
+const { getList, setSearchParams, delList } = methods
+
+// ========== CRUD 相关 ==========
+const actionLoading = ref(false) // 遮罩层
+const actionType = ref('') // 操作按钮的类型
+const dialogVisible = ref(false) // 是否显示弹出层
+const dialogTitle = ref('edit') // 弹出层标题
+const formRef = ref<FormExpose>() // 表单 Ref
+
+// 设置标题
+const setDialogTile = (type: string) => {
+  dialogTitle.value = t('action.' + type)
+  actionType.value = type
+  dialogVisible.value = true
+}
+
+// 新增操作
+const handleCreate = () => {
+  setDialogTile('create')
+  // 重置表单
+  unref(formRef)?.getElFormRef()?.resetFields()
+}
+
+// 修改操作
+const handleUpdate = async (row: FormVO) => {
+  setDialogTile('update')
+  // 设置数据
+  const res = await FormApi.getFormApi(row.id)
+  unref(formRef)?.setValues(res)
+}
+
+// 提交按钮
+const submitForm = async () => {
+  actionLoading.value = true
+  // 提交请求
+  try {
+    const data = unref(formRef)?.formModel as FormVO
+    if (actionType.value === 'create') {
+      await FormApi.createFormApi(data)
+      ElMessage.success(t('common.createSuccess'))
+    } else {
+      await FormApi.updateFormApi(data)
+      ElMessage.success(t('common.updateSuccess'))
+    }
+    // 操作成功,重新加载列表
+    dialogVisible.value = false
+    await getList()
+  } finally {
+    actionLoading.value = false
+  }
+}
+
+// 删除操作
+const handleDelete = (row: FormVO) => {
+  delList(row.id, false)
+}
+
+// ========== 详情相关 ==========
+const detailRef = ref() // 详情 Ref
+
+// 详情操作
+const handleDetail = async (row: FormVO) => {
+  // 设置数据
+  detailRef.value = row
+  setDialogTile('detail')
+}
+
+// ========== 初始化 ==========
+getList()
+</script>
 
 <template>
-  <div>index</div>
-</template>
+  <!-- 搜索工作区 -->
+  <ContentWrap>
+    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
+  </ContentWrap>
+  <ContentWrap>
+    <!-- 操作工具栏 -->
+    <div class="mb-10px">
+      <el-button type="primary" v-hasPermi="['bpm:form:create']" @click="handleCreate">
+        <Icon icon="ep:zoom-in" class="mr-5px" /> {{ t('action.add') }}
+      </el-button>
+    </div>
+    <!-- 列表 -->
+    <Table
+      :columns="allSchemas.tableColumns"
+      :selection="false"
+      :data="tableObject.tableList"
+      :loading="tableObject.loading"
+      :pagination="{
+        total: tableObject.total
+      }"
+      v-model:pageSize="tableObject.pageSize"
+      v-model:currentPage="tableObject.currentPage"
+      @register="register"
+    >
+      <template #status="{ row }">
+        <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
+      </template>
+      <template #createTime="{ row }">
+        <span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
+      </template>
+      <template #action="{ row }">
+        <el-button link type="primary" v-hasPermi="['bpm:form:update']" @click="handleUpdate(row)">
+          <Icon icon="ep:edit" class="mr-1px" /> {{ t('action.edit') }}
+        </el-button>
+        <el-button link type="primary" v-hasPermi="['bpm:form:update']" @click="handleDetail(row)">
+          <Icon icon="ep:view" class="mr-1px" /> {{ t('action.detail') }}
+        </el-button>
+        <el-button link type="primary" v-hasPermi="['bpm:form:delete']" @click="handleDelete(row)">
+          <Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }}
+        </el-button>
+      </template>
+    </Table>
+  </ContentWrap>
 
-<style scoped></style>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <!-- 对话框(添加 / 修改) -->
+    <Form
+      v-if="['create', 'update'].includes(actionType)"
+      :schema="allSchemas.formSchema"
+      :rules="rules"
+      ref="formRef"
+    />
+    <!-- 对话框(详情) -->
+    <Descriptions
+      v-if="actionType === 'detail'"
+      :schema="allSchemas.detailSchema"
+      :data="detailRef"
+    >
+      <template #status="{ row }">
+        <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
+      </template>
+      <template #createTime="{ row }">
+        <span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
+      </template>
+    </Descriptions>
+    <!-- 操作按钮 -->
+    <template #footer>
+      <el-button
+        v-if="['create', 'update'].includes(actionType)"
+        type="primary"
+        :loading="actionLoading"
+        @click="submitForm"
+      >
+        {{ t('action.save') }}
+      </el-button>
+      <el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
+    </template>
+  </Dialog>
+</template>
diff --git a/yudao-ui-admin-vue3/src/views/bpm/group/group.data.ts b/yudao-ui-admin-vue3/src/views/bpm/group/group.data.ts
new file mode 100644
index 000000000..83562b156
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/views/bpm/group/group.data.ts
@@ -0,0 +1,69 @@
+import { reactive } from 'vue'
+import { useI18n } from '@/hooks/web/useI18n'
+import { required } from '@/utils/formRules'
+import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
+import { DICT_TYPE } from '@/utils/dict'
+const { t } = useI18n() // 国际化
+
+// 表单校验
+export const rules = reactive({
+  name: [required]
+})
+
+// CrudSchema
+const crudSchemas = reactive<CrudSchema[]>([
+  {
+    label: t('common.index'),
+    field: 'id',
+    type: 'index',
+    form: {
+      show: false
+    },
+    detail: {
+      show: false
+    }
+  },
+  {
+    label: '组名',
+    field: 'name',
+    search: {
+      show: true
+    }
+  },
+  {
+    label: '成员',
+    field: 'memberUserIds'
+  },
+  {
+    label: '描述',
+    field: 'description'
+  },
+  {
+    label: t('common.status'),
+    field: 'status',
+    dictType: DICT_TYPE.COMMON_STATUS
+  },
+  {
+    label: '备注',
+    field: 'remark'
+  },
+  {
+    label: t('common.createTime'),
+    field: 'createTime',
+    form: {
+      show: false
+    }
+  },
+  {
+    label: t('table.action'),
+    field: 'action',
+    width: '240px',
+    form: {
+      show: false
+    },
+    detail: {
+      show: false
+    }
+  }
+])
+export const { allSchemas } = useCrudSchemas(crudSchemas)
diff --git a/yudao-ui-admin-vue3/src/views/bpm/group/index.vue b/yudao-ui-admin-vue3/src/views/bpm/group/index.vue
index b03cb75c8..20c9c7201 100644
--- a/yudao-ui-admin-vue3/src/views/bpm/group/index.vue
+++ b/yudao-ui-admin-vue3/src/views/bpm/group/index.vue
@@ -1,7 +1,229 @@
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { ref, unref, onMounted } from 'vue'
+import dayjs from 'dayjs'
+import { ElMessage, ElSelect, ElOption } from 'element-plus'
+import { DICT_TYPE } from '@/utils/dict'
+import { useTable } from '@/hooks/web/useTable'
+import { useI18n } from '@/hooks/web/useI18n'
+import { FormExpose } from '@/components/Form'
+import type { UserGroupVO } from '@/api/bpm/userGroup/types'
+import { rules, allSchemas } from './group.data'
+import * as UserGroupApi from '@/api/bpm/userGroup'
+import { getListSimpleUsersApi } from '@/api/system/user'
+import { UserVO } from '@/api/system/user/types'
+
+const { t } = useI18n() // 国际化
+
+// ========== 列表相关 ==========
+const { register, tableObject, methods } = useTable<UserGroupVO>({
+  getListApi: UserGroupApi.getUserGroupPageApi,
+  delListApi: UserGroupApi.deleteUserGroupApi
+})
+const { getList, setSearchParams, delList } = methods
+
+// ========== CRUD 相关 ==========
+const actionLoading = ref(false) // 遮罩层
+const actionType = ref('') // 操作按钮的类型
+const dialogVisible = ref(false) // 是否显示弹出层
+const dialogTitle = ref('edit') // 弹出层标题
+const formRef = ref<FormExpose>() // 表单 Ref
+
+// ========== 用户选择  ==========
+const userIds = ref<number[]>([])
+const userOptions = ref<UserVO[]>([])
+const getUserOptions = async () => {
+  const res = await getListSimpleUsersApi()
+  userOptions.value.push(...res)
+}
+
+// 设置标题
+const setDialogTile = (type: string) => {
+  dialogTitle.value = t('action.' + type)
+  actionType.value = type
+  dialogVisible.value = true
+}
+
+// 新增操作
+const handleCreate = () => {
+  setDialogTile('create')
+  userIds.value = []
+  // 重置表单
+  unref(formRef)?.getElFormRef()?.resetFields()
+}
+
+// 修改操作
+const handleUpdate = async (row: UserGroupVO) => {
+  setDialogTile('update')
+  // 设置数据
+  const res = await UserGroupApi.getUserGroupApi(row.id)
+  userIds.value = res.memberUserIds
+  unref(formRef)?.setValues(res)
+}
+
+// 提交按钮
+const submitForm = async () => {
+  actionLoading.value = true
+  // 提交请求
+  try {
+    const data = unref(formRef)?.formModel as UserGroupVO
+    data.memberUserIds = userIds.value
+    if (actionType.value === 'create') {
+      await UserGroupApi.createUserGroupApi(data)
+      ElMessage.success(t('common.createSuccess'))
+    } else {
+      await UserGroupApi.updateUserGroupApi(data)
+      ElMessage.success(t('common.updateSuccess'))
+    }
+    // 操作成功,重新加载列表
+    dialogVisible.value = false
+    await getList()
+  } finally {
+    actionLoading.value = false
+  }
+}
+
+// 删除操作
+const handleDelete = (row: UserGroupVO) => {
+  delList(row.id, false)
+}
+
+// 根据用户名获取用户真实名
+const getUserNickName = (userId: number) => {
+  for (const user of userOptions.value) {
+    if (user.id === userId) return user.nickname
+  }
+  return '未知(' + userId + ')'
+}
+// ========== 详情相关 ==========
+const detailRef = ref() // 详情 Ref
+
+// 详情操作
+const handleDetail = async (row: UserGroupVO) => {
+  // 设置数据
+  detailRef.value = row
+  setDialogTile('detail')
+}
+
+// ========== 初始化 ==========
+onMounted(async () => {
+  await getList()
+  await getUserOptions()
+})
+</script>
 
 <template>
-  <div>index</div>
-</template>
+  <!-- 搜索工作区 -->
+  <ContentWrap>
+    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
+  </ContentWrap>
+  <ContentWrap>
+    <!-- 操作工具栏 -->
+    <div class="mb-10px">
+      <el-button type="primary" v-hasPermi="['bpm:user-group:create']" @click="handleCreate">
+        <Icon icon="ep:zoom-in" class="mr-5px" /> {{ t('action.add') }}
+      </el-button>
+    </div>
+    <!-- 列表 -->
+    <Table
+      :columns="allSchemas.tableColumns"
+      :selection="false"
+      :data="tableObject.tableList"
+      :loading="tableObject.loading"
+      :pagination="{
+        total: tableObject.total
+      }"
+      v-model:pageSize="tableObject.pageSize"
+      v-model:currentPage="tableObject.currentPage"
+      @register="register"
+    >
+      <template #status="{ row }">
+        <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
+      </template>
+      <template #memberUserIds="{ row }">
+        <span v-for="userId in row.memberUserIds" :key="userId">
+          {{ getUserNickName(userId) + ' ' }}
+        </span>
+      </template>
+      <template #createTime="{ row }">
+        <span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
+      </template>
+      <template #action="{ row }">
+        <el-button
+          link
+          type="primary"
+          v-hasPermi="['bpm:user-group:update']"
+          @click="handleUpdate(row)"
+        >
+          <Icon icon="ep:edit" class="mr-1px" /> {{ t('action.edit') }}
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          v-hasPermi="['bpm:user-group:update']"
+          @click="handleDetail(row)"
+        >
+          <Icon icon="ep:view" class="mr-1px" /> {{ t('action.detail') }}
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          v-hasPermi="['bpm:user-group:delete']"
+          @click="handleDelete(row)"
+        >
+          <Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }}
+        </el-button>
+      </template>
+    </Table>
+  </ContentWrap>
 
-<style scoped></style>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <!-- 对话框(添加 / 修改) -->
+    <Form
+      v-if="['create', 'update'].includes(actionType)"
+      :schema="allSchemas.formSchema"
+      :rules="rules"
+      ref="formRef"
+    >
+      <template #memberUserIds>
+        <el-select v-model="userIds" multiple>
+          <el-option
+            v-for="item in userOptions"
+            :key="item.id"
+            :label="item.nickname"
+            :value="item.id"
+          />
+        </el-select>
+      </template>
+    </Form>
+    <!-- 对话框(详情) -->
+    <Descriptions
+      v-if="actionType === 'detail'"
+      :schema="allSchemas.detailSchema"
+      :data="detailRef"
+    >
+      <template #memberUserIds="{ row }">
+        <span v-for="userId in row.memberUserIds" :key="userId">
+          {{ getUserNickName(userId) + ' ' }}
+        </span>
+      </template>
+      <template #status="{ row }">
+        <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
+      </template>
+      <template #createTime="{ row }">
+        <span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
+      </template>
+    </Descriptions>
+    <!-- 操作按钮 -->
+    <template #footer>
+      <el-button
+        v-if="['create', 'update'].includes(actionType)"
+        type="primary"
+        :loading="actionLoading"
+        @click="submitForm"
+      >
+        {{ t('action.save') }}
+      </el-button>
+      <el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
+    </template>
+  </Dialog>
+</template>