kagg886 1 місяць тому
батько
коміт
da41e5bb23
1 змінених файлів з 396 додано та 330 видалено
  1. 396 330
      src/views/flow/flowCraft/index.vue

+ 396 - 330
src/views/flow/flowCraft/index.vue

@@ -1,401 +1,467 @@
 <script lang="ts" setup>
 import { ref, computed, onMounted, reactive, toRefs } from 'vue'
-import { asyncComputed } from '@vueuse/core'
 import { staticFlowForm, listFlowForm } from '/@/api/flow/flowForm'
-import { useStore } from '/@/store/index'
+import { useStore } from '/@/store'
+import { useLoading } from '/@/utils/loading-util'
 import AddFormData from '/@/views/flow/flowForm/list/component/addFormData.vue'
-import {
-  FlowFormTableColumns,
-  FlowFormTableDataState
-} from '/@/views/flow/flowForm/list/component/model'
+import { FlowFormTableColumns, FlowFormTableDataState } from '/@/views/flow/flowForm/list/component/model'
 
 const store = useStore()
 
 // 获取主题配置
 const getThemeConfig = computed(() => {
-  return store.state.themeConfig.themeConfig
+	return store.state.themeConfig.themeConfig
 })
 
 // 判断是否为深色模式
 const isDark = computed(() => {
-  return getThemeConfig.value.isIsDark
+	return getThemeConfig.value.isIsDark
 })
 
