Просмотр исходного кода

对移动端的工作流的初步调整,做实验用

zlwu 1 год назад
Родитель
Сommit
769df7900e

+ 14 - 0
mobile/src/pages.json

@@ -52,6 +52,20 @@
 				"navigationBarTitleText": "路由守卫测试",
 				"navigationStyle": "custom"
 			}
+		},
+		{
+			"path": "pages/workflow/process",
+			"style": {
+				"navigationBarTitleText": "流程详情",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/workflow/initiated",
+			"style": {
+				"navigationBarTitleText": "我发起的流程",
+				"navigationStyle": "custom"
+			}
 		}
 	],
 	"globalStyle": {

+ 667 - 0
mobile/src/pages/index/index.vue

@@ -133,6 +133,50 @@
                 </template>
             </Card>
 
+            <!-- 工作流模块 -->
+            <Card>
+                <template #header>
+                    <view class="workflow-header">
+                        <text class="workflow-icon">🔄</text>
+                        <text class="card-title">工作流</text>
+                    </view>
+                </template>
+                <template #default>
+                    <view class="workflow-tabs">
+                        <view
+                            v-for="tab in workflowTabs"
+                            :key="tab.key"
+                            class="workflow-tab"
+                            :class="{ active: activeWorkflowTab === tab.key }"
+                            @click="handleWorkflowTabClick(tab.key)"
+                        >
+                            <text class="tab-icon">{{ tab.icon }}</text>
+                            <text class="tab-text">{{ tab.label }}</text>
+                            <view v-if="tab.badge > 0" class="tab-badge">
+                                <text class="tab-badge-text">{{ tab.badge }}</text>
+                            </view>
+                        </view>
+                    </view>
+
+                    <!-- 我发起的内容 -->
+                    <view v-if="activeWorkflowTab === 'initiated'" class="workflow-content">
+                        <view class="workflow-empty">
+                            <u-button type="primary" @click="gotoInitiatedList">
+                                查看我发起的流程
+                            </u-button>
+                        </view>
+                    </view>
+
+                    <!-- 其他标签页内容(暂时显示占位) -->
+                    <view v-else class="workflow-placeholder">
+                        <text class="placeholder-icon">🚧</text>
+                        <text class="placeholder-text">
+                            {{ getTabName(activeWorkflowTab) }}功能开发中
+                        </text>
+                    </view>
+                </template>
+            </Card>
+
             <!-- 快捷功能 -->
             <Card>
                 <template #header>
@@ -169,6 +213,7 @@ import GridLayout from '@/components/GridLayout.vue';
 import { userStorage } from '@/utils/request';
 import { userManager, userState } from '@/utils/user';
 import { guardedSwitchTab } from '@/utils/route-guard';
+import { workflowApi, getStatusStyle, formatTime, routerJump } from '@/utils/workflow-api';
 
 export default {
     name: 'HomePage',
@@ -279,12 +324,32 @@ export default {
                 { title: '校历', icon: '📅' },
                 { title: '帮助', icon: '❓' },
             ],
+            // 工作流相关数据
+            workflowTabs: [
+                { key: 'initiated', label: '我发起的', icon: '📝', badge: 0 },
+                { key: 'todo', label: '我的待办', icon: '⏰', badge: 0 },
+                { key: 'finished', label: '我的已办', icon: '✅', badge: 0 },
+                { key: 'copy', label: '我的抄送', icon: '📋', badge: 0 },
+            ],
+            activeWorkflowTab: 'initiated',
+            workflowList: [],
+            workflowLoading: false,
+            workflowQuery: {
+                pageNum: 1,
+                pageSize: 10,
+                flowCode: '',
+                category: '',
+            },
+            categoryList: [],
+            showCategories: false,
+            hasMore: true,
         };
     },
     onLoad() {
         this.checkLoginStatus();
         this.updateDateTime();
         this.startTimeUpdate();
+        this.loadWorkflowData();
     },
     onUnload() {
         if (this.timeTimer) {
@@ -359,6 +424,282 @@ export default {
         viewAllNews() {
             uni.showToast({ title: '查看全部动态', icon: 'none' });
         },
+
+        // 工作流相关方法
+        switchWorkflowTab(tab) {
+            this.activeWorkflowTab = tab;
+            this.workflowQuery.pageNum = 1;
+            this.workflowList = [];
+            this.hasMore = true;
+
+            if (tab === 'initiated') {
+                this.loadWorkflowData();
+            } else {
+                // 其他标签页暂时不加载数据
+                uni.showToast({ title: this.getTabName(tab) + '功能开发中', icon: 'none' });
+            }
+        },
+
+        async loadWorkflowData() {
+            if (this.workflowLoading) return;
+
+            this.workflowLoading = true;
+            try {
+                const response = await workflowApi.pageByCurrent(this.workflowQuery);
+
+                if (response.code === 200) {
+                    const newData = response.rows || [];
+
+                    if (this.workflowQuery.pageNum === 1) {
+                        this.workflowList = newData;
+                    } else {
+                        this.workflowList = [...this.workflowList, ...newData];
+                    }
+
+                    // 检查是否还有更多数据
+                    this.hasMore = newData.length === this.workflowQuery.pageSize;
+
+                    // 更新待办数量徽章
+                    this.updateWorkflowBadges();
+                } else {
+                    uni.showToast({
+                        title: response.msg || '加载失败',
+                        icon: 'error',
+                    });
+                }
+            } catch (error) {
+                console.error('加载工作流数据失败:', error);
+                uni.showToast({
+                    title: '网络错误',
+                    icon: 'error',
+                });
+            } finally {
+                this.workflowLoading = false;
+            }
+        },
+
+        async loadCategoryData() {
+            try {
+                const response = await workflowApi.categoryTree();
+                if (response.code === 200) {
+                    this.categoryList = response.data || [];
+                }
+            } catch (error) {
+                console.error('加载分类数据失败:', error);
+            }
+        },
+
+        loadMoreWorkflow() {
+            if (!this.hasMore || this.workflowLoading) return;
+
+            this.workflowQuery.pageNum += 1;
+            this.loadWorkflowData();
+        },
+
+        handleWorkflowSearch() {
+            this.workflowQuery.pageNum = 1;
+            this.workflowList = [];
+            this.hasMore = true;
+            this.loadWorkflowData();
+        },
+
+        showCategoryFilter() {
+            this.showCategories = !this.showCategories;
+            if (this.showCategories && this.categoryList.length === 0) {
+                this.loadCategoryData();
+            }
+        },
+
+        selectCategory(categoryId) {
+            this.workflowQuery.category = categoryId;
+            this.showCategories = false;
+            this.handleWorkflowSearch();
+        },
+
+        // 保证 handleWorkflowItemClick 统一跳转详情
+        handleWorkflowItemClick(item) {
+            this.handleView(item);
+        },
+
+        handleEdit(item) {
+            const routerJumpVo = {
+                businessId: item.businessId,
+                taskId: item.id,
+                type: 'update',
+                formCustom: item.formCustom,
+                formPath: item.formPath,
+            };
+            routerJump(routerJumpVo);
+        },
+
+        handleView(item) {
+            const routerJumpVo = {
+                businessId: item.businessId,
+                taskId: item.id,
+                type: 'view',
+                formCustom: item.formCustom,
+                formPath: item.formPath,
+            };
+            // 直接使用 uni.navigateTo 跳转到新页面
+            const params = Object.keys(routerJumpVo)
+                .filter((key) => routerJumpVo[key] !== undefined && routerJumpVo[key] !== null)
+                .map((key) => `${key}=${encodeURIComponent(routerJumpVo[key])}`)
+                .join('&');
+            uni.navigateTo({
+                url: `/pages/workflow/process?${params}`,
+                fail: () => {
+                    uni.showToast({
+                        title: '页面跳转失败',
+                        icon: 'error',
+                    });
+                },
+            });
+        },
+
+        async handleDelete(item) {
+            try {
+                const confirmed = await this.showConfirm('确认删除', '是否确认删除该工作流?');
+                if (!confirmed) return;
+
+                const response = await workflowApi.deleteByInstanceIds([item.id]);
+                if (response.code === 200) {
+                    uni.showToast({
+                        title: '删除成功',
+                        icon: 'success',
+                    });
+                    this.handleWorkflowSearch();
+                } else {
+                    uni.showToast({
+                        title: response.msg || '删除失败',
+                        icon: 'error',
+                    });
+                }
+            } catch (error) {
+                console.error('删除失败:', error);
+                uni.showToast({
+                    title: '删除失败',
+                    icon: 'error',
+                });
+            }
+        },
+
+        async handleCancel(item) {
+            try {
+                const confirmed = await this.showConfirm('确认撤销', '是否确认撤销该工作流?');
+                if (!confirmed) return;
+
+                const data = {
+                    businessId: item.businessId,
+                    message: '申请人撤销流程!',
+                };
+
+                const response = await workflowApi.cancelProcessApply(data);
+                if (response.code === 200) {
+                    uni.showToast({
+                        title: '撤销成功',
+                        icon: 'success',
+                    });
+                    this.handleWorkflowSearch();
+                } else {
+                    uni.showToast({
+                        title: response.msg || '撤销失败',
+                        icon: 'error',
+                    });
+                }
+            } catch (error) {
+                console.error('撤销失败:', error);
+                uni.showToast({
+                    title: '撤销失败',
+                    icon: 'error',
+                });
+            }
+        },
+
+        // 工具方法
+        getStatusText(status) {
+            const statusMap = {
+                draft: '草稿',
+                waiting: '待审批',
+                complete: '已完成',
+                cancel: '已取消',
+                back: '已退回',
+                suspend: '挂起',
+                terminate: '终止',
+            };
+            return statusMap[status] || '未知';
+        },
+
+        getStatusColor(status) {
+            const colorMap = {
+                draft: 'background-color: #909399; color: white',
+                waiting: 'background-color: #e6a23c; color: white',
+                complete: 'background-color: #67c23a; color: white',
+                cancel: 'background-color: #f56c6c; color: white',
+                back: 'background-color: #409eff; color: white',
+                suspend: 'background-color: #909399; color: white',
+                terminate: 'background-color: #f56c6c; color: white',
+            };
+            return colorMap[status] || colorMap.draft;
+        },
+
+        formatWorkflowTime(timeStr) {
+            return formatTime(timeStr);
+        },
+
+        canEdit(status) {
+            return ['draft', 'cancel', 'back'].includes(status);
+        },
+
+        canDelete(status) {
+            return ['draft', 'cancel', 'back'].includes(status);
+        },
+
+        canCancel(status) {
+            return status === 'waiting';
+        },
+
+        getTabName(tab) {
+            const tabMap = {
+                initiated: '我发起的',
+                todo: '我的待办',
+                finished: '我的已办',
+                copy: '我的抄送',
+            };
+            return tabMap[tab] || '';
+        },
+
+        updateWorkflowBadges() {
+            // 这里可以根据实际需求更新各个标签页的徽章数量
+            // 暂时设置为工作流列表的长度
+            const tabIndex = this.workflowTabs.findIndex((tab) => tab.key === 'initiated');
+            if (tabIndex !== -1) {
+                this.workflowTabs[tabIndex].badge = this.workflowList.length;
+            }
+        },
+
+        showConfirm(title, content) {
+            return new Promise((resolve) => {
+                uni.showModal({
+                    title,
+                    content,
+                    success: (res) => {
+                        resolve(res.confirm);
+                    },
+                    fail: () => {
+                        resolve(false);
+                    },
+                });
+            });
+        },
+        gotoInitiatedList() {
+            uni.navigateTo({
+                url: '/pages/workflow/initiated',
+            });
+        },
+        handleWorkflowTabClick(tab) {
+            this.activeWorkflowTab = tab;
+            // 其他tab逻辑可保留
+        },
     },
 };
 </script>
@@ -637,6 +978,332 @@ export default {
     height: 20px;
 }
 
+/* 工作流模块样式 */
+.workflow-header {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+.workflow-icon {
+    color: #409eff;
+    font-size: $uni-font-size-lg;
+}
+
+.workflow-tabs {
+    display: flex;
+    margin-bottom: $uni-spacing-col-lg;
+    background: $uni-bg-color-grey;
+    border-radius: $uni-border-radius-base;
+    padding: 4px;
+}
+
+.workflow-tab {
+    flex: 1;
+    padding: 8px 12px;
+    text-align: center;
+    border-radius: $uni-border-radius-base;
+    position: relative;
+    transition: $uni-transition;
+
+    &.active {
+        background: $uni-color-primary;
+        color: $uni-text-color-inverse;
+    }
+}
+
+.tab-icon {
+    font-size: 16px;
+    margin-bottom: 2px;
+}
+
+.tab-text {
+    font-size: $uni-font-size-sm;
+    font-weight: 500;
+}
+
+.tab-badge {
+    position: absolute;
+    top: -2px;
+    right: -2px;
+    background: #ff4757;
+    border-radius: $uni-border-radius-circle;
+    min-width: 16px;
+    height: 16px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.tab-badge-text {
+    color: $uni-text-color-inverse;
+    font-size: 10px;
+    font-weight: 500;
+    padding: 0 4px;
+}
+
+.workflow-content {
+    margin-top: $uni-spacing-col-base;
+}
+
+/* 搜索和筛选 */
+.workflow-filter {
+    margin-bottom: $uni-spacing-col-lg;
+}
+
+.filter-row {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    margin-bottom: 12px;
+}
+
+.search-input {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    background: $uni-bg-color-grey;
+    border-radius: $uni-border-radius-base;
+    padding: 0 12px;
+    height: 36px;
+
+    .iconfont {
+        margin-right: 8px;
+        color: $uni-text-color-grey;
+        font-size: 16px;
+        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23999' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E");
+        width: 16px;
+        height: 16px;
+        background-size: contain;
+        background-repeat: no-repeat;
+        background-position: center;
+        filter: invert(60%);
+    }
+
+    input {
+        flex: 1;
+        border: none;
+        background: transparent;
+        font-size: $uni-font-size-base;
+        color: $uni-text-color;
+    }
+}
+
+.filter-button {
+    width: 36px;
+    height: 36px;
+    background: $uni-color-primary;
+    border-radius: $uni-border-radius-base;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    .iconfont {
+        color: $uni-text-color-inverse;
+        font-size: 16px;
+        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23fff' viewBox='0 0 24 24'%3E%3Cpath d='M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z'/%3E%3C/svg%3E");
+        width: 16px;
+        height: 16px;
+        background-size: contain;
+        background-repeat: no-repeat;
+        background-position: center;
+    }
+}
+
+.category-filter {
+    margin-top: 12px;
+}
+
+.category-scroll {
+    width: 100%;
+}
+
+.category-list {
+    display: flex;
+    gap: 8px;
+    white-space: nowrap;
+    padding-bottom: 4px;
+}
+
+.category-item {
+    padding: 6px 12px;
+    background: $uni-bg-color-grey;
+    border-radius: $uni-border-radius-base;
+    white-space: nowrap;
+    transition: $uni-transition;
+
+    &.active {
+        background: $uni-color-primary;
+        color: $uni-text-color-inverse;
+    }
+}
+
+.category-text {
+    font-size: $uni-font-size-sm;
+}
+
+/* 工作流列表 */
+.workflow-loading {
+    text-align: center;
+    padding: 40px 0;
+    color: $uni-text-color-grey;
+}
+
+.workflow-empty {
+    text-align: center;
+    padding: 40px 0;
+    color: $uni-text-color-grey;
+}
+
+.empty-icon {
+    font-size: 48px;
+    margin-bottom: 12px;
+    display: block;
+}
+
+.empty-text {
+    font-size: $uni-font-size-base;
+}
+
+.workflow-list {
+    margin-top: $uni-spacing-col-base;
+}
+
+.workflow-item {
+    background: $uni-bg-color;
+    border-radius: $uni-border-radius-base;
+    padding: $uni-spacing-col-lg;
+    margin-bottom: 12px;
+    box-shadow: $uni-shadow;
+    transition: $uni-transition;
+
+    &:hover {
+        box-shadow: $uni-shadow-hover;
+    }
+}
+
+.workflow-header-info {
+    margin-bottom: 12px;
+}
+
+.workflow-title-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    margin-bottom: 8px;
+}
+
+.workflow-title {
+    font-size: $uni-font-size-lg;
+    font-weight: 500;
+    color: $uni-text-color;
+    flex: 1;
+    margin-right: 12px;
+}
+
+.workflow-status {
+    padding: 4px 8px;
+    border-radius: $uni-border-radius-base;
+    font-size: $uni-font-size-sm;
+    font-weight: 500;
+    white-space: nowrap;
+}
+
+.workflow-code {
+    font-size: $uni-font-size-sm;
+    color: $uni-text-color-grey;
+}
+
+.workflow-meta {
+    margin-bottom: 12px;
+}
+
+.meta-item {
+    display: flex;
+    margin-bottom: 4px;
+
+    &:last-child {
+        margin-bottom: 0;
+    }
+}
+
+.meta-label {
+    font-size: $uni-font-size-sm;
+    color: $uni-text-color-grey;
+    width: 70px;
+    flex-shrink: 0;
+}
+
+.meta-value {
+    font-size: $uni-font-size-sm;
+    color: $uni-text-color;
+    flex: 1;
+}
+
+.workflow-actions {
+    display: flex;
+    gap: 8px;
+    flex-wrap: wrap;
+}
+
+.action-button {
+    padding: 6px 12px;
+    border-radius: $uni-border-radius-base;
+    font-size: $uni-font-size-sm;
+    font-weight: 500;
+    text-align: center;
+    min-width: 50px;
+    transition: $uni-transition;
+
+    &.edit {
+        background: #409eff;
+        color: $uni-text-color-inverse;
+    }
+
+    &.delete {
+        background: #f56c6c;
+        color: $uni-text-color-inverse;
+    }
+
+    &.view {
+        background: $uni-color-primary;
+        color: $uni-text-color-inverse;
+    }
+
+    &.cancel {
+        background: #e6a23c;
+        color: $uni-text-color-inverse;
+    }
+
+    &:active {
+        opacity: 0.8;
+        transform: scale(0.98);
+    }
+}
+
+.load-more {
+    text-align: center;
+    padding: 20px 0;
+    color: $uni-color-primary;
+    font-size: $uni-font-size-base;
+}
+
+.workflow-placeholder {
+    text-align: center;
+    padding: 60px 0;
+    color: $uni-text-color-grey;
+}
+
+.placeholder-icon {
+    font-size: 48px;
+    margin-bottom: 12px;
+    display: block;
+}
+
+.placeholder-text {
+    font-size: $uni-font-size-base;
+}
+
 /* 页面进入动画 */
 .page-container {
     animation: fadeInUp 0.6s ease-out;

+ 167 - 0
mobile/src/pages/workflow/initiated.vue

@@ -0,0 +1,167 @@
+<template>
+    <view class="initiated-container">
+        <view class="header">
+            <text class="title">我发起的流程</text>
+        </view>
+        <view v-if="loading" class="loading">加载中...</view>
+        <view v-else-if="workflowList.length === 0" class="empty">
+            <text>暂无数据</text>
+        </view>
+        <view v-else class="workflow-list">
+            <view
+                v-for="item in workflowList"
+                :key="item.id"
+                class="workflow-item touchable"
+                @click="gotoDetail(item)"
+            >
+                <view class="workflow-header-info">
+                    <view class="workflow-title-row">
+                        <text class="workflow-title">{{ item.flowName }}</text>
+                        <view class="workflow-status" :style="getStatusColor(item.flowStatus)">
+                            <text class="status-text">{{ getStatusText(item.flowStatus) }}</text>
+                        </view>
+                    </view>
+                    <text class="workflow-code">{{ item.flowCode }}</text>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import { workflowApi, formatTime } from '@/utils/workflow-api';
+export default {
+    data() {
+        return {
+            workflowList: [],
+            loading: true,
+        };
+    },
+    onLoad() {
+        this.loadWorkflowData();
+    },
+    methods: {
+        async loadWorkflowData() {
+            this.loading = true;
+            try {
+                const res = await workflowApi.pageByCurrent({ pageNum: 1, pageSize: 20 });
+                if (res.code === 200) {
+                    this.workflowList = res.rows || [];
+                } else {
+                    uni.showToast({ title: res.msg || '加载失败', icon: 'error' });
+                }
+            } catch (e) {
+                uni.showToast({ title: '网络错误', icon: 'error' });
+            } finally {
+                this.loading = false;
+            }
+        },
+        gotoDetail(item) {
+            const params = [
+                `businessId=${encodeURIComponent(item.businessId)}`,
+                `taskId=${encodeURIComponent(item.id)}`,
+                `type=view`,
+                `formCustom=${encodeURIComponent(item.formCustom || '')}`,
+                `formPath=${encodeURIComponent(item.formPath || '')}`,
+            ].join('&');
+            uni.navigateTo({
+                url: `/pages/workflow/process?${params}`,
+            });
+        },
+        getStatusText(status) {
+            const statusMap = {
+                draft: '草稿',
+                waiting: '待审批',
+                complete: '已完成',
+                cancel: '已取消',
+                back: '已退回',
+                suspend: '挂起',
+                terminate: '终止',
+            };
+            return statusMap[status] || '未知';
+        },
+        getStatusColor(status) {
+            const colorMap = {
+                draft: 'background-color: #909399; color: white',
+                waiting: 'background-color: #e6a23c; color: white',
+                complete: 'background-color: #67c23a; color: white',
+                cancel: 'background-color: #f56c6c; color: white',
+                back: 'background-color: #409eff; color: white',
+                suspend: 'background-color: #909399; color: white',
+                terminate: 'background-color: #f56c6c; color: white',
+            };
+            return colorMap[status] || colorMap.draft;
+        },
+        formatTime,
+    },
+};
+</script>
+
+<style scoped>
+.initiated-container {
+    padding: 20px;
+    background: #fff;
+    min-height: 100vh;
+}
+.header {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    margin-bottom: 20px;
+}
+.title {
+    font-size: 20px;
+    font-weight: bold;
+}
+.loading {
+    text-align: center;
+    color: #999;
+    margin-top: 40px;
+}
+.empty {
+    text-align: center;
+    color: #bbb;
+    margin-top: 40px;
+}
+.workflow-list {
+    margin-top: 12px;
+}
+.workflow-item {
+    background: #f8f9fa;
+    border-radius: 8px;
+    padding: 16px;
+    margin-bottom: 12px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
+    transition: box-shadow 0.2s;
+}
+.workflow-item:active {
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
+}
+.workflow-header-info {
+    margin-bottom: 8px;
+}
+.workflow-title-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    margin-bottom: 4px;
+}
+.workflow-title {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333;
+    flex: 1;
+    margin-right: 12px;
+}
+.workflow-status {
+    padding: 2px 8px;
+    border-radius: 12px;
+    font-size: 12px;
+    font-weight: 500;
+    white-space: nowrap;
+}
+.workflow-code {
+    font-size: 12px;
+    color: #888;
+}
+</style>

+ 117 - 0
mobile/src/pages/workflow/process.vue

@@ -0,0 +1,117 @@
+<template>
+    <view class="process-container">
+        <view class="header">
+            <text class="title">流程详情</text>
+            <text v-if="isViewMode" class="view-mode">(只读模式)</text>
+        </view>
+        <view v-if="loading" class="loading">加载中...</view>
+        <view v-else>
+            <u-form :disabled="isViewMode">
+                <u-form-item label="流程名称">
+                    <u-input v-model="processDetail.flowName" disabled />
+                </u-form-item>
+                <u-form-item label="流程编码">
+                    <u-input v-model="processDetail.flowCode" disabled />
+                </u-form-item>
+                <u-form-item label="分类">
+                    <u-input v-model="processDetail.categoryName" disabled />
+                </u-form-item>
+                <u-form-item label="状态">
+                    <u-input :value="processDetail.flowStatusName || statusText" disabled />
+                </u-form-item>
+                <u-form-item label="创建时间">
+                    <u-input :value="formatTime(processDetail.createTime)" disabled />
+                </u-form-item>
+                <!-- 可扩展更多字段 -->
+            </u-form>
+            <view v-if="!isViewMode" class="actions">
+                <u-button type="primary" @click="handleSubmit">提交</u-button>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import { workflowApi, formatTime, getStatusStyle } from '@/utils/workflow-api';
+export default {
+    data() {
+        return {
+            processDetail: {},
+            loading: true,
+            isViewMode: false,
+        };
+    },
+    computed: {
+        statusText() {
+            const statusMap = {
+                draft: '草稿',
+                waiting: '待审批',
+                complete: '已完成',
+                cancel: '已取消',
+                back: '已退回',
+                suspend: '挂起',
+                terminate: '终止',
+            };
+            return statusMap[this.processDetail.flowStatus] || '未知';
+        },
+    },
+    onLoad(options) {
+        this.isViewMode = options.type === 'view';
+        this.loadDetail(options);
+    },
+    methods: {
+        async loadDetail(options) {
+            this.loading = true;
+            try {
+                // 使用新接口获取详情
+                const res = await workflowApi.processDetail(options.businessId);
+                if (res.code === 200 && res.data) {
+                    this.processDetail = res.data;
+                } else {
+                    uni.showToast({ title: res.msg || '加载失败', icon: 'error' });
+                }
+            } catch (e) {
+                uni.showToast({ title: '网络错误', icon: 'error' });
+            } finally {
+                this.loading = false;
+            }
+        },
+        formatTime,
+        handleSubmit() {
+            uni.showToast({ title: '提交功能开发中', icon: 'none' });
+        },
+    },
+};
+</script>
+
+<style scoped>
+.process-container {
+    padding: 20px;
+    background: #fff;
+    min-height: 100vh;
+}
+.header {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    margin-bottom: 20px;
+}
+.title {
+    font-size: 20px;
+    font-weight: bold;
+}
+.view-mode {
+    color: #909399;
+    font-size: 14px;
+}
+.loading {
+    text-align: center;
+    color: #999;
+    margin-top: 40px;
+}
+.actions {
+    margin-top: 32px;
+    display: flex;
+    justify-content: flex-end;
+}
+</style>

+ 146 - 0
mobile/src/utils/workflow-api.js

@@ -0,0 +1,146 @@
+import { request } from './request.js';
+
+// 工作流API配置
+export const workflowApi = {
+    // 获取流程实例分页列表(我发起的)
+    pageByCurrent: (params) =>
+        request({
+            url: '/workflow/instance/pageByCurrent',
+            method: 'GET',
+            data: params,
+        }),
+    // 删除流程实例
+    deleteByInstanceIds: (instanceIds) =>
+        request({
+            url: '/workflow/instance/deleteByInstanceIds',
+            method: 'DELETE',
+            data: instanceIds,
+        }),
+    // 撤销流程申请
+    cancelProcessApply: (data) =>
+        request({
+            url: '/workflow/instance/cancelProcessApply',
+            method: 'POST',
+            data,
+        }),
+    // 获取流程分类树
+    categoryTree: () =>
+        request({
+            url: '/workflow/category/categoryTree',
+            method: 'GET',
+        }),
+    // 获取我的待办任务
+    todoTasks: (params) =>
+        request({
+            url: '/workflow/task/todoTasks',
+            method: 'GET',
+            data: params,
+        }),
+    // 获取我的已办任务
+    finishedTasks: (params) =>
+        request({
+            url: '/workflow/task/finishedTasks',
+            method: 'GET',
+            data: params,
+        }),
+    // 获取我的抄送任务
+    copyTasks: (params) =>
+        request({
+            url: '/workflow/task/copyTasks',
+            method: 'GET',
+            data: params,
+        }),
+    // 启动流程实例
+    startProcessInstance: (data) =>
+        request({
+            url: '/workflow/instance/start',
+            method: 'POST',
+            data,
+        }),
+    // 办理任务
+    complete: (data) =>
+        request({
+            url: '/workflow/task/complete',
+            method: 'POST',
+            data,
+        }),
+    // 获取流程历史
+    processHistory: (instanceId) =>
+        request({
+            url: `/workflow/instance/history/${instanceId}`,
+            method: 'GET',
+        }),
+    // 获取流程实例详情(根据业务ID)
+    processDetail: (businessId) =>
+        request({
+            url: `/workflow/instance/getInfo/${businessId}`,
+            method: 'GET',
+        }),
+};
+
+// 工作流状态映射
+export const workflowStatus = {
+    draft: { label: '草稿', color: '#909399', icon: '📝' },
+    waiting: { label: '待审批', color: '#e6a23c', icon: '⏰' },
+    complete: { label: '已完成', color: '#67c23a', icon: '✅' },
+    cancel: { label: '已取消', color: '#f56c6c', icon: '❌' },
+    back: { label: '已退回', color: '#409eff', icon: '↩️' },
+    suspend: { label: '挂起', color: '#909399', icon: '⏸️' },
+    terminate: { label: '终止', color: '#f56c6c', icon: '🛑' },
+};
+
+// 工作流分类图标映射
+export const categoryIcons = {
+    leave: '📅',
+    repair: '🔧',
+    purchase: '🛒',
+    reimbursement: '💰',
+    approval: '📋',
+    default: '📄',
+};
+
+// 获取状态样式
+export const getStatusStyle = (status) => workflowStatus[status] || workflowStatus.draft;
+
+// 获取分类图标
+export const getCategoryIcon = (category) => categoryIcons[category] || categoryIcons.default;
+
+// 工作流路由跳转方法
+export const routerJump = (routerJumpVo) => {
+    const { businessId, taskId, type, formCustom, formPath } = routerJumpVo;
+    const params = { businessId, taskId, type, formCustom, formPath };
+    const queryString = Object.keys(params)
+        .filter((key) => params[key] !== undefined && params[key] !== null)
+        .map((key) => `${key}=${encodeURIComponent(params[key])}`)
+        .join('&');
+    uni.navigateTo({
+        url: `/pages/workflow/process?${queryString}`,
+        fail: () => {
+            uni.showToast({
+                title: '页面跳转失败',
+                icon: 'error',
+            });
+        },
+    });
+};
+
+// 格式化时间显示
+export const formatTime = (timeStr) => {
+    if (!timeStr) return '';
+    const time = new Date(timeStr);
+    const now = new Date();
+    const diff = now.getTime() - time.getTime();
+    if (diff < 60 * 1000) {
+        return '刚刚';
+    }
+    if (diff < 60 * 60 * 1000) {
+        return `${Math.floor(diff / (60 * 1000))}分钟前`;
+    }
+    if (diff < 24 * 60 * 60 * 1000) {
+        return `${Math.floor(diff / (60 * 60 * 1000))}小时前`;
+    }
+    if (diff < 7 * 24 * 60 * 60 * 1000) {
+        return `${Math.floor(diff / (24 * 60 * 60 * 1000))}天前`;
+    }
+    return time.toLocaleDateString();
+};