Browse Source

流程列表、流程筛选、消息跳转

HD_wangm 3 months ago
parent
commit
48c45e7b12

+ 14 - 0
api/process.js

@@ -261,4 +261,18 @@ export function getSeal(staffId) {
     	}
     }
   })
+}
+
+// 获取环节信息
+export function getTacheInfo(tinsId) {
+	return request({
+		url: preUrl,
+		method: 'post',
+		data: {
+			serviceId: 'bpm_2026V0100PHONE001',
+			params: {
+				tinsid: tinsId
+			}
+		}
+	})
 }

+ 7 - 2
components/ygoa/messageList.vue

@@ -152,9 +152,14 @@ function scrollQuery(pageNo, pSize) {
 
 	emits('scrollToBottom', params, complete)
 }
-// 跳转详情
+//跳转详情
 function handleToDetail(message) {
-	emits('clickItem', message)
+	emits('clickItem', {
+		messageid: message.messageid,
+		universalid: message.universalid,
+		jump_id: message.jump_id,
+		title: message.title
+	})
 }
 // 格式化时间
 function formatDate(now) {

+ 227 - 25
components/ygoa/processList.vue

@@ -2,26 +2,38 @@
 	<view class="content" :style="`height: ${contentHeight}`">
 		<z-paging :fixed="false" @query="queryData" :value="list" :default-page-size="pSize" :default-page-no="pageNo"
 			ref="paging">
+			<!-- 简单的加载中提示 -->
+			<view v-if="isLoading && list.length === 0" class="loading-container">
+				<view class="loading-content">
+					<text class="loading-text">加载中...</text>
+				</view>
+			</view>
+
+			<!-- 列表项 -->
 			<view v-for="(process, index) in list" :key="index">
 				<uni-card @click="handleToDetail(process)" :isFull="true" padding="10px 0">
 					<uni-row>
-						<uni-col :xs="3" :sm="2">
+						<!-- <uni-col :xs="3" :sm="2">
 							<view class="icon_container">
 								<text style="font-size: calc(40px + 0.5 * (1rem - 16px));" class="ygoa_work_icon"
-									:class="iconDict && iconDict[process.modelName] ? iconDict[process.modelName] : 'icon-outsourcing'"
-></text>
+									:class="iconDict && iconDict[process.modelName] ? iconDict[process.modelName] : 'icon-outsourcing'"></text>
 							</view>
-						</uni-col>
-						<uni-col :xs="21" :sm="22">
+						</uni-col> -->
+						<uni-col :xs="24" :sm="24">
 							<uni-card padding="0" :isFull="true" :border="false" :is-shadow="false">
 								<template v-slot:title>
 									<uni-row>
-										<uni-col :xs="19" :sm="22" v-if="current !== 0">
+										<uni-col :xs="4" :sm="2">
+											<text class="abbreviation-label" :style="`background-color: ${getLabelBgColor(process.abbreviation)}`">
+												{{ process.abbreviation || '默认' }}
+											</text>
+										</uni-col>
+										<uni-col :xs="15" :sm="20" v-if="current !== 0">
 											<view class="process_title uni-ellipsis">
 												<text>{{ process.insName }}</text>
 											</view>
 										</uni-col>
-										<uni-col v-else>
+										<uni-col :xs="19" :sm="22" v-else>
 											<view class="process_title uni-ellipsis">
 												<text>{{ process.insName }}</text>
 											</view>
@@ -37,9 +49,9 @@
 								</template>
 								<view class="process_contant">
 									<uni-row>
-										<uni-col :xs="process.isCancel == '1'?18:24" :sm="process.isCancel == '1'?19:24">
+										<uni-col :xs="process.isCancel == '1'?20:24" :sm="process.isCancel == '1'?20:24">
 											<view v-if="process.tmodelName == undefined">
-												<uni-row v-if="process.insSubName != ''">
+												<!-- <uni-row v-if="process.insSubName != ''">
 													<uni-col :xs="24" :sm="24">
 														<text style="color: #79abff;font-weight: bold;">{{ process.insSubName }}</text>
 													</uni-col>
@@ -52,10 +64,42 @@
 												<uni-row>
 													<uni-col :xs="8" :sm="7">创建时间:</uni-col>
 													<uni-col :xs="16" :sm="17">{{ process.createdate }}</uni-col>
-												</uni-row>
+												</uni-row> -->
+												<view v-if="process.insSubName2" v-for="(item, idx) in parseInsSubName2(process.insSubName2)" :key="idx" class="info-item">
+													<text class="info-label">
+														{{ item.key }}:
+													</text>
+													<text class="info-value uni-ellipsis"
+														:class="{
+															'amount-value': item.key.includes('金额'),
+															'timeout-value': item.key === '待办超时'
+														}">
+														{{ item.value }}
+													</text>
+												</view>
+												<view v-else>
+													<view class="info-item">
+													<text v-if="process.insSubName != ''" class="info-label" :xs="24" :sm="24">
+														<text style="color: #79abff;font-weight: bold;">{{ process.insSubName }}</text>
+													</text>
+													</view>
+
+													<view class="info-item">
+														<text class="info-label">当前流程:</text>
+														<text class="info-value uni-ellipsis">{{ process.tmodelName || '无'}}</text>
+													</view>
+												</view>
+												<!-- <view class="info-item">
+													<text class="info-label">
+														创建时间:
+													</text>
+													<text class="info-value">
+														{{ process.createdate }}
+													</text>
+												</view> -->
 											</view>
 											<view v-else @click.stop.prevent="handleCollapseChange(process, index)">
-												<uni-row v-if="process.insSubName != ''">
+												<!-- <uni-row v-if="process.insSubName != ''">
 													<uni-col :xs="24" :sm="24">
 														<text style="color: #79abff;font-weight: bold;">{{ process.insSubName }}</text>
 													</uni-col>
@@ -70,10 +114,46 @@
 												<uni-row>
 													<uni-col :xs="8" :sm="7">创建时间:</uni-col>
 													<uni-col :xs="16" :sm="17">{{ process.createdate }}</uni-col>