-// 顶部统计卡片数据
-const summaryCards = asyncComputed(async () => {
-	const {prepare,already,start} = await staticFlowForm() as unknown as {
+// 统计数据
+const summaryData = ref({
+	prepare: 0,
+	start: 0,
+	already: 0,
+})
+
+// 使用 useLoading hook 获取统计数据
+const { loading: summaryLoading, doLoading: getSummaryData } = useLoading(async () => {
+	const { prepare, already, start } = (await staticFlowForm()) as unknown as {
 		prepare: number
 		already: number
 		start: number
 	}
-	return [
-		{
-			title: '我的待办',
-			number: prepare,
-			description: '待处理任务',
-			icon: 'ele-Warning',
-			color: '#f56c6c'
-		},
-		{
-			title: '我发起的',
-			number: start,
-			description: '发起的工单',
-			icon: 'ele-Document',
-			color: '#409eff'
-		},
-		{
-			title: '我处理的',
-			number: already,
-			description: '已处理任务',
-			icon: 'ele-User',
-			color: '#67c23a'
-		}
-	]
+
+	summaryData.value.prepare = prepare
+	summaryData.value.start = start
+	summaryData.value.already = already
 })
 
 // 统计卡片背景色计算属性
 const getSummaryCardBgColor = (index: number) => {
-  if (isDark.value) {
-    // 深色模式下的背景色
-    const darkColors = ['#2a1f1f', '#1f2a3a', '#1f2a1f']
-    return darkColors[index] || '#1f1f1f'
-  } else {
-    // 浅色模式下的背景色
-    const lightColors = ['#fef0f0', '#f0f9ff', '#f0f9ff']
-    return lightColors[index] || '#f4f4f5'
-  }
+	if (isDark.value) {
+		// 深色模式下的背景色
+		const darkColors = ['#2a1f1f', '#1f2a3a', '#1f2a1f']
+		return darkColors[index] || '#1f1f1f'
+	} else {
+		// 浅色模式下的背景色
+		const lightColors = ['#fef0f0', '#f0f9ff', '#f0f9ff']
+		return lightColors[index] || '#f4f4f5'
+	}
 }
 
 // 表单数据相关
 const addFormDataRef = ref()
-const loading = ref(false)
 
 const state = reactive<FlowFormTableDataState>({
-  ids: [],
-  tableData: {
-    data: [],
-    total: 0,
-    loading: false,
-    param: {
-      pageNum: 1,
-      pageSize: 6,
-      name: undefined,
-      status: 1,
-      isPub: true,
-      createdAt: undefined,
-      dateRange: []
-    },
-  },
-});
+	ids: [],
+	tableData: {
+		data: [],
+		total: 0,
+		loading: false,
+		param: {
+			pageNum: 1,
+			pageSize: 6,
+			name: undefined,
+			status: 1,
+			isPub: true,
+			createdAt: undefined,
+			dateRange: [],
+		},
+	},
+})
 
 const { tableData } = toRefs(state)
 
+// 使用 useLoading hook 获取表单列表
+const { loading: formListLoading, doLoading: flowFormList } = useLoading(async () => {
+	const res: { list: any[]; total: number } = (await listFlowForm(state.tableData.param)) as unknown as { list: any[]; total: number }
+	let list = res.list ?? []
+	list.map((item: any) => {
+		item.createdBy = item.createdUser?.userNickname
+	})
+	state.tableData.data = list
+	state.tableData.total = res.total
+})
+
 onMounted(() => {
-  initTableData();
+	initTableData()
 })
 
 const initTableData = () => {
-  flowFormList();
-}
-
-const flowFormList = () => {
-  loading.value = true
-  listFlowForm(state.tableData.param).then((res: any) => {
-    let list = res.list ?? [];
-    list.map((item: any) => {
-      item.createdBy = item.createdUser?.userNickname
-    })
-    state.tableData.data = list;
-    state.tableData.total = res.total;
-    loading.value = false
-  })
+	getSummaryData()
+	flowFormList()
 }
 
 // 处理表单点击事件
 const handleFormClick = (form: FlowFormTableColumns) => {
-  // 设置表单数据,dataId为0表示新增
-  form.dataId = 0
-  addFormDataRef.value.openDialog(form)
+	// 设置表单数据,dataId为0表示新增
+	form.dataId = 0
+	addFormDataRef.value.openDialog(form)
 }
 
 // 分页处理
 const handleSizeChange = (val: number) => {
-  state.tableData.param.pageSize = val
-  flowFormList()
+	state.tableData.param.pageSize = val
+	flowFormList()
 }
 
 const handleCurrentChange = (val: number) => {
-  state.tableData.param.pageNum = val
-  flowFormList()
+	state.tableData.param.pageNum = val
+	flowFormList()
 }
 
 // 获取表单数据列表(用于刷新)
 const getFormDataList = () => {
-  // 这里可以根据需要实现刷新逻辑
-  console.log('刷新表单数据列表')
+	// 这里可以根据需要实现刷新逻辑
+	console.log('刷新表单数据列表')
 }
 </script>
 
 <template>
-  <div class="flow-craft-container" :data-theme="isDark ? 'dark' : ''">
-    <!-- 顶部统计卡片 -->
-    <div class="summary-section">
-      <el-row :gutter="20">
-        <el-col :span="24 / summaryCards.length" v-for="(card, index) in summaryCards" :key="card.title">
-          <el-card class="summary-card" shadow="hover">
-            <div class="summary-content">
-              <div class="summary-icon" :style="{ backgroundColor: getSummaryCardBgColor(index), color: card.color }">
-                <el-icon :size="24">
-                  <component :is="card.icon" />
-                </el-icon>
-              </div>
-              <div class="summary-info">
-                <div class="summary-number">{{ card.number }}</div>
-                <div class="summary-title">{{ card.title }}</div>
-                <div class="summary-desc">{{ card.description }}</div>
-              </div>
-            </div>
-          </el-card>
-        </el-col>
-      </el-row>
-    </div>
-
-    <!-- 自定义表单区域 -->
-    <div class="section-container">
-      <div class="section-header">
-        <div class="section-bar"></div>
-        <h3 class="section-title">自定义表单</h3>
-      </div>
-      <el-row :gutter="20">
-        <el-col :span="8" v-for="form in tableData.data" :key="form.id">
-          <el-card
-            class="function-card"
-            shadow="hover"
-            @click="handleFormClick(form)"
-          >
-            <div class="card-content">
-              <el-icon :size="32" color="#409eff">
-                <ele-Document />
-              </el-icon>
-              <div class="card-title">{{ form.name }}</div>
-              <div class="card-subtitle">点击发起表单审批</div>
-            </div>
-          </el-card>
-        </el-col>
-      </el-row>
-
-      <!-- 分页 -->
-      <div class="pagination-container" v-show="tableData.total > 0">
-        <el-pagination
-          v-model:current-page="tableData.param.pageNum"
-          v-model:page-size="tableData.param.pageSize"
-          :page-sizes="[6]"
-          :total="tableData.total"
-          layout="total, sizes, prev, pager, next, jumper"
-          @size-change="handleSizeChange"
-          @current-change="handleCurrentChange"
-        />
-      </div>
-    </div>
-
-    <!-- 发起表单审批组件 -->
-    <add-form-data
-      ref="addFormDataRef"
-      @getFormDataList="getFormDataList"
-    />
-  </div>
+	<div class="flow-craft-container" :data-theme="isDark ? 'dark' : ''">
+		<!-- 顶部统计卡片 -->
+		<div class="summary-section">
+			<el-row :gutter="20">
+				<el-col :span="8">
+					<el-card class="summary-card" shadow="hover" v-loading="summaryLoading">
+						<div class="summary-content">
+							<div class="summary-icon" :style="{ backgroundColor: getSummaryCardBgColor(0), color: '#f56c6c' }">
+								<el-icon :size="24">
+									<ele-Warning />
+								</el-icon>
+							</div>
+							<div class="summary-info">
+								<div class="summary-number">
+									{{ summaryData.prepare }}
+								</div>
+								<div class="summary-title">我的待办</div>
+								<div class="summary-desc">待处理任务</div>
+							</div>
+						</div>
+					</el-card>
+				</el-col>
+				<el-col :span="8">
+					<el-card class="summary-card" shadow="hover" v-loading="summaryLoading">
+						<div class="summary-content">
+							<div class="summary-icon" :style="{ backgroundColor: getSummaryCardBgColor(1), color: '#409eff' }">
+								<el-icon :size="24">
+									<ele-Document />
+								</el-icon>
+							</div>
+							<div class="summary-info">
+								<div class="summary-number">
+									{{ summaryData.start }}
+								</div>
+								<div class="summary-title">我发起的</div>
+								<div class="summary-desc">发起的工单</div>
+							</div>
+						</div>
+					</el-card>
+				</el-col>
+				<el-col :span="8">
+					<el-card class="summary-card" shadow="hover" v-loading="summaryLoading">
+						<div class="summary-content">
+							<div class="summary-icon" :style="{ backgroundColor: getSummaryCardBgColor(2), color: '#67c23a' }">
+								<el-icon :size="24">
+									<ele-User />
+								</el-icon>
+							</div>
+							<div class="summary-info">
+								<div class="summary-number">
+									{{ summaryData.already }}
+								</div>
+								<div class="summary-title">我处理的</div>
+								<div class="summary-desc">已处理任务</div>
+							</div>
+						</div>
+					</el-card>
+				</el-col>
+			</el-row>
+		</div>
+
+		<!-- 自定义表单区域 -->
+		<div class="section-container">
+			<div class="section-header">
+				<div class="section-bar"></div>
+				<h3 class="section-title">自定义表单</h3>
+			</div>
+
+			<!-- 加载占位符 -->
+			<div v-if="formListLoading" class="loading-placeholder">
+				<el-row :gutter="20">
+					<el-col :span="8" v-for="i in 6" :key="i">
+						<el-card class="function-card placeholder" shadow="hover">
+							<div class="card-content">
+								<el-skeleton :rows="2" animated />
+							</div>
+						</el-card>
+					</el-col>
+				</el-row>
+			</div>
+
+			<!-- 表单列表 -->
+			<el-row v-else :gutter="20">
+				<el-col :span="8" v-for="form in tableData.data" :key="form.id">
+					<el-card class="function-card" shadow="hover" @click="handleFormClick(form)">
+						<div class="card-content">
+							<el-icon :size="32" color="#409eff">
+								<ele-Document />
+							</el-icon>
+							<div class="card-title">{{ form.name }}</div>
+							<div class="card-subtitle">点击发起表单审批</div>
+						</div>
+					</el-card>
+				</el-col>
+			</el-row>
+
+			<!-- 空状态 -->
+			<div v-if="!formListLoading && tableData.data.length === 0" class="empty-state">
+				<el-empty description="暂无表单数据" />
+			</div>
+
+			<!-- 分页 -->
+			<div class="pagination-container" v-show="tableData.total > 0">
+				<el-pagination
+					v-model:current-page="tableData.param.pageNum"
+					v-model:page-size="tableData.param.pageSize"
+					:page-sizes="[6]"
+					:total="tableData.total"
+					layout="total, sizes, prev, pager, next, jumper"
+					@size-change="handleSizeChange"
+					@current-change="handleCurrentChange"
+				/>
+			</div>
+		</div>
+
+		<!-- 发起表单审批组件 -->
+		<add-form-data ref="addFormDataRef" @getFormDataList="getFormDataList" />
+	</div>
 </template>
 
 <style scoped lang="scss">
 .flow-craft-container {
-  padding: 20px;
-  background-color: #f5f7fa;
-  min-height: 100vh;
-
-  .summary-section {
-    margin-bottom: 30px;
-
-    .summary-card {
-      .summary-content {
-        display: flex;
-        align-items: center;
-        gap: 16px;
-
-        .summary-icon {
-          width: 60px;
-          height: 60px;
-          border-radius: 12px;
-          display: flex;
-          align-items: center;
-          justify-content: center;
-        }
-
-        .summary-info {
-          flex: 1;
-
-          .summary-number {
-            font-size: 28px;
-            font-weight: bold;
-            color: #303133;
-            line-height: 1;
-            margin-bottom: 4px;
-          }
-
-          .summary-title {
-            font-size: 16px;
-            font-weight: 600;
-            color: #303133;
-            margin-bottom: 4px;
-          }
-
-          .summary-desc {
-            font-size: 14px;
-            color: #909399;
-          }
-        }
-      }
-    }
-  }
-
-  .section-container {
-    margin-bottom: 30px;
-
-    .section-header {
-      display: flex;
-      align-items: center;
-      margin-bottom: 20px;
-
-      .section-bar {
-        width: 4px;
-        height: 24px;
-        background-color: #67c23a;
-        border-radius: 2px;
-        margin-right: 12px;
-      }
-
-      .section-title {
-        font-size: 18px;
-        font-weight: 600;
-        color: #303133;
-        margin: 0;
-      }
-    }
-
-    .function-card {
-      margin-bottom: 20px;
-      cursor: pointer;
-      transition: all 0.3s ease;
-
-      &:hover {
-        transform: translateY(-2px);
-        box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
-      }
-
-      .card-content {
-        text-align: center;
-        padding: 20px;
-
-        .card-title {
-          font-size: 16px;
-          font-weight: 600;
-          color: #303133;
-          margin: 16px 0 8px 0;
-        }
-
-        .card-subtitle {
-          font-size: 14px;
-          color: #909399;
-          line-height: 1.4;
-        }
-      }
-    }
-
-    .pagination-container {
-      margin-top: 20px;
-      text-align: center;
-    }
-  }
+	padding: 20px;
+	background-color: #f5f7fa;
+	min-height: 100vh;
+
+	.summary-section {
+		margin-bottom: 30px;
+
+		.summary-card {
+			.summary-content {
+				display: flex;
+				align-items: center;
+				gap: 16px;
+
+				.summary-icon {
+					width: 60px;
+					height: 60px;
+					border-radius: 12px;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+				}
+
+				.summary-info {
+					flex: 1;
+
+					.summary-number {
+						font-size: 28px;
+						font-weight: bold;
+						color: #303133;
+						line-height: 1;
+						margin-bottom: 4px;
+
+						.el-skeleton {
+							width: 60px;
+							height: 28px;
+						}
+					}
+
+					.summary-title {
+						font-size: 16px;
+						font-weight: 600;
+						color: #303133;
+						margin-bottom: 4px;
+					}
+
+					.summary-desc {
+						font-size: 14px;
+						color: #909399;
+					}
+				}
+			}
+		}
+	}
+
+	.section-container {
+		margin-bottom: 30px;
+
+		.section-header {
+			display: flex;
+			align-items: center;
+			margin-bottom: 20px;
+
+			.section-bar {
+				width: 4px;
+				height: 24px;
+				background-color: #67c23a;
+				border-radius: 2px;
+				margin-right: 12px;
+			}
+
+			.section-title {
+				font-size: 18px;
+				font-weight: 600;
+				color: #303133;
+				margin: 0;
+			}
+		}
+
+		.loading-placeholder {
+			.function-card.placeholder {
+				cursor: default;
+
+				&:hover {
+					transform: none;
+					box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+				}
+
+				.card-content {
+					padding: 20px;
+
+					.el-skeleton {
+						.el-skeleton__item {
+							height: 20px;
+							margin-bottom: 16px;
+						}
+					}
+				}
+			}
+		}
+
+		.function-card {
+			margin-bottom: 20px;
+			cursor: pointer;
+			transition: all 0.3s ease;
+
+			&:hover {
+				transform: translateY(-2px);
+				box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
+			}
+
+			.card-content {
+				text-align: center;
+				padding: 20px;
+
+				.card-title {
+					font-size: 16px;
+					font-weight: 600;
+					color: #303133;
+					margin: 16px 0 8px 0;
+				}
+
+				.card-subtitle {
+					font-size: 14px;
+					color: #909399;
+					line-height: 1.4;
+				}
+			}
+		}
+
+		.empty-state {
+			text-align: center;
+			padding: 40px 0;
+		}
+
+		.pagination-container {
+			margin-top: 20px;
+			text-align: center;
+		}
+	}
 }
 
 // 深色模式样式覆盖
-[data-theme="dark"] {
-  .flow-craft-container {
-    background-color: #1f1f1f;
-
-    .summary-section {
-      .summary-card {
-        .summary-content {
-          .summary-info {
-            .summary-number {
-              color: #e5eaf3;
-            }
-
-            .summary-title {
-              color: #e5eaf3;
-            }
-
-            .summary-desc {
-              color: #a3a6ad;
-            }
-          }
-        }
-      }
-    }
-
-    .section-container {
-      .section-header {
-        .section-title {
-          color: #e5eaf3;
-        }
-      }
-
-      .function-card {
-        &:hover {
-          box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
-        }
-
-        .card-content {
-          .card-title {
-            color: #e5eaf3;
-          }
-
-          .card-subtitle {
-            color: #a3a6ad;
-          }
-        }
-      }
-    }
-  }
+[data-theme='dark'] {
+	.flow-craft-container {
+		background-color: #1f1f1f;
+
+		.summary-section {
+			.summary-card {
+				.summary-content {
+					.summary-info {
+						.summary-number {
+							color: #e5eaf3;
+						}
+
+						.summary-title {
+							color: #e5eaf3;
+						}
+
+						.summary-desc {
+							color: #a3a6ad;
+						}
+					}
+				}
+			}
+		}
+
+		.section-container {
+			.section-header {
+				.section-title {
+					color: #e5eaf3;
+				}
+			}
+
+			.function-card {
+				&:hover {
+					box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
+				}
+
+				.card-content {
+					.card-title {
+						color: #e5eaf3;
+					}
+
+					.card-subtitle {
+						color: #a3a6ad;
+					}
+				}
+			}
+		}
+	}
 }
 
 // 响应式设计
 @media (max-width: 1200px) {
-  .flow-craft-container {
-    .summary-section {
-      .el-col {
-        margin-bottom: 20px;
-      }
-    }
-  }
+	.flow-craft-container {
+		.summary-section {
+			.el-col {
+				margin-bottom: 20px;
+			}
+		}
+	}
 }
 
 @media (max-width: 768px) {
-  .flow-craft-container {
-    padding: 15px;
-
-    .summary-section {
-      .el-col {
-        width: 100% !important;
-        margin-bottom: 15px;
-      }
-    }
-
-    .section-container {
-      .el-col {
-        width: 100% !important;
-        margin-bottom: 15px;
-      }
-    }
-  }
+	.flow-craft-container {
+		padding: 15px;
+
+		.summary-section {
+			.el-col {
+				width: 100% !important;
+				margin-bottom: 15px;
+			}
+		}
+
+		.section-container {
+			.el-col {
+				width: 100% !important;
+				margin-bottom: 15px;
+			}
+		}
+	}
 }
 </style>