houyaf 3 years ago
parent
commit
40f264b260

+ 4 - 4
src/api/system/docApi.ts

@@ -6,7 +6,7 @@ import apiRequest from '@/network/http/apiRequest'
  */
 export function getDirByList() {
   return apiRequest({
-    url: '/doc/dir/',
+    url: '/doc/dir',
     method: 'GET',
   })
 }
@@ -16,7 +16,7 @@ export function getDirByList() {
  * @returns
  * @param dirId
  */
-export function getDir(dirId: number) {
+export function getDirById(dirId: number) {
   return apiRequest({
     url: `/doc/dir/${dirId}`,
     method: 'GET',
@@ -54,7 +54,7 @@ export function updateDir(data: any) {
  * @returns
  * @param dirId
  */
-export function deleteDir(dirId: number) {
+export function deleteDirById(dirId: number) {
   return apiRequest({
     url: `/doc/dir/${dirId}`,
     method: 'DELETE',
@@ -130,7 +130,7 @@ export function updateFile(data: any) {
  * @returns
  * @param fileId
  */
-export function deleteFile(fileId: number) {
+export function deleteFileById(fileId: number) {
   return apiRequest({
     url: `/doc/file/${fileId}`,
     method: 'DELETE',

+ 4 - 4
src/api/system/wfActivityDefApi.ts

@@ -31,9 +31,9 @@ export function getWfActivityDefByList(wfDefId: number) {
  * @param wfDefId
  * @param wfActivityDefId
  */
-export function getWfActivityDefById(wfDefId: number, wfActivityDefId: number) {
+export function getWfActivityDefById(wfDefId: number, activityDefId: number) {
   return apiRequest({
-    url: `/wf/activity/def/${wfDefId}/${wfActivityDefId}`,
+    url: `/wf/activity/def/${wfDefId}/${activityDefId}`,
     method: 'GET',
   })
 }
@@ -70,9 +70,9 @@ export function updateWfActivityDef(data: any) {
  * @param wfDefId
  * @param wfActivityDefId
  */
-export function deleteWfActivityDefById(wfDefId: number, wfActivityDefId: number) {
+export function deleteWfActivityDefById(wfDefId: number, activityDefId: number) {
   return apiRequest({
-    url: `/wf/activity/def/${wfDefId}/${wfActivityDefId}`,
+    url: `/wf/activity/def/${wfDefId}/${activityDefId}`,
     method: 'DELETE',
   })
 }

+ 5 - 5
src/router/modules/module.ts

@@ -104,7 +104,7 @@ const routes: RouteRecordRaw[] = [
       {
         path: 'index',
         name: 'article_index',
-        component: () => import('@/views/article/art/index.vue'),
+        component: () => import('@/views/article/index.vue'),
         meta: {
           title: '文章管理',
           sidebar: false,
@@ -115,9 +115,9 @@ const routes: RouteRecordRaw[] = [
   },
 
   {
-    path: '/doc',
+    path: '/components',
     component: Layout,
-    redirect: '/doc/index',
+    redirect: '/components/index',
     name: 'document',
     meta: {
       title: '企业网盘',
@@ -127,11 +127,11 @@ const routes: RouteRecordRaw[] = [
       {
         path: 'index',
         name: 'doc_index',
-        component: () => import('@/views/document/doc/index.vue'),
+        component: () => import('@/views/document/index.vue'),
         meta: {
           title: '文件管理',
           sidebar: false,
-          activeMenu: '/doc',
+          activeMenu: '/components',
         },
       },
     ],

+ 21 - 0
src/utils/tool.ts

@@ -808,3 +808,24 @@ export function ocTypeFilter(i: string | number) {
   ]
   return strs[i as unknown as keyof typeof strs]
 }
+
+export function wfInsStatusFilter(i: string | number) {
+  if (i >= 0) {
+    const strs = [
+      '进行中',
+      '已完成',
+    ]
+    return strs[i as unknown as keyof typeof strs]
+  }
+  else {
+    return '已撤销'
+  }
+}
+
+export function wfActivityInsStatusFilter(i: string | number) {
+  const strs = [
+    '未开始',
+    '已完成',
+  ]
+  return strs[i as unknown as keyof typeof strs]
+}

+ 0 - 27
src/views/article/art/index.vue

@@ -1,27 +0,0 @@
-<route lang="yaml">
-meta:
-  enabled: false
-</route>
-
-<script lang="ts" setup>
-const value = ref(true)
-const value1 = ref(true)
-const value2 = ref(true)
-const value3 = ref(false)
-</script>
-
-<template>
-  <div>
-    <page-header title="开关" />
-    <page-main title="基础用法" class="demo">
-      <el-switch v-model="value" active-color="#13ce66" inactive-color="#ff4949" />
-    </page-main>
-    <page-main title="文字描述" class="demo">
-      <el-switch v-model="value1" active-text="按月付费" inactive-text="按年付费" />
-    </page-main>
-    <page-main title="禁用状态" class="demo">
-      <el-switch v-model="value2" disabled style="margin-right: 10px;" />
-      <el-switch v-model="value3" disabled />
-    </page-main>
-  </div>
-</template>

+ 0 - 27
src/views/article/cat/index.vue

@@ -1,27 +0,0 @@
-<route lang="yaml">
-meta:
-  enabled: false
-</route>
-
-<script lang="ts" setup>
-const value = ref(true)
-const value1 = ref(true)
-const value2 = ref(true)
-const value3 = ref(false)
-</script>
-
-<template>
-  <div>
-    <page-header title="开关" />
-    <page-main title="基础用法" class="demo">
-      <el-switch v-model="value" active-color="#13ce66" inactive-color="#ff4949" />
-    </page-main>
-    <page-main title="文字描述" class="demo">
-      <el-switch v-model="value1" active-text="按月付费" inactive-text="按年付费" />
-    </page-main>
-    <page-main title="禁用状态" class="demo">
-      <el-switch v-model="value2" disabled style="margin-right: 10px;" />
-      <el-switch v-model="value3" disabled />
-    </page-main>
-  </div>
-</template>

+ 116 - 0
src/views/article/components/Art.vue

@@ -0,0 +1,116 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import { createArt, getArtById, updateArt } from '@/api/system/artApi'
+const emit = defineEmits(['success'])
+
+const formRef = ref<FormInstance>()
+const formRules = ref<FormRules>({
+  artTitle: [
+    { required: true, message: '请输入标题', trigger: 'blur' },
+  ],
+  artContent: [
+    { required: true, message: '请输入描述', trigger: 'blur' },
+  ],
+})
+
+const state = reactive({
+  title: '',
+  dialogVisible: false,
+  loading: false,
+  formData: {
+    artId: 0,
+    artTitle: '',
+    artContent: '',
+  },
+  search: {
+    artId: 0,
+  },
+  actionType: 1,
+})
+
+onMounted(() => {})
+
+const getData = async () => {
+  state.loading = true
+  const { data } = await getArtById(state.search.artId)
+  state.loading = false
+  state.formData = data
+}
+
+function onSubmit() {
+  formRef.value && formRef.value.validate(async (valid) => {
+    if (valid) {
+      if (state.actionType === 1) {
+        const { msg } = await createArt(state.formData)
+        ElMessage.success(msg)
+        state.dialogVisible = false
+        emit('success')
+      }
+      else {
+        const { msg } = await updateArt(state.formData)
+        ElMessage.success(msg)
+        state.dialogVisible = false
+        emit('success')
+      }
+    }
+  })
+}
+
+function onCancel() {
+  state.dialogVisible = false
+}
+
+function resetFormData() {
+  state.formData = {
+    artId: 0,
+    artTitle: '',
+    artContent: '',
+  }
+}
+
+defineExpose({
+  showAddModel() {
+    resetFormData()
+    state.title = '新增'
+    state.actionType = 1
+    state.dialogVisible = true
+  },
+  showEditModel(artId: number) {
+    resetFormData()
+    state.title = '编辑'
+    state.actionType = 2
+    state.dialogVisible = true
+    state.search.artId = artId
+    getData()
+  },
+})
+</script>
+
+<template>
+  <el-drawer v-model="state.dialogVisible" :title="state.title" width="600px" :close-on-click-modal="false" append-to-body destroy-on-close>
+    <el-form ref="formRef" :model="state.formData" :rules="formRules" label-width="120px" label-suffix=":">
+      <el-form-item label="名称" prop="artTitle">
+        <el-input v-model="state.formData.artTitle" placeholder="请输入标题" />
+      </el-form-item>
+
+      <el-form-item label="简要说明" prop="artCatDesc">
+        <el-input v-model="state.formData.artCatDesc" type="textarea" rows="10" placeholder="请输入简要说明" />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <el-button size="large" @click="onCancel">
+        取消
+      </el-button>
+      <el-button type="primary" size="large" @click="onSubmit">
+        保存
+      </el-button>
+    </template>
+  </el-drawer>
+</template>

+ 115 - 0
src/views/article/components/ArtCat.vue

@@ -0,0 +1,115 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import { createArtCat, getArtCatById, updateArtCat } from '@/api/system/artCatApi'
+const emit = defineEmits(['success'])
+
+const formRef = ref<FormInstance>()
+const formRules = ref<FormRules>({
+  artCatTitle: [
+    { required: true, message: '请输入标题', trigger: 'blur' },
+  ],
+  artCatDesc: [
+    { required: false, message: '请输入描述', trigger: 'blur' },
+  ],
+})
+
+const state = reactive({
+  title: '',
+  dialogVisible: false,
+  loading: false,
+  formData: {
+    artCatId: 0,
+    artCatTitle: '',
+    artCatDesc: '',
+  },
+  search: {
+    artCatId: 0,
+  },
+  actionType: 1,
+})
+
+onMounted(() => {})
+
+const getData = async () => {
+  state.loading = true
+  const { data } = await getArtCatById(state.search.artCatId)
+  state.loading = false
+  state.formData = data
+}
+
+function onSubmit() {
+  formRef.value && formRef.value.validate(async (valid) => {
+    if (valid) {
+      if (state.actionType === 1) {
+        const { msg } = await createArtCat(state.formData)
+        ElMessage.success(msg)
+        state.dialogVisible = false
+        emit('success')
+      }
+      else {
+        const { msg } = await updateArtCat(state.formData)
+        ElMessage.success(msg)
+        state.dialogVisible = false
+        emit('success')
+      }
+    }
+  })
+}
+
+function onCancel() {
+  state.dialogVisible = false
+}
+
+function resetFormData() {
+  state.formData = {
+    artCatId: 0,
+    artCatTitle: '',
+    artCatDesc: '',
+  }
+}
+
+defineExpose({
+  showAddModel() {
+    resetFormData()
+    state.title = '新增'
+    state.actionType = 1
+    state.dialogVisible = true
+  },
+  showEditModel(roleId: number) {
+    resetFormData()
+    state.title = '编辑'
+    state.actionType = 2
+    state.dialogVisible = true
+    getData(roleId)
+  },
+})
+</script>
+
+<template>
+  <el-drawer v-model="state.dialogVisible" :title="state.title" width="600px" :close-on-click-modal="false" append-to-body destroy-on-close>
+    <el-form ref="formRef" :model="state.formData" :rules="formRules" label-width="120px" label-suffix=":">
+      <el-form-item label="名称" prop="artCatTitle">
+        <el-input v-model="state.formData.artCatTitle" placeholder="请输入标题" />
+      </el-form-item>
+
+      <el-form-item label="简要说明" prop="artCatDesc">
+        <el-input v-model="state.formData.artCatDesc" type="textarea" rows="10" placeholder="请输入简要说明" />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <el-button size="large" @click="onCancel">
+        取消
+      </el-button>
+      <el-button type="primary" size="large" @click="onSubmit">
+        保存
+      </el-button>
+    </template>
+  </el-drawer>
+</template>

+ 206 - 0
src/views/article/components/ArtCatTree.vue

@@ -0,0 +1,206 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { deleteArtCatById, getArtCatByList } from '@/api/system/artCatApi'
+const emit = defineEmits(['onItemSelect'])
+
+interface TreeNode {
+  label: string
+  isLeaf: boolean
+  artCatId: number
+  children?: TreeNode[]
+}
+
+interface Object {
+  artCatId: number
+  artCatTitle: string
+  artCatDesc: string
+}
+
+const state = reactive({
+  loading: false,
+  keywords: '',
+  treeData: [] as TreeNode[],
+  curArtCatId: 0,
+  dataList: [] as Object[],
+  dialogState: 1,
+  search: {
+    keywords: '',
+  },
+})
+
+const leftTreeRef = ref()
+
+const getArtCatData = async () => {
+  const { data } = await getArtCatByList(state.search)
+  const children: TreeNode[] = []
+  data.forEach((item: any) => {
+    return children.push({
+      label: item.artCatTitle,
+      artCatId: item.artCatId,
+      isLeaf: true,
+    })
+  })
+  state.treeData = children
+
+  if (data && data.length > 0) {
+    state.curArtCatId = data[0].artCatId
+    await nextTick(() => {
+      leftTreeRef.value.setCurrentKey(state.curArtCatId)
+      emit('onItemSelect', state.curArtCatId)
+    })
+  }
+}
+
+onMounted(() => {
+  getArtCatData()
+})
+
+// 模型选择
+const onTreeNodeClick = (node: TreeNode) => {
+  if (node.isLeaf) {
+    state.curArtCatId = node.artCatId
+    emit('onItemSelect', state.curArtCatId)
+  }
+}
+
+const artCatRef = ref()
+// 新增
+const onArtCatAdd = () => {
+  artCatRef.value.showAddModel()
+}
+
+// 编辑
+const onArtCatEdit = () => {
+  const artCatId = state.curArtCatId
+  if (artCatId > 0) {
+    artCatRef.value.showEditModel(artCatId)
+  }
+}
+
+// 删除
+const onArtCatDelete = (row: any) => {
+  const { artCatId, artCatTitle } = row
+  ElMessageBox.confirm(`确认删除「${artCatTitle}」吗?`, '确认信息').then(async () => {
+    const { msg } = await deleteArtCatById(artCatId)
+    ElMessage.success(msg)
+    await getArtCatData()
+  })
+}
+</script>
+
+<template>
+  <el-scrollbar class="tree">
+    <ElTree ref="leftTreeRef" :data="state.treeData" default-expand-all node-key="wfDefId" @node-click="onTreeNodeClick">
+      <template #default="{ node, data }">
+        <div class="custom-tree-node">
+          <div class="label" :title="node.label">
+            <el-icon v-if="data.isLeaf" size="16px" style="margin-right: 5px;">
+              <svg-icon name="ep:connection" />
+            </el-icon>
+            <div class="text">
+              {{ data.label }}
+            </div>
+          </div>
+          <div class="actions">
+            <el-button-group>
+              <el-button type="primary" plain size="small" @click.stop="onArtCatAdd()">
+                <template #icon>
+                  <el-icon>
+                    <svg-icon name="i-ep:plus" />
+                  </el-icon>
+                </template>
+              </el-button>
+
+              <el-button type="info" plain size="small" @click.stop="onArtCatEdit()">
+                <template #icon>
+                  <el-icon>
+                    <svg-icon name="i-ep:edit" />
+                  </el-icon>
+                </template>
+              </el-button>
+
+              <el-button type="danger" plain size="small" @click.stop="onArtCatDelete(data)">
+                <template #icon>
+                  <el-icon>
+                    <svg-icon name="i-ep:delete" />
+                  </el-icon>
+                </template>
+              </el-button>
+            </el-button-group>
+          </div>
+        </div>
+      </template>
+    </ElTree>
+  </el-scrollbar>
+</template>
+
+<style lang="scss" scoped>
+.btns {
+  display: inline-flex;
+  width: 100%;
+  margin-bottom: var(--container-padding);
+
+  .add {
+    width: 100%;
+  }
+}
+
+.tree {
+  flex: 1;
+  overflow-y: auto;
+
+  .el-tree {
+    .el-tree-node__content {
+      height: 40px;
+    }
+
+    .is-current > .el-tree-node__content {
+      background-color: var(--el-color-primary-light-9);
+    }
+
+    .custom-tree-node {
+      flex: 1;
+      position: relative;
+      width: 0;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+
+      .label {
+        display: inline-flex;
+        align-items: center;
+        width: calc(100% - 10px);
+        color: var(--el-text-color-primary);
+
+        .text {
+          @include text-overflow;
+        }
+      }
+
+      &:hover {
+        .actions {
+          display: block;
+        }
+      }
+
+      .actions {
+        display: none;
+        position: absolute;
+        right: 10px;
+        top: 50%;
+        transform: translateY(-50%);
+
+        .el-button {
+          padding: 5px 8px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 141 - 0
src/views/article/components/ArtList.vue

@@ -0,0 +1,141 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import { ElMessage, ElMessageBox } from 'element-plus'
+import ArtComponent from './Art.vue'
+import { deleteArtById, getArtByPage } from '@/api/system/artApi'
+
+const state = reactive({
+  loading: false,
+  dataList: [],
+  search: {
+    artCatId: -1,
+    keywords: '',
+  },
+})
+
+// 获取数据
+const getArtData = async () => {
+  if (state.search.artCatId > 0) {
+    state.loading = true
+    const { data } = await getArtByPage(state.search)
+    state.loading = false
+    state.dataList = data
+    state.loading = false
+  }
+}
+
+const artTableRef = ref()
+const artDefRef = ref()
+
+// 查询
+const onSearch = () => {
+  getArtData()
+}
+
+// 新增
+const onArtAdd = () => {
+  artDefRef.value.showAddModel()
+}
+
+// 查看
+const onArtView = (row: any) => {
+  artDefRef.value.showAddModel()
+}
+
+// 修改
+const onArtEdit = (row: any) => {
+  artDefRef.value.showEditModel(row.artId)
+}
+
+// 删除
+const onArtDelete = (row: any) => {
+  const { artId, artTitle } = row
+  ElMessageBox.confirm(`确认删除「${artTitle}」吗?`, '确认信息').then(async () => {
+    const { msg } = await deleteArtById(artId)
+    ElMessage.success(msg)
+    await getArtData()
+  })
+}
+
+defineExpose({
+  loadData(artCatId: number) {
+    state.search.artCatId = artCatId
+    getArtData()
+  },
+})
+</script>
+
+<template>
+  <div class="container">
+    <tool-bar>
+      <template #left>
+        <span />
+      </template>
+
+      <template #right>
+        <el-input v-model="state.search.keywords" placeholder="请输入名称,支持模糊查询" style="padding-right: 10px;" clearable />
+        <el-button type="primary" @click="onSearch">
+          <template #icon>
+            <el-icon>
+              <svg-icon name="i-ep:search" />
+            </el-icon>
+          </template>
+        </el-button>
+
+        <el-button type="primary" @click="onArtAdd">
+          新增
+        </el-button>
+      </template>
+    </tool-bar>
+
+    <el-table ref="artTableRef" v-loading="state.loading" class="list-table" size="small" :data="state.dataList" border stripe highlight-current-row>
+      <el-table-column type="selection" width="50" align="center" />
+      <el-table-column type="index" align="center" label="序号" width="70" />
+
+      <el-table-column prop="artTitle" width="250px" label="标题" align="center" header-align="center">
+        <template #default="scope">
+          <span>{{ scope.row.artTitle }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column prop="artCatTitle" width="150px" label="分类" align="center" header-align="center">
+        <template #default="scope">
+          <span>{{ scope.row.artCatTitle }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column prop="editorName" width="150px" label="发布人" align="center" header-align="center">
+        <template #default="scope">
+          <span>{{ scope.row.editorName }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column prop="issuedAt" label="发布时间" align="left" header-align="center">
+        <template #default="scope">
+          <span>{{ scope.row.issuedAt }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column label="操作" width="150" align="center" fixed="right">
+        <template #default="scope">
+          <div>
+            <el-button type="primary" size="small" plain @click="onArtView(scope.row)">
+              查看
+            </el-button>
+            <el-button type="primary" size="small" plain @click="onArtEdit(scope.row)">
+              编辑
+            </el-button>
+            <el-button v-if="!scope.row.isFixed" type="danger" size="small" plain @click="onArtDelete(scope.row)">
+              删除
+            </el-button>
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+    <ArtComponent ref="artDefRef" @success="getArtData" />
+  </div>
+</template>

+ 47 - 0
src/views/article/index.vue

@@ -0,0 +1,47 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import LeftCatTreeComponent from './components/ArtCatTree.vue'
+import ArtListComponent from './components/ArtList.vue'
+const artCatTreeRef = ref()
+const artListRef = ref()
+
+const onItemSelect = (artCatId: number) => {
+  artListRef.value.loadData(artCatId)
+}
+</script>
+
+<template>
+  <div class="absolute-container">
+    <tool-header title="企业智库" />
+
+    <div class="page-main">
+      <LayoutContainer left-side-width="250px" hide-left-side-toggle>
+        <template #leftSide>
+          <LeftCatTreeComponent ref="artCatTreeRef" @onItemSelect="onItemSelect" />
+        </template>
+
+        <ArtListComponent ref="artListRef" />
+      </LayoutContainer>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.absolute-container {
+  :deep(.left-side) {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+  }
+}
+</style>

+ 0 - 27
src/views/document/cat/index.vue

@@ -1,27 +0,0 @@
-<route lang="yaml">
-meta:
-  enabled: false
-</route>
-
-<script lang="ts" setup>
-const value = ref(true)
-const value1 = ref(true)
-const value2 = ref(true)
-const value3 = ref(false)
-</script>
-
-<template>
-  <div>
-    <page-header title="开关" />
-    <page-main title="基础用法" class="demo">
-      <el-switch v-model="value" active-color="#13ce66" inactive-color="#ff4949" />
-    </page-main>
-    <page-main title="文字描述" class="demo">
-      <el-switch v-model="value1" active-text="按月付费" inactive-text="按年付费" />
-    </page-main>
-    <page-main title="禁用状态" class="demo">
-      <el-switch v-model="value2" disabled style="margin-right: 10px;" />
-      <el-switch v-model="value3" disabled />
-    </page-main>
-  </div>
-</template>

+ 110 - 0
src/views/document/components/Dir.vue

@@ -0,0 +1,110 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import { createDir, getDirById, updateDir } from '@/api/system/docApi'
+const emit = defineEmits(['success'])
+
+const formRef = ref<FormInstance>()
+const formRules = ref<FormRules>({
+  dirTitle: [
+    { required: true, message: '请输入标题', trigger: 'blur' },
+  ],
+})
+
+const state = reactive({
+  title: '',
+  dialogVisible: false,
+  loading: false,
+  formData: {
+    dirId: 0,
+    dirTitle: '',
+    parentId: 0,
+  },
+  search: {
+    dirId: 0,
+  },
+  actionType: 1,
+})
+
+onMounted(() => {})
+
+const getData = async () => {
+  state.loading = true
+  const { data } = await getDirById(state.search.dirId)
+  state.loading = false
+  state.formData = data
+}
+
+function onSubmit() {
+  formRef.value && formRef.value.validate(async (valid) => {
+    if (valid) {
+      if (state.actionType === 1) {
+        const { msg } = await createDir(state.formData)
+        ElMessage.success(msg)
+        state.dialogVisible = false
+        emit('success')
+      }
+      else {
+        const { msg } = await updateDir(state.formData)
+        ElMessage.success(msg)
+        state.dialogVisible = false
+        emit('success')
+      }
+    }
+  })
+}
+
+function onCancel() {
+  state.dialogVisible = false
+}
+
+function resetFormData() {
+  state.formData = {
+    dirId: 0,
+    dirTitle: '',
+    parentId: 0,
+  }
+}
+
+defineExpose({
+  showAddModel(parentId: number) {
+    resetFormData()
+    state.title = '新增'
+    state.actionType = 1
+    state.dialogVisible = true
+    state.formData.parentId = parentId
+  },
+  showEditModel(dirId: number) {
+    resetFormData()
+    state.title = '编辑'
+    state.actionType = 2
+    state.dialogVisible = true
+    state.search.dirId = dirId
+    getData()
+  },
+})
+</script>
+
+<template>
+  <el-drawer v-model="state.dialogVisible" :title="state.title" width="600px" :close-on-click-modal="false" append-to-body destroy-on-close>
+    <el-form ref="formRef" :model="state.formData" :rules="formRules" label-width="120px" label-suffix=":">
+      <el-form-item label="目录名称" prop="dirTitle">
+        <el-input v-model="state.formData.dirTitle" placeholder="请输入目录名称" />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <el-button size="large" @click="onCancel">
+        取消
+      </el-button>
+      <el-button type="primary" size="large" @click="onSubmit">
+        保存
+      </el-button>
+    </template>
+  </el-drawer>
+</template>

+ 225 - 0
src/views/document/components/DirTree.vue

@@ -0,0 +1,225 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import { ElMessage, ElMessageBox } from 'element-plus'
+import DirComponent from './Dir.vue'
+import { deleteDirById, getDirByList } from '@/api/system/docApi'
+const emit = defineEmits(['onItemSelect'])
+
+interface TreeNode {
+  label: string
+  isLeaf: boolean
+  dirId: number
+  children?: TreeNode[]
+}
+
+interface Object {
+  dirId: number
+  dirTitle: string
+  dirDesc: string
+}
+
+const state = reactive({
+  loading: false,
+  keywords: '',
+  treeData: [] as TreeNode[],
+  curDirId: 0,
+  dataList: [] as Object[],
+  dialogState: 1,
+  search: {
+    keywords: '',
+  },
+})
+
+const leftTreeRef = ref()
+
+const getDirData = async () => {
+  const { data } = await getDirByList()
+  const children: TreeNode[] = []
+  data.forEach((item: any) => {
+    return children.push({
+      label: item.dirTitle,
+      dirId: item.dirId,
+      isLeaf: true,
+    })
+  })
+  state.treeData = children
+
+  if (data && data.length > 0) {
+    state.curDirId = data[0].dirId
+    await nextTick(() => {
+      leftTreeRef.value.setCurrentKey(state.curDirId)
+      emit('onItemSelect', state.curDirId)
+    })
+  }
+}
+
+onMounted(() => {
+  getDirData()
+})
+
+// 模型选择
+const onTreeNodeClick = (node: TreeNode) => {
+  if (node.isLeaf) {
+    state.curDirId = node.dirId
+    emit('onItemSelect', state.curDirId)
+  }
+}
+
+const dirRef = ref()
+// 新增
+const onDirAdd = () => {
+  dirRef.value.showAddModel()
+}
+
+// 编辑
+const onDirEdit = () => {
+  const dirId = state.curDirId
+  if (dirId > 0) {
+    dirRef.value.showEditModel(dirId)
+  }
+}
+
+// 删除
+const onDirDelete = (row: any) => {
+  const { dirId, dirTitle } = row
+  ElMessageBox.confirm(`确认删除「${dirTitle}」吗?`, '确认信息').then(async () => {
+    const { msg } = await deleteDirById(dirId)
+    ElMessage.success(msg)
+    await getDirData()
+  })
+}
+</script>
+
+<template>
+  <el-scrollbar class="tree">
+    <el-button-group class="btns">
+      <el-button type="primary" class="add" @click="onDirAdd()">
+        <template #icon>
+          <el-icon>
+            <svg-icon name="i-ep:plus" />
+          </el-icon>
+        </template>
+        文件夹
+      </el-button>
+      <el-button @click="getDirData">
+        <template #icon>
+          <el-icon>
+            <svg-icon name="i-ep:refresh" />
+          </el-icon>
+        </template>
+      </el-button>
+    </el-button-group>
+    <ElTree ref="leftTreeRef" :data="state.treeData" default-expand-all node-key="wfDefId" @node-click="onTreeNodeClick">
+      <template #default="{ node, data }">
+        <div class="custom-tree-node">
+          <div class="label" :title="node.label">
+            <el-icon v-if="data.isLeaf" size="16px" style="margin-right: 5px;">
+              <svg-icon name="ep:folder" />
+            </el-icon>
+            <div class="text">
+              {{ data.label }}
+            </div>
+          </div>
+          <div class="actions">
+            <el-button-group>
+              <el-button type="primary" plain size="small" @click.stop="onDirAdd()">
+                <template #icon>
+                  <el-icon>
+                    <svg-icon name="i-ep:plus" />
+                  </el-icon>
+                </template>
+              </el-button>
+
+              <el-button type="info" plain size="small" @click.stop="onDirEdit()">
+                <template #icon>
+                  <el-icon>
+                    <svg-icon name="i-ep:edit" />
+                  </el-icon>
+                </template>
+              </el-button>
+
+              <el-button type="danger" plain size="small" @click.stop="onDirDelete(data)">
+                <template #icon>
+                  <el-icon>
+                    <svg-icon name="i-ep:delete" />
+                  </el-icon>
+                </template>
+              </el-button>
+            </el-button-group>
+          </div>
+        </div>
+      </template>
+    </ElTree>
+    <DirComponent ref="dirRef" @success="getDirData" />
+  </el-scrollbar>
+</template>
+
+<style lang="scss" scoped>
+.btns {
+  display: inline-flex;
+  width: 100%;
+  margin-bottom: var(--container-padding);
+
+  .add {
+    width: 100%;
+  }
+}
+
+.tree {
+  flex: 1;
+  overflow-y: auto;
+
+  .el-tree {
+    .el-tree-node__content {
+      height: 40px;
+    }
+
+    .is-current > .el-tree-node__content {
+      background-color: var(--el-color-primary-light-9);
+    }
+
+    .custom-tree-node {
+      flex: 1;
+      position: relative;
+      width: 0;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+
+      .label {
+        display: inline-flex;
+        align-items: center;
+        width: calc(100% - 10px);
+        color: var(--el-text-color-primary);
+
+        .text {
+          @include text-overflow;
+        }
+      }
+
+      &:hover {
+        .actions {
+          display: block;
+        }
+      }
+
+      .actions {
+        display: none;
+        position: absolute;
+        right: 10px;
+        top: 50%;
+        transform: translateY(-50%);
+
+        .el-button {
+          padding: 5px 8px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 148 - 0
src/views/document/components/FileList.vue

@@ -0,0 +1,148 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { deleteFileById, getFileByPage } from '@/api/system/docApi'
+import { uploadFile } from '@/api/system/uploadApi'
+
+const state = reactive({
+  listLoading: false,
+  dataList: [],
+  search: {
+    dirId: -1,
+    keywords: '',
+  },
+})
+
+// 获取数据
+const getData = async () => {
+  if (state.search.dirId > 0) {
+    state.listLoading = true
+    const { data } = await getFileByPage(state.search)
+    state.listLoading = false
+    state.dataList = data
+  }
+}
+
+const fileTableRef = ref()
+
+// 查询
+const onSearch = () => {
+  getData()
+}
+
+// 下载
+const onDownload = (row: any) => {
+
+}
+
+// 删除
+const onDelete = (row: any) => {
+  const { fileId, fileTitle } = row
+  ElMessageBox.confirm(`确认删除「${fileTitle}」吗?`, '确认信息').then(async () => {
+    const { msg } = await deleteFileById(fileId)
+    ElMessage.success(msg)
+    await getData()
+  })
+}
+
+function beforeFileUpload(file: any) {
+  const isLt5M = file.size / 1024 / 1024 < 5
+  if (!isLt5M) {
+    ElMessage.error('上传文件大小不能超过 5MB!')
+  }
+  return isLt5M
+}
+
+// 上传文件
+const onUpload = async (param: any) => {
+  const formData = new FormData()
+  formData.append('uploadfile', param.file)
+  if (state.search.dirId) {
+    formData.append('dirId', state.search.dirId.toString())
+  }
+  await uploadFile(formData)
+  await getData()
+}
+
+defineExpose({
+  loadData(dirId: number) {
+    state.search.dirId = dirId
+    getData()
+  },
+})
+</script>
+
+<template>
+  <div class="container">
+    <tool-bar>
+      <template #left>
+        <span />
+      </template>
+
+      <template #right>
+        <el-input v-model="state.search.keywords" placeholder="请输入名称,支持模糊查询" clearable />
+        <el-button type="primary" style="padding-right: 10px;" @click="onSearch">
+          <template #icon>
+            <el-icon>
+              <svg-icon name="i-ep:search" />
+            </el-icon>
+          </template>
+        </el-button>
+
+        <el-upload
+          action
+          multiple
+          :http-request="onUpload"
+          :before-upload="beforeFileUpload"
+          :show-file-list="false"
+        >
+          <el-button type="primary" style="padding-left: 10px;">
+            上传
+          </el-button>
+        </el-upload>
+      </template>
+    </tool-bar>
+
+    <el-table ref="fileTableRef" v-loading="state.loading" class="list-table" size="small" :data="state.dataList" border stripe highlight-current-row>
+      <el-table-column type="index" align="center" label="序号" width="70" />
+
+      <el-table-column prop="fileTitle" label="文件名称" align="center" header-align="center">
+        <template #default="scope">
+          <el-link target="_blank" :href="scope.row.fileUrl" dowload>
+            <i class="el-icon-document" />
+            {{ scope.row.fileTitle }}
+          </el-link>
+        </template>
+      </el-table-column>
+
+      <el-table-column prop="uploaderName" width="160" label="上传人" header-align="center" align="center">
+        <template #default="scope">
+          <span>{{ scope.row.uploaderName }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column prop="uploadedAt" width="180" label="上传时间" header-align="center" align="center">
+        <template #default="scope">
+          <span><i class="el-icon-time" /> {{ scope.row.uploadedAt }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column label="操作" width="150" align="center" fixed="right">
+        <template #default="scope">
+          <div>
+            <el-button type="primary" size="small" plain @click="onDownload(scope.row)">
+              下载
+            </el-button>
+            <el-button v-if="!scope.row.isFixed" type="danger" size="small" plain @click="onDelete(scope.row)">
+              删除
+            </el-button>
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>

+ 0 - 27
src/views/document/doc/index.vue

@@ -1,27 +0,0 @@
-<route lang="yaml">
-meta:
-  enabled: false
-</route>
-
-<script lang="ts" setup>
-const value = ref(true)
-const value1 = ref(true)
-const value2 = ref(true)
-const value3 = ref(false)
-</script>
-
-<template>
-  <div>
-    <page-header title="开关" />
-    <page-main title="基础用法" class="demo">
-      <el-switch v-model="value" active-color="#13ce66" inactive-color="#ff4949" />
-    </page-main>
-    <page-main title="文字描述" class="demo">
-      <el-switch v-model="value1" active-text="按月付费" inactive-text="按年付费" />
-    </page-main>
-    <page-main title="禁用状态" class="demo">
-      <el-switch v-model="value2" disabled style="margin-right: 10px;" />
-      <el-switch v-model="value3" disabled />
-    </page-main>
-  </div>
-</template>

+ 47 - 0
src/views/document/index.vue

@@ -0,0 +1,47 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import DirTreeComponent from './components/DirTree.vue'
+import FileListComponent from './components/FileList.vue'
+const dirTreeRef = ref()
+const fileListRef = ref()
+
+const onItemSelect = (dirId: number) => {
+  fileListRef.value.loadData(dirId)
+}
+</script>
+
+<template>
+  <div class="absolute-container">
+    <tool-header title="企业智库" />
+
+    <div class="page-main">
+      <LayoutContainer left-side-width="250px" hide-left-side-toggle>
+        <template #leftSide>
+          <DirTreeComponent ref="dirTreeRef" @onItemSelect="onItemSelect" />
+        </template>
+
+        <FileListComponent ref="fileListRef" />
+      </LayoutContainer>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.absolute-container {
+  :deep(.left-side) {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+  }
+}
+</style>

+ 170 - 0
src/views/job/job/components/Job.vue

@@ -0,0 +1,170 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import { createJob, getJobById, updateJob } from '@/api/system/jobApi'
+import JobClassSelector from '@/views/job/job/components/JobClassSelector.vue'
+const emit = defineEmits(['success'])
+
+const formRef = ref<FormInstance>()
+const formRules = ref<FormRules>({
+  positionName: [
+    { required: true, message: '请输入标题', trigger: 'blur' },
+  ],
+  positionDesc: [
+    { required: false, message: '请输入描述', trigger: 'blur' },
+  ],
+})
+
+const state = reactive({
+  title: '',
+  dialogVisible: false,
+  loading: false,
+  formData: {
+    jobId: '',
+    jobTitle: '',
+    jobCode: '',
+    jobType: 1,
+    jobClass: '',
+    jobCron: '',
+    cycleTitle: '',
+    cyclePeriod: 1,
+    cycleUnit: 1,
+    jobDesc: '',
+  },
+  actionType: 1,
+})
+
+onMounted(() => {})
+
+const getData = async (jobId: number) => {
+  state.loading = true
+  const { data } = await getJobById(jobId)
+  state.loading = false
+  state.formData = data
+}
+
+function onSubmit() {
+  formRef.value && formRef.value.validate(async (valid) => {
+    if (valid) {
+      if (state.actionType === 1) {
+        const { msg } = await createJob(state.formData)
+        ElMessage.success(msg)
+        state.dialogVisible = false
+        emit('success')
+      }
+      else {
+        const { msg } = await updateJob(state.formData)
+        ElMessage.success(msg)
+        state.dialogVisible = false
+        emit('success')
+      }
+    }
+  })
+}
+
+function onCancel() {
+  state.dialogVisible = false
+}
+
+function resetFormData() {
+  state.formData = {
+    jobId: '',
+    jobTitle: '',
+    jobCode: '',
+    jobType: 1,
+    jobClass: '',
+    jobCron: '',
+    cycleTitle: '',
+    cyclePeriod: 1,
+    cycleUnit: 1,
+    jobDesc: '',
+  }
+}
+
+defineExpose({
+  showAddModel() {
+    resetFormData()
+    state.title = '新增岗位'
+    state.actionType = 1
+    state.dialogVisible = true
+  },
+  showEditModel(positionId: number) {
+    resetFormData()
+    state.title = '编辑岗位'
+    state.actionType = 2
+    state.dialogVisible = true
+    getData(positionId)
+  },
+
+})
+</script>
+
+<template>
+  <el-drawer v-model="state.dialogVisible" :title="state.title" width="600px" :close-on-click-modal="false" append-to-body destroy-on-close>
+    <el-form ref="formRef" :model="state.formData" :rules="formRules" label-width="120px" label-suffix=":">
+      <el-form-item label="Job名称">
+        <el-input v-model="state.formData.jobTitle" placeholder="输入Job名称" />
+      </el-form-item>
+
+      <el-form-item label="Code">
+        <el-input v-model="state.formData.jobCode" placeholder="Job Code" />
+      </el-form-item>
+
+      <el-form-item label="Class">
+        <JobClassSelector v-model="state.formData.jobClassId" placeholder="Job Class" />
+      </el-form-item>
+
+      <el-form-item label="类型">
+        <el-radio-group v-model="state.formData.jobType">
+          <el-radio :label="1">
+            Cron任务
+          </el-radio>
+          <el-radio :label="2">
+            循环任务
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+
+      <el-form-item label="频次说明">
+        <el-input v-model="state.formData.cycleTitle" placeholder="频次说明" />
+      </el-form-item>
+
+      <el-form-item v-if="state.formData.jobType === 1" label="Cron表达式">
+        <el-input v-model="state.formData.jobCron" placeholder="Class" />
+      </el-form-item>
+
+      <el-form-item v-if="state.formData.jobType === 2" label="周期">
+        <el-input v-model="state.formData.cyclePeriod" placeholder="Cycle Period" />
+        <el-select v-model="state.formData.cycleUnit">
+          <el-option value="1">
+            小时
+          </el-option>
+          <el-option value="2">
+            分钟
+          </el-option>
+          <el-option value="3">
+            秒
+          </el-option>
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="说明">
+        <el-input v-model="state.formData.jobDesc" placeholder="输入说明" />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <el-button size="large" @click="onCancel">
+        取消
+      </el-button>
+      <el-button type="primary" size="large" @click="onSubmit">
+        保存
+      </el-button>
+    </template>
+  </el-drawer>
+</template>

+ 47 - 0
src/views/job/job/components/JobClassSelector.vue

@@ -0,0 +1,47 @@
+<script lang="ts" setup name="JobClassSelector">
+import { getJobClassByList } from '@/api/system/jobClassApi'
+const props = withDefaults(defineProps<{
+  modelValue: number
+}>(), {
+  modelValue: 0,
+})
+const emit = defineEmits(['update:modelValue'])
+interface JobClass {
+  jobClassId: number
+  jobClassTitle: string
+}
+const jobClassId = computed({
+  get: () => {
+    return props.modelValue
+  },
+  set: (val) => {
+    emit('update:modelValue', val)
+  },
+})
+const state = reactive({
+  loading: false,
+  dataList: [] as JobClass[],
+})
+
+const getData = async () => {
+  state.loading = true
+  const { data } = await getJobClassByList({})
+  state.loading = false
+  state.dataList = data
+}
+onMounted(() => {
+  getData ()
+})
+</script>
+
+<template>
+  <el-select v-model="jobClassId" v-loading="state.loading" style="width: 100%" filterable placeholder="请选择" value-key="jobClassId">
+    <el-option key="0" label="请选择" :value="0" disabled />
+    <el-option
+      v-for="item in state.dataList"
+      :key="item.jobClassId"
+      :label="item.jobClassTitle"
+      :value="item.jobClassId"
+    />
+  </el-select>
+</template>

+ 166 - 21
src/views/job/job/index.vue

@@ -1,27 +1,172 @@
-<route lang="yaml">
-meta:
-  enabled: false
-</route>
-
-<script lang="ts" setup>
-const value = ref(true)
-const value1 = ref(true)
-const value2 = ref(true)
-const value3 = ref(false)
+<script lang="ts" setup name="GroupCatIndex">
+import { ElMessage, ElMessageBox } from 'element-plus'
+import JobComponent from './components/Job.vue'
+import { deleteJobById, getJobByList } from '@/api/system/jobApi'
+
+const state = reactive({
+  loading: false,
+  // 表格是否自适应高度
+  tableAutoHeight: false,
+  // 搜索
+  search: {
+    keywords: '',
+  },
+  // 列表数据
+  dataList: [],
+})
+
+const jobRef = ref()
+
+const getData = async () => {
+  state.loading = true
+  const { data } = await getJobByList(state.search)
+  state.dataList = data
+  state.loading = false
+}
+
+onMounted(() => {
+  getData()
+})
+
+function onJobAdd() {
+  jobRef.value.showAddModel()
+}
+
+function onJobEdit(row: any) {
+  const groupCatId = row.groupCatId
+  jobRef.value.showEditModel(groupCatId)
+}
+
+function onJobDel(row: any) {
+  const { jobId, jobTitle } = row
+  ElMessageBox.confirm(`确认删除「${jobTitle}」吗?`, '确认信息').then(async () => {
+    const { msg } = await deleteJobById(jobId)
+    ElMessage.success(msg)
+    await getData()
+  }).catch(() => {})
+}
+
+const onChangeStatus = (status: number, row: any) => {
+  window.console.log(status)
+}
 </script>
 
 <template>
-  <div>
-    <page-header title="开关" />
-    <page-main title="基础用法" class="demo">
-      <el-switch v-model="value" active-color="#13ce66" inactive-color="#ff4949" />
-    </page-main>
-    <page-main title="文字描述" class="demo">
-      <el-switch v-model="value1" active-text="按月付费" inactive-text="按年付费" />
-    </page-main>
-    <page-main title="禁用状态" class="demo">
-      <el-switch v-model="value2" disabled style="margin-right: 10px;" />
-      <el-switch v-model="value3" disabled />
+  <div :class="{ 'absolute-container': state.tableAutoHeight }">
+    <page-header title="系统作业">
+      <template #content>
+        <div>
+          系统支持设定定时器作业
+        </div>
+      </template>
+
+      <el-button-group>
+        <el-button type="primary" size="large" @click="onJobAdd">
+          <template #icon>
+            <el-icon>
+              <svg-icon name="ep:plus" />
+            </el-icon>
+          </template>
+          新增
+        </el-button>
+      </el-button-group>
+    </page-header>
+
+    <page-main>
+      <el-table v-loading="state.loading" class="list-table" :data="state.dataList" border stripe highlight-current-row height="100%">
+        <el-table-column type="index" label="序号" width="60" header-align="center" align="center" />
+
+        <el-table-column label="JOB名称" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.jobTitle }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="类型" align="center" width="90">
+          <template #default="scope">
+            <span v-if="scope.row.jobType === 1">Cron任务</span>
+            <span v-else> 循环任务 </span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="频次" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.cycleTitle }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="JOB Class" align="center" width="220">
+          <template #default="scope">
+            <span>{{ scope.row.jobClass }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="参数" align="center" width="220">
+          <template #default="scope">
+            <span v-if="scope.row.jobType === 1">{{ scope.row.jobCron }}</span>
+            <span v-else>{{ scope.row.cyclePeriod }} {{ scope.row.cycleUnit }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="说明" header-align="left" align="left">
+          <template #default="scope">
+            <span>{{ scope.row.jobDesc }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column align="center" label="状态" width="150">
+          <template #default="scope">
+            <div>
+              <el-switch
+                v-model="scope.row.status"
+                active-text="启动"
+                inactive-text="停止"
+                :active-value="1"
+                :inactive-value="0"
+                @change="(status) => onChangeStatus(status, scope.row)"
+              />
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" width="250" align="center" fixed="right">
+          <template #default="scope">
+            <el-button type="primary" size="small" plain @click="onJobEdit(scope.row)">
+              编辑
+            </el-button>
+            <el-button v-if="!scope.row.isFixed" type="danger" size="small" plain @click="onJobDel(scope.row)">
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
     </page-main>
+    <JobComponent ref="jobRef" @success="getData" />
   </div>
 </template>
+
+<style lang="scss" scoped>
+.absolute-container {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+
+  .page-header {
+    margin-bottom: 0;
+  }
+
+  .page-main {
+    // 让 page-main 的高度自适应
+    flex: 1;
+    overflow: auto;
+    display: flex;
+    flex-direction: column;
+
+    .search-container {
+      margin-bottom: 0;
+    }
+  }
+}
+</style>

+ 123 - 0
src/views/job/job_class/components/JobClass.vue

@@ -0,0 +1,123 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import { createJobClass, getJobClassById, updateJobClass } from '@/api/system/jobClassApi'
+const emit = defineEmits(['success'])
+
+const formRef = ref<FormInstance>()
+const formRules = ref<FormRules>({
+  jobClass: [
+    { required: true, message: '请输入标题', trigger: 'blur' },
+  ],
+  positionDesc: [
+    { required: false, message: '请输入描述', trigger: 'blur' },
+  ],
+})
+
+const state = reactive({
+  title: '',
+  dialogVisible: false,
+  loading: false,
+  formData: {
+    jobClassId: 0,
+    jobClassTitle: '',
+    jobClass: '',
+    jobClassDesc: '',
+  },
+  search: {
+    jobClassId: 0,
+  },
+  actionType: 1,
+})
+
+onMounted(() => {})
+
+const getData = async () => {
+  state.loading = true
+  const { data } = await getJobClassById(state.search.jobClassId)
+  state.loading = false
+  state.formData = data
+}
+
+function onSubmit() {
+  formRef.value && formRef.value.validate(async (valid) => {
+    if (valid) {
+      if (state.actionType === 1) {
+        const { msg } = await createJobClass(state.formData)
+        ElMessage.success(msg)
+        state.dialogVisible = false
+        emit('success')
+      }
+      else {
+        const { msg } = await updateJobClass(state.formData)
+        ElMessage.success(msg)
+        state.dialogVisible = false
+        emit('success')
+      }
+    }
+  })
+}
+
+function onCancel() {
+  state.dialogVisible = false
+}
+
+function resetFormData() {
+  state.formData = {
+    jobClassId: 0,
+    jobClassTitle: '',
+    jobClass: '',
+    jobClassDesc: '',
+  }
+}
+
+defineExpose({
+  showAddModel() {
+    resetFormData()
+    state.title = '新增'
+    state.actionType = 1
+    state.dialogVisible = true
+  },
+  showEditModel(jobClassId: number) {
+    resetFormData()
+    state.title = '编辑'
+    state.actionType = 2
+    state.dialogVisible = true
+    state.search.jobClassId = jobClassId
+    getData()
+  },
+
+})
+</script>
+
+<template>
+  <el-drawer v-model="state.dialogVisible" :title="state.title" width="600px" :close-on-click-modal="false" append-to-body destroy-on-close>
+    <el-form ref="formRef" :model="state.formData" :rules="formRules" label-width="120px" label-suffix=":">
+      <el-form-item label="Class 名称">
+        <el-input v-model="state.formData.jobClassTitle" placeholder="输入Job名称" />
+      </el-form-item>
+
+      <el-form-item label="Class">
+        <el-input v-model="state.formData.jobClass" placeholder="Class" />
+      </el-form-item>
+
+      <el-form-item label="说明">
+        <el-input v-model="state.formData.jobClassDesc" placeholder="输入说明" />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <el-button size="large" @click="onCancel">
+        取消
+      </el-button>
+      <el-button type="primary" size="large" @click="onSubmit">
+        保存
+      </el-button>
+    </template>
+  </el-drawer>
+</template>

+ 127 - 21
src/views/job/job_class/index.vue

@@ -1,27 +1,133 @@
-<route lang="yaml">
-meta:
-  enabled: false
-</route>
-
-<script lang="ts" setup>
-const value = ref(true)
-const value1 = ref(true)
-const value2 = ref(true)
-const value3 = ref(false)
+<script lang="ts" setup name="GroupCatIndex">
+import { ElMessage, ElMessageBox } from 'element-plus'
+import JobComponent from './components/JobClass.vue'
+import { deleteJobClassById, getJobClassByList } from '@/api/system/jobClassApi'
+
+const state = reactive({
+  loading: false,
+  // 表格是否自适应高度
+  tableAutoHeight: false,
+  // 搜索
+  search: {
+    keywords: '',
+  },
+  // 列表数据
+  dataList: [],
+})
+
+const jobRef = ref()
+
+const getData = async () => {
+  state.loading = true
+  const { data } = await getJobClassByList(state.search)
+  state.dataList = data
+  state.loading = false
+}
+
+onMounted(() => {
+  getData()
+})
+
+function onJobAdd() {
+  jobRef.value.showAddModel()
+}
+
+function onJobClassEdit(row: any) {
+  const jobClassId = row.jobClassId
+  jobRef.value.showEditModel(jobClassId)
+}
+
+function onJobClassDel(row: any) {
+  const { jobClassId, jobClassTitle } = row
+  ElMessageBox.confirm(`确认删除「${jobClassTitle}」吗?`, '确认信息').then(async () => {
+    const { msg } = await deleteJobClassById(jobClassId)
+    ElMessage.success(msg)
+    await getData()
+  }).catch(() => {})
+}
 </script>
 
 <template>
-  <div>
-    <page-header title="开关" />
-    <page-main title="基础用法" class="demo">
-      <el-switch v-model="value" active-color="#13ce66" inactive-color="#ff4949" />
-    </page-main>
-    <page-main title="文字描述" class="demo">
-      <el-switch v-model="value1" active-text="按月付费" inactive-text="按年付费" />
-    </page-main>
-    <page-main title="禁用状态" class="demo">
-      <el-switch v-model="value2" disabled style="margin-right: 10px;" />
-      <el-switch v-model="value3" disabled />
+  <div :class="{ 'absolute-container': state.tableAutoHeight }">
+    <page-header title="系统作业">
+      <template #content>
+        <div>
+          系统支持设定定时器作业
+        </div>
+      </template>
+
+      <el-button-group>
+        <el-button type="primary" size="large" @click="onJobAdd">
+          <template #icon>
+            <el-icon>
+              <svg-icon name="ep:plus" />
+            </el-icon>
+          </template>
+          新增
+        </el-button>
+      </el-button-group>
+    </page-header>
+
+    <page-main>
+      <el-table v-loading="state.loading" class="list-table" :data="state.dataList" border stripe highlight-current-row height="100%">
+        <el-table-column type="index" label="序号" width="60" header-align="center" align="center" />
+
+        <el-table-column label="类名称" align="center">
+          <template #default="scope">
+            <span>{{ scope.row.jobClassTitle }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="JOB Class" align="center" width="300">
+          <template #default="scope">
+            <span>{{ scope.row.jobClass }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="说明" header-align="left" align="left">
+          <template #default="scope">
+            <span>{{ scope.row.jobClassDesc }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" width="250" align="center" fixed="right">
+          <template #default="scope">
+            <el-button type="primary" size="small" plain @click="onJobClassEdit(scope.row)">
+              编辑
+            </el-button>
+            <el-button v-if="!scope.row.isFixed" type="danger" size="small" plain @click="onJobClassDel(scope.row)">
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
     </page-main>
+    <JobComponent ref="jobRef" @success="getData" />
   </div>
 </template>
+
+<style lang="scss" scoped>
+.absolute-container {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+
+  .page-header {
+    margin-bottom: 0;
+  }
+
+  .page-main {
+    // 让 page-main 的高度自适应
+    flex: 1;
+    overflow: auto;
+    display: flex;
+    flex-direction: column;
+
+    .search-container {
+      margin-bottom: 0;
+    }
+  }
+}
+</style>

+ 154 - 0
src/views/workflow/components/WfDefList.vue

@@ -0,0 +1,154 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import { getWfDefByList } from '@/api/system/wfDefApi'
+const emit = defineEmits(['onItemSelect'])
+
+interface TreeNode {
+  label: string
+  isLeaf: boolean
+  wfDefId: number
+  children?: TreeNode[]
+}
+
+interface Object {
+  wfDefId: number
+  wfDefTitle: string
+  wfDefDesc: string
+}
+
+const state = reactive({
+  loading: false,
+  keywords: '',
+  treeData: [] as TreeNode[],
+  curWfDefId: 0,
+  dataList: [] as Object[],
+  dialogState: 1,
+  search: {
+    keywords: '',
+  },
+})
+
+const leftTreeRef = ref()
+
+const getWfDefData = async () => {
+  const { data } = await getWfDefByList({})
+  const children: TreeNode[] = []
+  data.forEach((item: any) => {
+    return children.push({
+      label: item.wfDefTitle,
+      wfDefId: item.wfDefId,
+      isLeaf: true,
+    })
+  })
+  state.treeData = children
+
+  if (data && data.length > 0) {
+    state.curWfDefId = data[0].wfDefId
+    await nextTick(() => {
+      leftTreeRef.value.setCurrentKey(state.curWfDefId)
+      emit('onItemSelect', state.curWfDefId)
+    })
+  }
+}
+
+onMounted(() => {
+  getWfDefData()
+})
+
+// 模型选择
+const onTreeNodeClick = (node: TreeNode) => {
+  if (node.isLeaf) {
+    state.curWfDefId = node.wfDefId
+    emit('onItemSelect', state.curWfDefId)
+  }
+}
+</script>
+
+<template>
+  <el-scrollbar class="tree">
+    <ElTree ref="leftTreeRef" :data="state.treeData" default-expand-all node-key="wfDefId" @node-click="onTreeNodeClick">
+      <template #default="{ node, data }">
+        <div class="custom-tree-node">
+          <div class="label" :title="node.label">
+            <el-icon v-if="data.isLeaf" size="16px" style="margin-right: 5px;">
+              <svg-icon name="ep:office-building" />
+            </el-icon>
+            <div class="text">
+              {{ data.label }}
+            </div>
+          </div>
+        </div>
+      </template>
+    </ElTree>
+  </el-scrollbar>
+</template>
+
+<style lang="scss" scoped>
+.btns {
+  display: inline-flex;
+  width: 100%;
+  margin-bottom: var(--container-padding);
+
+  .add {
+    width: 100%;
+  }
+}
+
+.tree {
+  flex: 1;
+  overflow-y: auto;
+
+  .el-tree {
+    .el-tree-node__content {
+      height: 40px;
+    }
+
+    .is-current > .el-tree-node__content {
+      background-color: var(--el-color-primary-light-9);
+    }
+
+    .custom-tree-node {
+      flex: 1;
+      position: relative;
+      width: 0;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+
+      .label {
+        display: inline-flex;
+        align-items: center;
+        width: calc(100% - 10px);
+        color: var(--el-text-color-primary);
+
+        .text {
+          @include text-overflow;
+        }
+      }
+
+      &:hover {
+        .actions {
+          display: block;
+        }
+      }
+
+      .actions {
+        display: none;
+        position: absolute;
+        right: 10px;
+        top: 50%;
+        transform: translateY(-50%);
+
+        .el-button {
+          padding: 5px 8px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 2 - 2
src/views/workflow/def/components/WfActivityDef.vue

@@ -38,7 +38,7 @@ const state = reactive({
     activityDefTitle: '',
     activityCode: '',
     formCode: '',
-    ractivityDefDesc: '',
+    activityDefDesc: '',
   },
   search: {
     wfDefId: 0,
@@ -87,7 +87,7 @@ function resetFormData() {
     activityDefTitle: '',
     activityCode: '',
     formCode: '',
-    ractivityDefDesc: '',
+    activityDefDesc: '',
   }
 }
 

+ 14 - 7
src/views/workflow/def/components/WfActivityDefList.vue

@@ -5,25 +5,24 @@ meta:
 
 <script lang="ts" setup>
 import { ElMessage, ElMessageBox } from 'element-plus'
+import WfActivityDefComponent from './WfActivityDef.vue'
 import { deleteWfActivityDefById, getWfActivityDefByList } from '@/api/system/wfActivityDefApi'
 
 const state = reactive({
   loading: false,
-  activityDefList: [],
+  dataList: [],
   search: {
     wfDefId: -1,
     keywords: '',
   },
 })
 
-const activityDefTreeRef = ref()
-
 // 查询帐号列表
 const getWfActivityDefData = async () => {
   state.loading = true
   const { data } = await getWfActivityDefByList(state.search.wfDefId)
   state.loading = false
-  state.activityDefList = data.data
+  state.dataList = data
   state.loading = false
 }
 
@@ -40,9 +39,9 @@ const onActivityDefAdd = () => {
   activityDefRef.value.showAddModel()
 }
 
-// 修改账号
+// 修改
 const onActivityDefEdit = (row: any) => {
-  activityDefRef.value.showEditModel(row.groupId, row.accountId)
+  activityDefRef.value.showEditModel(row.wfDefId, row.activityDefId)
 }
 
 // 删除
@@ -54,6 +53,13 @@ const onActivityDefDelete = (row: any) => {
     await getWfActivityDefData()
   })
 }
+
+defineExpose({
+  loadData(wfDefId: number) {
+    state.search.wfDefId = wfDefId
+    getWfActivityDefData()
+  },
+})
 </script>
 
 <template>
@@ -79,7 +85,7 @@ const onActivityDefDelete = (row: any) => {
       </template>
     </tool-bar>
 
-    <el-table ref="activityDefTreeRef" v-loading="state.loading" class="list-table" :data="state.dataList" border stripe highlight-current-row>
+    <el-table ref="activityDefTableRef" v-loading="state.loading" class="list-table" size="small" :data="state.dataList" border stripe highlight-current-row>
       <el-table-column type="selection" width="50" align="center" />
       <el-table-column type="index" align="center" label="序号" width="70" />
 
@@ -120,5 +126,6 @@ const onActivityDefDelete = (row: any) => {
         </template>
       </el-table-column>
     </el-table>
+    <WfActivityDefComponent ref="activityDefRef" @success="getWfActivityDefData" />
   </div>
 </template>

+ 10 - 13
src/views/workflow/def/components/WfDefList.vue

@@ -6,6 +6,7 @@ meta:
 <script lang="ts" setup>
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { deleteWfDefById, getWfDefByList } from '@/api/system/wfDefApi'
+const emit = defineEmits(['onItemSelect'])
 
 interface TreeNode {
   label: string
@@ -44,19 +45,13 @@ const getWfDefData = async () => {
       isLeaf: true,
     })
   })
-  state.treeData = [
-    {
-      label: '工作流程',
-      isLeaf: false,
-      wfDefId: -1,
-      children,
-    },
-  ]
+  state.treeData = children
 
   if (data && data.length > 0) {
     state.curWfDefId = data[0].wfDefId
     await nextTick(() => {
-      leftTreeRef.value.setCurrentKey(data[0].wfDefId)
+      leftTreeRef.value.setCurrentKey(state.curWfDefId)
+      emit('onItemSelect', state.curWfDefId)
     })
   }
 }
@@ -69,6 +64,7 @@ onMounted(() => {
 const onTreeNodeClick = (node: TreeNode) => {
   if (node.isLeaf) {
     state.curWfDefId = node.wfDefId
+    emit('onItemSelect', state.curWfDefId)
   }
 }
 
@@ -79,7 +75,7 @@ const onWfDefAdd = () => {
 }
 
 // 编辑
-const onWfDefEdit = (row: { wfDefId: number }) => {
+const onWfDefEdit = () => {
   const wfDefId = state.curWfDefId
   if (wfDefId > 0) {
     wfDefRef.value.showEditModel(wfDefId)
@@ -104,13 +100,13 @@ const onWfDefDelete = (row: any) => {
         <div class="custom-tree-node">
           <div class="label" :title="node.label">
             <el-icon v-if="data.isLeaf" size="16px" style="margin-right: 5px;">
-              <svg-icon name="ep:office-building" />
+              <svg-icon name="ep:connection" />
             </el-icon>
             <div class="text">
               {{ data.label }}
             </div>
           </div>
-
+          <!--
           <div class="actions">
             <el-button-group>
               <el-button type="primary" plain size="small" @click.stop="onWfDefAdd()">
@@ -121,7 +117,7 @@ const onWfDefDelete = (row: any) => {
                 </template>
               </el-button>
 
-              <el-button type="info" plain size="small" @click.stop="onWfDefEdit(data)">
+              <el-button type="info" plain size="small" @click.stop="onWfDefEdit()">
                 <template #icon>
                   <el-icon>
                     <svg-icon name="i-ep:edit" />
@@ -138,6 +134,7 @@ const onWfDefDelete = (row: any) => {
               </el-button>
             </el-button-group>
           </div>
+          -->
         </div>
       </template>
     </ElTree>

+ 24 - 4
src/views/workflow/def/index.vue

@@ -6,8 +6,12 @@ meta:
 <script lang="ts" setup>
 import WfDefListComponent from './components/WfDefList.vue'
 import WfActivityDefListComponent from './components/WfActivityDefList.vue'
-const wDefListRef = ref()
-const wfActivityDefListRef = ref()
+const wfDefListRef = ref()
+const activityDefListRef = ref()
+
+const onItemSelect = (wfDefId: number) => {
+  activityDefListRef.value.loadData(wfDefId)
+}
 </script>
 
 <template>
@@ -17,11 +21,27 @@ const wfActivityDefListRef = ref()
     <div class="page-main">
       <LayoutContainer left-side-width="250px" hide-left-side-toggle>
         <template #leftSide>
-          <WfDefListComponent ref="wfDefListRef" />
+          <WfDefListComponent ref="wfDefListRef" @onItemSelect="onItemSelect" />
         </template>
 
-        <WfActivityDefListComponent ref="wfActivityDefListRef" />
+        <WfActivityDefListComponent ref="activityDefListRef" />
       </LayoutContainer>
     </div>
   </div>
 </template>
+
+<style lang="scss" scoped>
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.absolute-container {
+  :deep(.left-side) {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+  }
+}
+</style>

+ 134 - 0
src/views/workflow/ins/components/WfInsList.vue

@@ -0,0 +1,134 @@
+<route lang="yaml">
+meta:
+  enabled: false
+</route>
+
+<script lang="ts" setup>
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { deleteWfInsById, getWfInsByPage } from '@/api/system/wfInsApi'
+import { wfInsStatusFilter } from '@/utils/tool'
+
+const state = reactive({
+  loading: false,
+  dataList: [],
+  search: {
+    wfDefId: -1,
+    keywords: '',
+  },
+})
+
+// 查询帐号列表
+const getWfInsData = async () => {
+  state.loading = true
+  const { data } = await getWfInsByPage(state.search)
+  state.loading = false
+  state.dataList = data
+  state.loading = false
+}
+
+const wfInsTableRef = ref()
+
+// 查询
+const onSearch = () => {
+  getWfInsData()
+}
+
+// 修改
+const onWfInsDetail = (row: any) => {
+  // activityDefRef.value.showEditModel(row.wfDefId, row.activityDefId)
+}
+
+// 删除
+const onWfInsDelete = (row: any) => {
+  const { ocId, wfInsId, wfInsTitle } = row
+  ElMessageBox.confirm(`确认删除「${wfInsTitle}」吗?`, '确认信息').then(async () => {
+    const { msg } = await deleteWfInsById(ocId, wfInsId)
+    ElMessage.success(msg)
+    await getWfInsData()
+  })
+}
+
+defineExpose({
+  loadData(wfDefId: number) {
+    state.search.wfDefId = wfDefId
+    getWfInsData()
+  },
+})
+</script>
+
+<template>
+  <div class="container">
+    <tool-bar>
+      <template #left>
+        <span />
+      </template>
+
+      <template #right>
+        <el-input v-model="state.search.keywords" placeholder="请输入名称,支持模糊查询" style="padding-right: 10px;" clearable />
+        <el-button type="primary" @click="onSearch">
+          <template #icon>
+            <el-icon>
+              <svg-icon name="i-ep:search" />
+            </el-icon>
+          </template>
+        </el-button>
+      </template>
+    </tool-bar>
+
+    <el-table ref="wfInsTableRef" v-loading="state.loading" class="list-table" size="small" :data="state.dataList" border stripe highlight-current-row>
+      <el-table-column type="selection" width="50" align="center" />
+      <el-table-column type="index" align="center" label="序号" width="70" />
+
+      <el-table-column width="150px" label="单位名称" align="center" header-align="center">
+        <template #default="scope">
+          <span>{{ scope.row.ocName }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column align="center" header-align="center" label="实例名称">
+        <template #default="scope">
+          <span>{{ scope.row.wfInsTitle }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column align="center" header-align="center" label="开始时间">
+        <template #default="scope">
+          <span>{{ scope.row.beginTime }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column align="center" header-align="center" label="结束时间">
+        <template #default="scope">
+          <span>{{ scope.row.finishTime }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column width="150px" label="状态" align="center" header-align="center">
+        <template #default="scope">
+          <el-tag v-if="scope.row.status === 0" type="danger">
+            {{ wfInsStatusFilter(scope.row.status) }}
+          </el-tag>
+          <el-tag v-else-if="scope.row.status === 1" type="warning">
+            {{ wfInsStatusFilter(scope.row.status) }}
+          </el-tag>
+          <el-tag v-else type="info">
+            {{ wfInsStatusFilter(scope.row.status) }}
+          </el-tag>
+        </template>
+      </el-table-column>
+
+      <el-table-column label="操作" width="150" align="center" fixed="right">
+        <template #default="scope">
+          <div>
+            <el-button type="primary" size="small" plain @click="onWfInsDetail(scope.row)">
+              详情
+            </el-button>
+            <el-button v-if="!scope.row.isFixed" type="danger" size="small" plain @click="onWfInsDelete(scope.row)">
+              删除
+            </el-button>
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>

+ 36 - 16
src/views/workflow/ins/index.vue

@@ -4,24 +4,44 @@ meta:
 </route>
 
 <script lang="ts" setup>
-const value = ref(true)
-const value1 = ref(true)
-const value2 = ref(true)
-const value3 = ref(false)
+import WfDefListComponent from '../components/WfDefList.vue'
+import WfInsListComponent from './components/WfInsList.vue'
+const wfDefListRef = ref()
+const wfInsListRef = ref()
+
+const onItemSelect = (wfDefId: number) => {
+  wfInsListRef.value.loadData(wfDefId)
+}
 </script>
 
 <template>
-  <div>
-    <page-header title="开关" />
-    <page-main title="基础用法" class="demo">
-      <el-switch v-model="value" active-color="#13ce66" inactive-color="#ff4949" />
-    </page-main>
-    <page-main title="文字描述" class="demo">
-      <el-switch v-model="value1" active-text="按月付费" inactive-text="按年付费" />
-    </page-main>
-    <page-main title="禁用状态" class="demo">
-      <el-switch v-model="value2" disabled style="margin-right: 10px;" />
-      <el-switch v-model="value3" disabled />
-    </page-main>
+  <div class="absolute-container">
+    <tool-header title="客户分类" />
+
+    <div class="page-main">
+      <LayoutContainer left-side-width="250px" hide-left-side-toggle>
+        <template #leftSide>
+          <WfDefListComponent ref="wfDefListRef" @onItemSelect="onItemSelect" />
+        </template>
+
+        <WfInsListComponent ref="wfInsListRef" />
+      </LayoutContainer>
+    </div>
   </div>
 </template>
+
+<style lang="scss" scoped>
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.absolute-container {
+  :deep(.left-side) {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+  }
+}
+</style>