-												</uni-row>
+												</uni-row> -->
+												<view v-if="process.insSubName2" v-for="(item, idx) in parseInsSubName2(process.insSubName2)" :key="idx" class="info-item">
+													<text class="info-label" :class="{ 'amount-label': item.key === '合计金额' || item.key === '金额' }">
+														{{ item.key }}:
+													</text>
+													<text class="info-value uni-ellipsis"
+														:class="{
+															'amount-value': item.key === '合计金额' || item.key === '金额',
+															'timeout-value': item.key === '待办超时'
+														}">
+														{{ item.value }}
+													</text>
+													<!-- <uni-row class="info-label">
+														<uni-col :xs="8" :sm="7">{{ item.key }}:</uni-col>
+														<uni-col :xs="16" :sm="17">{{ item.value }}</uni-col>
+													</uni-row> -->
+												</view>
+												<view v-else>
+													<view class="info-item">
+													<text v-if="process.insSubName != ''" class="info-label" :xs="24" :sm="24">
+														<text style="color: #79abff;font-weight: bold;">{{ process.insSubName }}</text>
+													</text>
+													</view>
+
+													<view class="info-item">
+														<text class="info-label">当前流程:</text>
+														<text class="info-value uni-ellipsis">{{ process.tmodelName || '无'}}</text>
+													</view>
+												</view>
+												<view class="info-item">
+													<text class="info-label">
+														创建时间:
+													</text>
+													<text class="info-value">
+														{{ process.createdate }}
+													</text>
+												</view>
 											</view>
 										</uni-col>
-										<uni-col :xs="6" :sm="5" v-if="process.isCancel == '1'">
+										<uni-col :xs="4" :sm="4" v-if="process.isCancel == '1'">
 											<uni-row>
 												<view class="button_container">
 													<button type="warn" size="mini"
@@ -87,12 +167,12 @@
 						</uni-col>
 					</uni-row>
 				</uni-card>
-				<uni-collapse v-if="process.flowStepItem != undefined">
-					<uni-collapse-item :border="false" :show-arrow="false" :open="process.flowStepItem.show">
+				<uni-collapse v-if="process.flowStepItem != undefined && process.flowStepItem.show">
+					<uni-collapse-item  class="no-title-collapse" :border="false" :show-arrow="false" :open="process.flowStepItem.show" title-border="none">
 						<template v-slot:title>
-							<!-- <view class="flow_step_section">
-								<uni-section title="当前流程" type="line" titleFontSize="0.8rem"></uni-section>
-							</view> -->
+							<view class="flow_step_section">
+								<!-- <uni-section title="流转过程" type="line" titleFontSize="0.8rem"></uni-section> -->
+							</view>
 						</template>
 						<view class="flow_step_container">
 							<up-steps class="up_step_view" :current="process.flowStepItem.stepActive"
@@ -108,7 +188,7 @@
 										:title="step.title" :desc="step.desc">
 										<template #icon>
 											<view class="active_step_circle">
-												<text class="active_step_text">{{ index + 1 }}</text>
+												<text class="active_step_text">{{ Number(index) + 1 }}</text>
 											</view>
 										</template>
 									</up-steps-item>
@@ -167,6 +247,52 @@ const paging = ref(null)
 // 加载完成 更新数据
 const list = ref([])
 const totalPage = ref(0)
+const isLoading = ref(false) // 添加加载状态
+
+/**
+ * 根据标签名称获取背景色
+ * @param label 标签名称(如"报销"、"申请")
+ * @returns 背景色值
+ */
+function getLabelBgColor(label) {
+	if (!label) return '#f5f5f5';
+	// 自定义标签颜色映射
+	const colorMap = {
+		'报销': '#e1f3ff',
+		'申请': '#f0f9eb',
+		'审批': '#fff7e6',
+		'结算': '#fef0f0',
+		'借款': '#f5fafe',
+		'采购': '#f9f5ff',
+		'付款': '#f9f5ff'
+	};
+	return colorMap[label] || '#f5f5f5';
+}
+
+/**
+ * 解析标准JSON格式的process.insSubName2
+ * 支持格式:[{"项目名称":"测试测试"},{"合计金额":"10000.00"}]
+ * @param jsonStr 标准JSON字符串
+ * @returns 键值对数组,格式如[{key: '项目名称', value: '测试测试'}, ...]
+ */
+function parseInsSubName2(data) {
+	debugger
+  if (!data || data == null) {
+    return [];
+  }
+
+  let arr = data;
+// 统一处理数组,转为键值对格式
+	return arr
+	  .filter(item => typeof item === 'object' && item !== null && Object.keys(item).length > 0)
+	  .map(item => {
+		const key = Object.keys(item)[0];
+		return {
+		  key: key || '',
+		  value: item[key] !== undefined ? item[key].toString() : ''
+		};
+	  });
+}
 
 function handleCollapseChange(process, index) {
 	// console.log('handleCollapseChange', process);
@@ -178,12 +304,18 @@ function handleCollapseChange(process, index) {
 				show: true
 			}
 			flowStepItem.options = returnParams.list.map((item, index) => {
-				const { tmodelName, name, createdate, finishdate, remark, state } = item
+				const { tmodelName, name, createdate, finishdate, remark, state, task, groupName, positionName } = item
 				if (state == 1) {
 					flowStepItem.stepActive = index
 				}
-				const title = tmodelName + (name == '' ? '' : ' ( ' + name + ' )')
-				const desc = (finishdate == '' ? '\n' : '办理时间: ' + finishdate)
+				const title = tmodelName + (name == '' ? '' : ' ( ' + name + ' ' + groupName + '-' + positionName + ' )')
+				let desc = (finishdate == '' ? '\n' : '办理时间: ' + finishdate)
+				if(task && task.length > 0) {
+					desc += '抄送信息'
+					task.forEach((taskitem) => {
+						desc += '\n抄送对象:(' + taskitem.username + ') 抄送时间: ' + taskitem.createdate
+					})
+				}
 				return {
 					title,
 					desc,
@@ -239,19 +371,27 @@ function queryData(pageNo, pSize, queryType) {
 }
 // 刷新数据
 function reloadData() {
+	isLoading.value = true
 	const params = {
 		pSize: props.pSize,
 		pageNo: props.pageNo,
 	}
-	emits('clickSegment', params, complete)
+	emits('clickSegment', params, (dataList, total, pageNo) => {
+		complete(dataList, total, pageNo)
+		isLoading.value = false
+	})
 }
 // 上拉加载
 function scrollQuery(pageNo, pSize) {
+	isLoading.value = true
 	const params = {
 		pSize,
 		pageNo,
 	}
-	emits('scrollToBottom', params, complete)
+	emits('scrollToBottom', params, (dataList, total, pageNo) => {
+		complete(dataList, total, pageNo)
+		isLoading.value = false
+	})
 }
 
 function handleToDetail(process) { // 跳转流程详情页
@@ -268,9 +408,30 @@ defineExpose({
 <style lang="scss" scoped>
 // @import url("@/static/font/ygoa/iconfont.css");
 
+::v-deep .no-title-collapse .uni-collapse-item__title {
+  display: none !important;
+}
+
+// 简单的加载中样式
+.loading-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 300rpx;
+}
+
+.loading-content {
+  text-align: center;
+}
+
+.loading-text {
+  font-size: 28rpx;
+  color: #999;
+}
+
 .flow_step_section {
 	.uni-section .uni-section-header {
-		padding: 5px 10px;
+		padding: 0px 0px;
 	}
 }
 
@@ -363,4 +524,45 @@ defineExpose({
 		color: #777;
 	}
 }
+// 左上角标签样式
+	.abbreviation-label {
+		margin: 5px 10px 0px 0px;
+		height: 1.5rem;
+	  padding: 0px 2px;
+	  border-radius: 4px;
+	  color: #409eff;
+	  // font-weight: 500;
+	  display: flex;
+	  align-items: center;
+	  justify-content: center;
+	  text-align: center;
+	  white-space: nowrap; // 不换行
+	}
+
+.info-label {
+  color: #666;
+  min-width: 70px;
+  font-size: 28rpx;
+  font-weight: 500;
+}
+.info-value {
+  // color: #333;
+  font-size: 28rpx;
+  flex: 1;
+  // word-break: break-all;
+  overflow: hidden;
+}
+
+// 信息项样式
+.info-item {
+  display: flex;
+  align-items: center;
+  font-size: 0.9rem;
+  line-height: 1.8;
+}
+
+.amount-value {
+  font-weight: 600;
+  color: #e6a23c;
+}
 </style>

+ 39 - 5
pages/message/index.vue

@@ -72,7 +72,7 @@
 import { onBeforeMount, onMounted, onUpdated, ref } from 'vue';
 import { onLoad, onShow } from '@dcloudio/uni-app'
 import { getMessageList, getNoticeList, getUnReadMessageNum, setMsgIsRead } from '@/api/message.js';
-import { getUserProcess, getUnProcessNum } from '@/api/process';
+import { getUserProcess, getUnProcessNum, getTacheInfo } from '@/api/process';
 import $tab from '@/plugins/tab.js';
 import $modal from '@/plugins/modal.js';
 import processList from '@/components/ygoa/processList.vue'
@@ -230,12 +230,12 @@ function getMessagePage({ pSize, pageNo, type, segmentValue }, callback) {
 		callback(returnParams.list, returnParams.total, pageNo)
 	})
 }
-// 点击消息列表项
-function handleToMessageDetail({ messageid, universalid }) {
+//点击消息列表项
+function handleToMessageDetail({ messageid, universalid, jump_id, title }) {
+	// 设置消息已读
 	setMsgIsRead(universalid + ',').then((res) => {
 		if (Number.isInteger(res)) {
-			msgListRef.value.reload();// 调用子组件刷新数据
-			$tab.navigateTo('/pages/message/detail/index?messageId=' + messageid + '&universalId=' + universalid)
+			msgListRef.value.reload();//调用子组件刷新数据
 		} else {
 			$modal.confirm('登录状态失效,您可以继续留在该页面,或者重新登录?').then(res => {
 				const loginInfo = getLoginInfo();
@@ -246,6 +246,40 @@ function handleToMessageDetail({ messageid, universalid }) {
 			}).catch(() => { })
 		}
 	})
+	debugger
+	if(jump_id) {
+		try {
+			// 解析jump_id JSON字符串
+			// const jumpInfo = JSON.parse(jump_id);
+			const { modelId, insId, tinsId, opType, insName } = jump_id;
+			
+			if (opType === 'process') {
+				getTacheInfo(tinsId).then(({returnParams}) => {
+					if(returnParams.oldTacheStatus == 1) {
+						//跳到流程办理页面
+						$tab.navigateTo('/pages/process/detail/index?insId=' + insId + '&tinsId=' + tinsId + '&insName=' + insName + '&control=1')
+					} else {
+						//跳到流程到流程查看页面
+						$tab.navigateTo('/pages/process/detail/index?insId=' + insId + '&insName=' + insName + '&control=0')
+					}
+				})
+				
+			} else if (opType === 'view') {
+				//跳到流程到流程查看页面
+				$tab.navigateTo('/pages/process/detail/index?insId=' + insId + '&insName=' + insName + '&control=0')
+			} else {
+				// 未知操作类型,跳转到消息详情页
+				$tab.navigateTo('/pages/message/detail/index?messageId=' + messageid + '&universalId=' + universalid)
+			}
+		} catch (error) {
+			console.error('jump_id解析失败:', error);
+			// 解析失败,跳转到消息详情页
+			$tab.navigateTo('/pages/message/detail/index?messageId=' + messageid + '&universalId=' + universalid)
+		}
+	} else {
+		// jump_id为空,跳转到消息详情页
+		$tab.navigateTo('/pages/message/detail/index?messageId=' + messageid + '&universalId=' + universalid)
+	}
 }
 
 // AI咨询按钮

+ 8 - 4
pages/mine/index.vue

@@ -42,24 +42,24 @@
 				<view class="function-item" @click="editData">
 					<text class="ygoa_icon icon-edit"></text>
 					<text class="title">编辑资料</text>
-					<text class="desc">></text>
+					<text class="desc"></text>
 				</view>
 
 				<view class="function-item" @click="checkIn">
 					<text class="ygoa_icon icon-checkIn"></text>
 					<text class="title">我的考勤</text>
-					<text class="desc">></text>
+					<text class="desc"></text>
 				</view>
 
 				<view class="function-item" @click="clockIn">
 					<text class="ygoa_icon icon-clockIn"></text>
 					<text class="title">我的打卡</text>
-					<text class="desc">></text>
+					<text class="desc"></text>
 				</view>
 				<view class="function-item" @click="setting">
 					<text class="ygoa_icon icon-setting"></text>
 					<text class="title">应用设置</text>
-					<text class="desc">></text>
+					<text class="desc"></text>
 				</view>
 			</view>
 		</uni-card>
@@ -204,6 +204,10 @@
 		margin-right: 0.5rem;
 	}
 
+	.desc::after {
+		content: ">";
+	}
+
 	.select1 {
 		display: inline-block;
 		width: 50%;

+ 12 - 3
pages/mine/setting/setting.vue

@@ -8,21 +8,21 @@
 				<uni-icons type="locked-filled" size="20"></uni-icons>
 				<text>修改密码</text>
 			</view>
-			<text>></text>
+			<text class="desc"></text>
 		</view>
 		<view class="list-item" @click="checkUpdate">
 			<view>
 				<uni-icons type="refreshempty" size="20"></uni-icons>
 				<text>检查更新</text>
 			</view>
-			<text>></text>
+			<text class="desc"></text>
 		</view>
 		<view class="list-item" @click="clearCache">
 			<view>
 				<uni-icons type="trash-filled" size="20"></uni-icons>
 				<text>清理缓存</text>
 			</view>
-			<text>></text>
+			<text class="desc"></text>
 		</view>
 		<!-- 退出登录按钮 -->
 		<view class="logOut">
@@ -101,4 +101,13 @@ function checkUpdate() {
 		font-size: calc(18px + .5*(1rem - 16px));
 	}
 }
+
+.desc {
+	color: #777;
+	margin-right: 0.5rem;
+}
+
+.desc::after {
+	content: ">";
+}
 </style>

+ 17 - 5
pages/process/detail/index.vue

@@ -242,6 +242,7 @@
 								</template>
 							</up-steps-item>
 							<up-steps-item v-else :title="step.title" :desc="step.desc" :key="index"></up-steps-item>
+							
 						</view>
 					</up-steps>
 				</view>
@@ -340,7 +341,7 @@
 	import config from '@/config.js'
 	import $tab from '@/plugins/tab.js'
 	import $modal from '@/plugins/modal.js'
-	import { getProcessFlowInfo, getProcessFormInfo, getProcessFormInfoInFlow, getProcessFlow, submitProcessFlow, cancelProcessFlow, uploadSignatureImg, uploadSignatureBoardImg, uploadFile, getSeal } from '@/api/process.js'
+	import { getProcessFlowInfo, getProcessFormInfo, getProcessFormInfoInFlow, getProcessFlow, submitProcessFlow, cancelProcessFlow, uploadSignatureImg, uploadSignatureBoardImg, uploadFile, getSeal, getTacheInfo } from '@/api/process.js'
 	import { getAttendanceSegment } from '@/api/work.js'
 	import { useUserStore } from '@/store/user.js'
 	import { getLoginInfo, getSession, setSession } from '@/utils/auth.js'
@@ -476,8 +477,9 @@
 	const isCancel = ref(false)
 	// 获取流程表单
 	function initProcessForm() {
+		debugger
 		if (processInfo.tinsId) {
-			// 待办审批流程表单数据todo
+			// 待办审批流程表单数据todo 
 			getProcessFormInfoInFlow(userStore.user.useId, processInfo).then(({ returnParams }) => {
 				console.log('returnParams', returnParams);
 
@@ -522,16 +524,23 @@
 	})
 	// 获取流程信息
 	function initProcessInfo() {
+		debugger
 		getProcessFlow(userStore.user.useId, processInfo).then(({ returnParams }) => {
 			options.value = returnParams.list.map((item, index) => {
-				const { tmodelName, name, createdate, finishdate, remark, state } = item
+				const { tmodelName, name, createdate, finishdate, remark, state, task, groupName, positionName } = item
 				if (state == 1) {
 					stepActive.value = index
 				}
-				const title = tmodelName + (name == '' ? '' : ' ( ' + name + ' )')
-				const desc = '创建时间: ' + createdate
+				const title = tmodelName + (name == '' ? '' : ' ( ' + name + ' ' + groupName + '-' + positionName + ' )')
+				let desc = '创建时间: ' + createdate
 					+ (finishdate == '' ? '\n' : '\n办理时间: ' + finishdate)
 					+ (remark == '' ? '\n' : '\n环节意见: ' + remark)
+					if(task && task.length > 0) {
+						desc += '抄送信息'
+						task.forEach((taskitem) => {
+							desc += '\n抄送对象:(' + taskitem.username + ') 抄送时间: ' + taskitem.createdate
+						})
+					}
 				return {
 					title,
 					desc,
@@ -1604,4 +1613,7 @@
 			}
 		}
 	}
+	.username {
+		color: #2979ff;
+	}
 </style>

+ 315 - 12
pages/process/index.vue

@@ -3,10 +3,10 @@
 	<view class="index_container">
 		<view class="search_container">
 			<uni-row>
-				<uni-col :xs="6" :sm="4">
+				<uni-col :xs="3" :sm="2">
 					<view @click="openPopup" class="popup_button_container">
 						<text class="ygoa_icon icon-filter"></text>
-						<text class="button_text">{{ candidates[searchItem] }}</text>
+						<!-- <text class="button_text">筛选</text> -->
 					</view>
 				</uni-col>
 				<view class="search_bar">
@@ -20,7 +20,7 @@
 						</uni-search-bar>
 					</uni-col>
 					<!-- 类型 下拉框搜索 -->
-					<uni-col v-else-if="1 == searchItem" :xs="18" :sm="20">
+					<uni-col v-else-if="1 == searchItem" :xs="16" :sm="18">
 						<picker class="picker_container" @change="bindPickerChange" :value="pickerItem"
 							:range="pickerItems" range-key="modelName">
 							<view class="uni-input input_text">
@@ -33,12 +33,18 @@
 						</view>
 					</uni-col>
 					<!-- 时间范围搜索 -->
-					<uni-col v-else-if="2 == searchItem" :xs="18" :sm="20">
+					<uni-col v-else-if="2 == searchItem" :xs="16" :sm="18">
 						<view class="datetime_picker_container">
 							<uni-datetime-picker type="daterange" @change="datetimePickerChange" @clear="cancelSearch"
 								:border="false" />
 						</view>
 					</uni-col>
+					<!-- 清空筛选按钮 -->
+					<uni-col :xs="2" :sm="2">
+						<view @click="cancelSearch" class="clear_button_container">
+							<uni-icons type="clear" size="calc(24px + .5*(1rem - 16px))" color="#999"></uni-icons>
+						</view>
+					</uni-col>
 				</view>
 			</uni-row>
 		</view>
@@ -53,11 +59,63 @@
 		</view>
 		<view class="popup_container">
 			<uni-popup ref="searchItemPopup" type="bottom">
-				<uni-list>
-					<uni-list-item @click="clickSearchItem(index)" v-for="(item, index) in candidates" :key="index"
-						clickable :title="item">
-					</uni-list-item>
-				</uni-list>
+				<!-- 弹窗标题 -->
+				<!-- <view class="popup-title">流程类型分类</view> -->
+				<!-- 流程分类列表 -->
+				<uni-section title="流程类型" type="line" class="uni-section">
+					<uni-collapse :accordion="true">
+						<uni-list class="flow-category-list">
+							<!-- 一级分类:流程类型(遍历ftypeList) -->
+							<uni-collapse-item 
+								class="flow-type-group" 
+								:open="false" 
+								:show-animation="true"
+								v-for="(type, typeIdx) in flowList.ftypeList" 
+								:key="typeIdx"
+								:title="type.typeName"
+							>
+								<template v-slot:title>
+									<view class="group-header">
+										<!-- 流程类型图标(可自定义) -->
+										<uni-icons type="list" size="24rpx" class="group-icon" />
+										<!-- 流程类型名称 -->
+										<text class="group-name">{{ type.typeName }}</text>
+										<!-- 流程类型下的总数量角标 -->
+										<!-- <view class="badge" v-if="getTypeItemCount(type.typeId) > 0">
+											{{ getTypeItemCount(type.typeId) }}
+										</view> -->
+										<uni-badge :text="getTypeItemCount(type.typeId)" v-if="getTypeItemCount(type.typeId) > 0"></uni-badge>
+									</view>
+								</template>
+								<view v-for="(item, index) in flowList.fList" :index="index" :key="item.modelId">
+									<!-- <uni-list-item @click="searchByModelId(item)"  v-if="type.typeId === item.typeId.typeId"
+										clickable  class=""> -->
+										<view 
+										    @click="searchByModelId(item)"  
+										    v-if="type.typeId === item.typeId.typeId"
+										    style="padding: 16rpx 60rpx; background: #fff; border-bottom: 1px solid #f5f5f7;"
+										    class="flow-item-sub"
+										  >
+											<view style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
+												<text class="sub-item-title">{{ item.modelName }}</text>
+												<uni-badge :text="getModelItemCount(item.modelId)" v-if="getModelItemCount(item.modelId) > 0"></uni-badge>
+											</view>
+											<!-- <view class="sub-item-title"> -->
+												<!-- <text>{{ item.modelName }}</text> -->
+												<!-- 单个流程项角标(根据modelId统计待办数量) -->
+												<!-- <view class="badge small-badge" v-if="getModelItemCount(item.modelId) > 0">
+													{{ getModelItemCount(item.modelId) }}
+												</view> -->
+											<!-- </view> -->
+										</view>
+									<!-- </uni-list-item> -->
+							</view>
+							</uni-collapse-item>
+						</uni-list>
+					</uni-collapse>
+				</uni-section>
+				<!-- <uni-section title="发起人" type="line" @click="clickSearchItem('createUser')"></uni-section>
+				<uni-section title="时间" type="line" @click="clickSearchItem('createTime')"></uni-section> -->
 			</uni-popup>
 		</view>
 	</view>
@@ -96,6 +154,16 @@
 				handleToProcessDetail({ username, insId, tinsId, insName, control })
 			})
 		}
+		const staffId = userStore.user.useId
+		const unitId = userStore.user.unitId
+		getProcessList(staffId, unitId).then(res => {
+			flowList.value = res.returnParams // 设置flowList
+			// 先加载完流程列表数据,再获取待办流程数量
+			// 使用 setTimeout 确保流程列表先显示
+			setTimeout(() => {
+				getProcessCountsByType()
+			}, 500) // 延迟500毫秒调用,确保流程列表数据先加载
+		})
 	})
 
 
@@ -105,6 +173,11 @@
 	const searchItem = ref(0)
 	// 搜索项弹出层
 	const searchItemPopup = ref(null)
+	// 待办流程数量按类型分组
+	const processCountsByType = ref({})
+	// 待办流程数量按模型分组
+	const processCountsByModel = ref({})
+	
 	// 打开搜索项弹出层
 	function openPopup() {
 		searchItemPopup.value.open()
@@ -115,8 +188,16 @@
 	}
 	// 选中搜索项
 	function clickSearchItem(item) {
-		searchItem.value = item
-		if (item == 1 && pickerItems.value.length == 1) initPickerItems()
+		debugger
+		// searchItem.value = item
+		// if (item == 1 && pickerItems.value.length == 1) initPickerItems()
+		if(item == "createUser") {
+			
+		} else if(item == "createTime") {
+			
+		} else {
+			search(item.modelId)
+		}
 		closePopup()
 	}
 	// 搜索参数
@@ -136,11 +217,21 @@
 	// 下拉框搜索
 	const pickerItems = ref([{ modelId: '', modelName: '无' }])
 	const pickerItem = ref(0)
+	// 流程列表
+	const flowList = ref({
+		fList: [],
+		ftypeList: [
+			{
+				typeName: ''
+			}
+		],
+	})
 	// 获取流程宫格数据
 	function initPickerItems() {
 		const staffId = userStore.user.useId
 		const unitId = userStore.user.unitId
 		getProcessList(staffId, unitId).then(res => {
+			// flowList.value = res.returnParams // 设置flowList
 			pickerItems.value = res.returnParams.fList.map(({ modelId, modelName }) => {
 				return { modelId, modelName }
 			})
@@ -157,6 +248,13 @@
 	function datetimePickerChange(event) {
 		search(event)
 	}
+	
+	// 搜索
+	function searchByModelId(item) {
+		queryParams.value = { 'modelId': item.modelId };
+		processListRef.value.onClickItem() // 调用子组件刷新数据
+		closePopup()
+	}
 	// 搜索
 	function search(queryParam) {
 		switch (searchItem.value) {
@@ -169,6 +267,17 @@
 				};
 				break;
 		}
+		// debugger
+		// if(queryParam.value == "createUser") {
+		// 	queryParams.value = { 'name': queryParam };
+		// } else if(queryParam.value == "createTime") {
+		// 	queryParams.value = {
+		// 		'starttime': queryParam[0],
+		// 		'endtime': queryParam[1]
+		// 	};
+		// } else {
+		// 	queryParams.value = { 'modelId': queryParam };
+		// }
 		processListRef.value.onClickItem() // 调用子组件刷新数据
 	}
 	// 取消搜索
@@ -176,7 +285,53 @@
 		queryParams.value = {}
 		processListRef.value.onClickItem() // 调用子组件刷新数据
 	}
-
+	
+	// 获取待办流程数量并按类型分组
+	function getProcessCountsByType() {
+		const params = {
+			staffId: userStore.user.useId,
+			page: 1,
+			pageNum: 9999, // 获取所有待办流程
+			modelId: "",
+			control: 1,
+			queryParams: {}
+		}
+		
+		getUserProcess(params).then(({ returnParams }) => {
+			const processList2 = returnParams.list || []
+			// 初始化计数器
+			const countsByType = {}
+			const countsByModel = {}
+			// 遍历流程列表,按typeId和modelId统计数量
+			processList2.forEach(process => {
+				// 根据process.modelId与flowList.value.fList中的item.modelId进行匹配
+				const matchingFlow = flowList.value.fList.find(f => f.modelId === process.modelId)
+				if (matchingFlow && matchingFlow.typeId) {
+					const typeId = matchingFlow.typeId.typeId
+					// 按typeId统计
+					countsByType[typeId] = (countsByType[typeId] || 0) + 1
+					// 按modelId统计
+					countsByModel[process.modelId] = (countsByModel[process.modelId] || 0) + 1
+				}
+			})
+			// 更新响应式数据
+			processCountsByType.value = countsByType
+			processCountsByModel.value = countsByModel
+		}).catch(error => {
+			console.error('获取待办流程数量失败:', error)
+		})
+	}
+	
+	// 根据typeId获取待办数量
+	function getTypeItemCount(typeId) {
+		return processCountsByType.value[typeId] || 0
+	}
+	
+	// 根据modelId获取待办数量
+	function getModelItemCount(modelId) {
+		return processCountsByModel.value[modelId] || 0
+	}
+	
 	// 分段器选项
 	const items = reactive(['我的', '抄送', '待办', '在办', '办结'])
 	// 分段器选项
@@ -244,6 +399,7 @@
 	}
 	// 跳转到流程详情页
 	function handleToProcessDetail({ username, insId, tinsId, insName, control }) {
+		debugger
 		let url = '/pages/process/detail/index?insId=' + insId + '&insName=' + insName + '&control=' + control
 		if (tinsId && current.value != 1) { // 排除抄送流程的tinsId
 			url = url + '&tinsId=' + tinsId
@@ -279,6 +435,12 @@
 </script>
 
 <style lang="scss" scoped>
+	::v-deep .uni-badge {
+		height: calc(1.5rem + 0px) !important;
+		min-width: calc(1.5rem + 0px) !important;
+		line-height: calc(1.375rem + 0px) !important;
+		font-size: calc(1.125rem + 0px) !important;
+	}
 	// @import url("@/static/font/ygoa/iconfont.css");
 	.search_container {
 		padding: 10px 0;
@@ -306,6 +468,22 @@
 			}
 		}
 
+		.clear_button_container {
+			display: flex;
+			justify-content: center;
+			margin-left: 5px;
+			height: 36px;
+			line-height: 36px;
+			width: 100%;
+			background-color: #f5f5f5;
+			text-align: center;
+			color: #999;
+
+			&:active {
+				background-color: #e0e0e0;
+			}
+		}
+
 		::v-deep .search_bar {
 			.uni-searchbar {
 				padding: 0;
@@ -535,4 +713,129 @@
 			}
 		}
 	}
+	
+	
+	/* 弹窗整体样式 */
+		.popup_container {
+			// min-height: 1000rpx;
+			::v-deep .uni-popup__content {
+				border-radius: 16rpx 16rpx 0 0;
+				background-color: #fff;
+			}
+		}
+	
+		/* 弹窗标题 */
+		.popup-title {
+			font-size: 32rpx;
+			font-weight: bold;
+			padding: 20rpx 30rpx;
+			border-bottom: 1px solid #f5f5f7;
+			color: #333;
+		}
+	/* 流程分类列表容器 */
+		.flow-category-list {
+			::v-deep .uni-list {
+				background: #fff;
+				border: none;
+			}
+		}
+	
+		/* 一级分类:流程类型组样式 */
+		.flow-type-group {
+			::v-deep .uni-collapse-item__content {
+				padding: 0;
+				border: none;
+			}
+	
+			.group-header {
+				display: flex;
+				align-items: center;
+				padding: 20rpx 30rpx;
+				width: 100%;
+				box-sizing: border-box;
+	
+				.group-icon {
+					margin-right: 16rpx;
+					color: #666;
+				}
+	
+				.group-name {
+					font-size: 28rpx;
+					flex: 1;
+					color: #333;
+				}
+	
+				.group-arrow {
+					color: #999;
+					margin-left: 10rpx;
+				}
+			}
+		}
+	
+		/* 二级分类:流程项样式 */
+		.flow-item-sub {
+			::v-deep .uni-list-item__container {
+				padding: 16rpx 60rpx; /* 缩进显示,区分一级分类 */
+				border: none;
+				// 点击时的高亮效果(可选)
+				&:active {
+					background-color: #F0F0F0; // 点击时加深一点,提升交互体验
+				}
+			}
+	
+			.sub-item-title {
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+				font-size: 26rpx;
+				color: #666;
+				width: 100%;
+				box-sizing: border-box;
+			}
+		}
+	
+		/* 通用角标样式 */
+		.badge {
+			min-width: 36rpx;
+			height: 36rpx;
+			line-height: 36rpx;
+			text-align: center;
+			background-color: #ff4d4f;
+			color: #fff;
+			font-size: 22rpx;
+			border-radius: 18rpx;
+			padding: 0 8rpx;
+			margin-right: 10rpx;
+			display: inline-block !important;
+		}
+	
+		/* 小角标(单个流程项使用,可选) */
+		.small-badge {
+			min-width: 30rpx;
+			height: 30rpx;
+			line-height: 30rpx;
+			font-size: 20rpx;
+			display: inline-block !important;
+		}
+	::v-deep .uni-section {
+		overflow-y: scroll;
+		max-height: 1000rpx;
+		min-height: 1000rpx;
+		background-color: #fff;
+	}
+
+// 给 uni-list-item 加相对定位,约束 right 插槽
+  ::v-deep .uni-list-item {
+    position: relative !important; // 关键:约束绝对定位的 right 插槽
+    width: 100% !important; // 确保列表项占满父容器
+    box-sizing: border-box !important;
+  }
+
+  // 调整 uni-badge 样式,避免溢出
+  ::v-deep .uni-badge {
+    position: static !important; // 取消绝对定位,改为静态流布局
+    margin-left: 10rpx !important; // 与文字保持间距
+    display: inline-block !important;
+	background-color: #ff4d4f !important;
+  }
 </style>

+ 136 - 1
pages/work/edit/index.vue

@@ -85,6 +85,24 @@
 								:end="`${formElements[0].defaultValue} 23:59:59`" @change="bukaTimeChange" />
 							<!-- 年月日 -->
 							<uni-datetime-picker v-else-if="'3' == elem.type" v-model="elem.defaultValue" type="date" />
+							<!-- 签名 -->
+							<view v-else-if="'13' == elem.type">
+								<view v-if="elem.defaultValue == ''">
+									<uni-row>
+										<uni-col :span="24">
+											<button type="primary" @click="handleSignature(index)">手动签名</button>
+										</uni-col>
+										<uni-col :span="24">
+											<button style="margin-top: 5px;" type="primary" @click="onclickAutosealButton(index)">一键签名</button>
+										</uni-col>
+									</uni-row>
+								</view>
+								<view v-else class="signature_img">
+									<img style="width: 100%;" mode="widthFix" @click="handleSignature(index)"
+										:src="config.baseUrlPre + elem.sealImgPath"
+										:alt="elem.elementName + '签名'" />
+								</view>
+							</view>
 						</uni-forms-item>
 					</view>
 				</uni-forms>
@@ -140,6 +158,35 @@
 		</view>
 	</view>
 	<view style="height: 5px; margin-top: 10px;"></view>
+	<!-- 签字版弹出层 -->
+	<uni-popup ref="signaturePopup" @maskClick="closeSignaturePopup">
+		<view class="signature_container" :class="{ 'signature_container_landscape': isLandscape }">
+			<view class="signature_content">
+				<l-signature ref="signatureRef" v-if="signaturePopupShow" :landscape="isLandscape" :penSize="8"
+					:minLineWidth="4" :maxLineWidth="12" :openSmooth="true" :preferToDataURL="true"
+					backgroundColor="#ffffff" penColor="black"></l-signature>
+			</view>
+			<view class="signature_button_container">
+				<uni-row :gutter="10">
+					<uni-col :span="6">
+						<button type="warn" @click="onclickSignatureButton('undo')">撤销</button>
+					</uni-col>
+					<uni-col :span="6">
+						<button type="warn" @click="onclickSignatureButton('clear')">清空</button>
+					</uni-col>
+					<uni-col :span="6">
+						<button type="primary" @click="onclickSignatureButton('save')">保存</button>
+					</uni-col>
+					<uni-col :span="6">
+						<button @click="onclickSignatureButton('landscape')">全屏</button>
+					</uni-col>
+					<!-- <uni-col :span="24">
+						<button type="primary" @click="onclickAutosealButton()">一键签名</button>
+					</uni-col> -->
+				</uni-row>
+			</view>
+		</view>
+	</uni-popup>
 </template>
 
 <script setup lang="ts">
@@ -152,6 +199,8 @@ import { convertToChineseCurrency } from '@/utils/ygoa.js'
 import { calCommonExp } from '@/utils/rpn.js'
 import { getProcessInfo, getProcessForm, submitProcessForm, uploadFile, getAttendanceSegment } from '@/api/work.js'
 import { useConfigStore } from '@/store/config.js'
+import { getProcessFlowInfo, getProcessFormInfo, getProcessFormInfoInFlow, getProcessFlow, submitProcessFlow, cancelProcessFlow, uploadSignatureImg, uploadSignatureBoardImg, getSeal } from '@/api/process.js'
+import config from '@/config.js'
 
 const configStore = useConfigStore()
 const fieldTypeDict = {
@@ -716,7 +765,7 @@ function validateRepeatingForm2() {
 		for (const elem in item) {
 			if (item[elem] == '') {
 				const name = repeatingForm.value.elementItem[ItemIndex].elementName.slice(3)
-				$modal.msgError(`详情表 ${name} 数据填写错误`)
+				$modal.msgError(`详情表${name}数据填写错误`)
 				flag = false
 				return
 			}
@@ -883,6 +932,92 @@ function submitProcess() { // 提交表单
 		$modal.msgError('表单填写错误')
 	});
 }
+
+	const signaturePopup = ref(null)
+	const signatureRef = ref(null)
+	const signaturePopupShow = ref(false)
+	const isLandscape = ref(false)
+	const signatureItemIndex = ref(-1)
+	function handleSignature(index) {
+		signatureItemIndex.value = index
+		signaturePopup.value.open()
+		initSignature()
+	}
+	// 初始化签字板
+	function initSignature() {
+		signaturePopupShow.value = false
+		setTimeout(() => {
+			signaturePopupShow.value = true
+		}, 100)
+	}
+	// 点击签字板按钮
+	function onclickSignatureButton(event) {
+		// console.log('onclickSignatureButton: ', event);
+		switch (event) {
+			case 'undo':
+				signatureRef.value.undo()
+				break;
+			case 'clear':
+				signatureRef.value.clear()
+				break;
+			case 'save':
+				signatureRef.value.canvasToTempFilePath({
+					success: (res : LSignatureToFileSuccess) => {
+						if (res.isEmpty) {
+							$modal.msgError('签名不能为空!')
+							return
+						}
+						// 判断上传文件是否是base64
+						if (res.tempFilePath.substring(0, 'data:image/png;base64,'.length) == 'data:image/png;base64,') {
+							const _fileData = res.tempFilePath
+							uploadSignatureBoardImg(userStore.user.useId, _fileData, formElements.value[signatureItemIndex.value].tableField)
+								.then(({ returnParams }) => {
+									formElements.value[signatureItemIndex.value].defaultValue = returnParams.sealInsID
+									formElements.value[signatureItemIndex.value].sealImgPath = returnParams.path
+									signatureItemIndex.value = -1
+									signaturePopupShow.value = false
+									signaturePopup.value.close()
+								})
+						} else {
+							// 转 base64
+							wx.getFileSystemManager().readFile({
+								filePath: res.tempFilePath,
+								encoding: 'base64',
+								success: fileData => {
+									const _fileData = 'data:image/png;base64,' + fileData.data
+									uploadSignatureBoardImg(userStore.user.useId, _fileData, formElements.value[signatureItemIndex.value].tableField)
+										.then(({ returnParams }) => {
+											formElements.value[signatureItemIndex.value].defaultValue = returnParams.sealInsID
+											formElements.value[signatureItemIndex.value].sealImgPath = returnParams.path
+											signatureItemIndex.value = -1
+											signaturePopupShow.value = false
+											signaturePopup.value.close()
+										})
+								}
+							})
+						}
+					}
+				} as LSignatureToTempFilePathOptions)
+				break;
+			case 'landscape':
+				isLandscape.value = !isLandscape.value
+				initSignature()
+				break;
+		}
+	}
+	function onclickAutosealButton(index) {
+		if (signatureItemIndex.value != -1) index = signatureItemIndex.value
+		getSeal(userStore.user.useId).then(({ returnParams }) => {
+			formElements.value[index].defaultValue = returnParams.sealFileId.universalid
+			formElements.value[index].sealImgPath = returnParams.sealFileId.path
+			signaturePopupShow.value = false
+			signaturePopup.value.close()
+		})
+	}
+	function closeSignaturePopup() {
+		signatureItemIndex.value = -1
+		signaturePopupShow.value = false
+	}
 </script>
 
 <style lang="scss" scoped>

+ 28 - 12
pages/work/index.vue

@@ -3,17 +3,25 @@
 	<view class="work-container">
 		<!-- 宫格组件 -->
 		<view v-if="processList.fList.length != 0">
-			<uni-section :title="processList.ftypeList[0].typeName" titleFontSize="1.3rem" type="line"></uni-section>
-			<view class="grid-body">
-				<uni-grid :column="uni_grid_column" :showBorder="false" @change="changeProcessGrid">
-					<uni-grid-item v-for="(item, index) in processList.fList" :index="index" :key="index">
-						<view class="grid-item-box">
-							<text class="ygoa_work_icon"
-								:class="item.modelPicture == 'default.png' ? 'icon-outsourcing' : item.modelPicture"></text>
-							<text class="text">{{ item.modelName }}</text>
+			<view v-for="(type, typeIndex) in processList.ftypeList" :key="typeIndex">
+				<uni-section :title="type.typeName" titleFontSize="1.3rem" type="line"></uni-section>
+				<view class="grid-body">
+					<uni-grid :column="uni_grid_column" :showBorder="false"  >
+						<view v-for="(item, index) in processList.fList" :index="index" :key="index" >
+							<view v-if="type.typeId === item.typeId.typeId" @click="changeProcessGrid(item)">
+								<uni-grid-item >
+									<view class="grid-item-box">
+										<text class="ygoa_work_icon"
+											:class="item.modelPicture == 'default.png' ? 'icon-outsourcing' : item.modelPicture"></text>
+										<view class="modelName">
+										<text class="text">{{ item.modelName }}</text>
+										</view>
+									</view>
+								</uni-grid-item>
+							</view>
 						</view>
-					</uni-grid-item>
-				</uni-grid>
+					</uni-grid>
+				</view>
 			</view>
 		</view>
 		<uni-section title="考勤管理" titleFontSize="1.3rem" type="line"></uni-section>
@@ -198,9 +206,13 @@ function initProcessList() {
 	})
 }
 // 点击流程宫格
-function changeProcessGrid(e) { 
+function changeProcessGrid(item) { 
 	// console.log('changeProcessGrid', e);
-	const { modelName, modelId, control } = processList.value.fList[e.detail.index]
+	// const { modelName, modelId, control } = processList.value.fList[e.detail.index]
+	const { modelName, modelId, control } = item
+	// const modelName = item.value.modelName
+	// const modelId = item.value.modelId
+	// const control = item.value.control
 	// 跳转流程申请页面
 	$tab.navigateTo('/pages/work/edit/index?modelName=' + modelName + '&modelId=' + modelId + '&control=' + control)
 }
@@ -290,6 +302,10 @@ view {
 	font-size: calc(26rpx + 1.5*(1rem - 16px));
 	margin-top: 10rpx;
 }
+.modelName {
+	max-width: 120rpx;
+	text-align: center;
+}
 .work-container {
 	.grid-item-box {
 		flex: 1;

+ 20 - 4
static/font/ygoa/work/iconfont.css

@@ -1,10 +1,10 @@
 @font-face {
   font-family: "ygoa_work_icon"; /* Project id 4734750 */
   /* Color fonts */
-  src:
-       url('iconfont.woff2?t=1757679033941') format('woff2'),
-       url('iconfont.woff?t=1757679033941') format('woff'),
-       url('iconfont.ttf?t=1757679033941') format('truetype');
+  src: 
+       url('iconfont.woff2?t=1765880383466') format('woff2'),
+       url('iconfont.woff?t=1765880383466') format('woff'),
+       url('iconfont.ttf?t=1765880383466') format('truetype');
 }
 
 .ygoa_work_icon {
@@ -15,6 +15,18 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-gongzifafang:before {
+  content: "\e608";
+}
+
+.icon-ruzhishenqing1:before {
+  content: "\e606";
+}
+
+.icon-hetongshenpi:before {
+  content: "\e607";
+}
+
 .icon-zhuanzheng:before {
   content: "\e603";
 }
@@ -63,6 +75,10 @@
   content: "\e6f5";
 }
 
+.icon-checkIn:before {
+  content: "\e60a";
+}
+
 .icon-outsourcing:before {
   content: "\e601";
 }

BIN
static/font/ygoa/work/iconfont.ttf


BIN
static/font/ygoa/work/iconfont.woff


BIN
static/font/ygoa/work/iconfont.woff2