processList.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. <template>
  2. <view class="content" :style="`height: ${contentHeight}`">
  3. <z-paging :fixed="false" @query="queryData" :value="list" :default-page-size="pSize" :default-page-no="pageNo"
  4. ref="paging">
  5. <!-- 简单的加载中提示 -->
  6. <view v-if="isLoading && list.length === 0" class="loading-container">
  7. <view class="loading-content">
  8. <text class="loading-text">加载中...</text>
  9. </view>
  10. </view>
  11. <!-- 列表项 -->
  12. <view v-for="(process, index) in list" :key="index">
  13. <uni-card @click="handleToDetail(process)" :isFull="true" padding="10px 0">
  14. <uni-row>
  15. <!-- <uni-col :xs="3" :sm="2">
  16. <view class="icon_container">
  17. <text style="font-size: calc(40px + 0.5 * (1rem - 16px));" class="ygoa_work_icon"
  18. :class="iconDict && iconDict[process.modelName] ? iconDict[process.modelName] : 'icon-outsourcing'"></text>
  19. </view>
  20. </uni-col> -->
  21. <uni-col :xs="24" :sm="24">
  22. <uni-card padding="0" :isFull="true" :border="false" :is-shadow="false">
  23. <template v-slot:title>
  24. <uni-row>
  25. <uni-col :xs="4" :sm="2">
  26. <text class="abbreviation-label" :style="`background-color: ${getLabelBgColor(process.abbreviation)}`">
  27. {{ process.abbreviation || '默认' }}
  28. </text>
  29. </uni-col>
  30. <uni-col :xs="15" :sm="20" v-if="current !== 0">
  31. <view class="process_title uni-ellipsis">
  32. <text>{{ process.insName }}</text>
  33. </view>
  34. </uni-col>
  35. <uni-col :xs="19" :sm="22" v-else>
  36. <view class="process_title uni-ellipsis">
  37. <text>{{ process.insName }}</text>
  38. </view>
  39. </uni-col>
  40. <uni-col :xs="5" :sm="2" v-if="current !== 0">
  41. <view class="process_status">
  42. <uni-tag v-if="current === 2" text="审批中" type="success"></uni-tag>
  43. <uni-tag v-else-if="current === 3" text="审批中" type="success"></uni-tag>
  44. <uni-tag v-else-if="current === 4" text="已办结" type="primary"></uni-tag>
  45. </view>
  46. </uni-col>
  47. </uni-row>
  48. </template>
  49. <view class="process_contant">
  50. <uni-row>
  51. <uni-col :xs="process.isCancel == '1'?20:24" :sm="process.isCancel == '1'?20:24">
  52. <view v-if="process.tmodelName == undefined">
  53. <!-- <uni-row v-if="process.insSubName != ''">
  54. <uni-col :xs="24" :sm="24">
  55. <text style="color: #79abff;font-weight: bold;">{{ process.insSubName }}</text>
  56. </uni-col>
  57. </uni-row>
  58. <uni-row>
  59. <uni-col :xs="8" :sm="7">流程类型:</uni-col>
  60. <uni-col :xs="16" :sm="17">{{ process.typeName || '无'
  61. }}</uni-col>
  62. </uni-row>
  63. <uni-row>
  64. <uni-col :xs="8" :sm="7">创建时间:</uni-col>
  65. <uni-col :xs="16" :sm="17">{{ process.createdate }}</uni-col>
  66. </uni-row> -->
  67. <view v-if="process.insSubName2" v-for="(item, idx) in parseInsSubName2(process.insSubName2)" :key="idx" class="info-item">
  68. <text class="info-label">
  69. {{ item.key }}:
  70. </text>
  71. <text class="info-value uni-ellipsis"
  72. :class="{
  73. 'amount-value': item.key.includes('金额'),
  74. 'timeout-value': item.key === '待办超时'
  75. }">
  76. {{ item.value }}
  77. </text>
  78. </view>
  79. <view v-else>
  80. <view class="info-item">
  81. <text v-if="process.insSubName != ''" class="info-label" :xs="24" :sm="24">
  82. <text style="color: #79abff;font-weight: bold;">{{ process.insSubName }}</text>
  83. </text>
  84. </view>
  85. <view class="info-item">
  86. <text class="info-label">当前流程:</text>
  87. <text class="info-value uni-ellipsis">{{ process.tmodelName || '无'}}</text>
  88. </view>
  89. </view>
  90. <!-- <view class="info-item">
  91. <text class="info-label">
  92. 创建时间:
  93. </text>
  94. <text class="info-value">
  95. {{ process.createdate }}
  96. </text>
  97. </view> -->
  98. </view>
  99. <view v-else @click.stop.prevent="handleCollapseChange(process, index)">
  100. <!-- <uni-row v-if="process.insSubName != ''">
  101. <uni-col :xs="24" :sm="24">
  102. <text style="color: #79abff;font-weight: bold;">{{ process.insSubName }}</text>
  103. </uni-col>
  104. </uni-row>
  105. <uni-row>
  106. <uni-col :xs="8" :sm="7">当前流程:</uni-col>
  107. <uni-col :xs="16" :sm="17">
  108. <text style="color: #79abff; text-decoration: underline;">{{
  109. process.tmodelName }}</text>
  110. </uni-col>
  111. </uni-row>
  112. <uni-row>
  113. <uni-col :xs="8" :sm="7">创建时间:</uni-col>
  114. <uni-col :xs="16" :sm="17">{{ process.createdate }}</uni-col>
  115. </uni-row> -->
  116. <view v-if="process.insSubName2" v-for="(item, idx) in parseInsSubName2(process.insSubName2)" :key="idx" class="info-item">
  117. <text class="info-label" :class="{ 'amount-label': item.key === '合计金额' || item.key === '金额' }">
  118. {{ item.key }}:
  119. </text>
  120. <text class="info-value uni-ellipsis"
  121. :class="{
  122. 'amount-value': item.key === '合计金额' || item.key === '金额',
  123. 'timeout-value': item.key === '待办超时'
  124. }">
  125. {{ item.value }}
  126. </text>
  127. <!-- <uni-row class="info-label">
  128. <uni-col :xs="8" :sm="7">{{ item.key }}:</uni-col>
  129. <uni-col :xs="16" :sm="17">{{ item.value }}</uni-col>
  130. </uni-row> -->
  131. </view>
  132. <view v-else>
  133. <view class="info-item">
  134. <text v-if="process.insSubName != ''" class="info-label" :xs="24" :sm="24">
  135. <text style="color: #79abff;font-weight: bold;">{{ process.insSubName }}</text>
  136. </text>
  137. </view>
  138. <view class="info-item">
  139. <text class="info-label">当前流程:</text>
  140. <text class="info-value uni-ellipsis">{{ process.tmodelName || '无'}}</text>
  141. </view>
  142. </view>
  143. <view class="info-item">
  144. <text class="info-label">
  145. 创建时间:
  146. </text>
  147. <text class="info-value">
  148. {{ process.createdate }}
  149. </text>
  150. </view>
  151. </view>
  152. </uni-col>
  153. <uni-col :xs="4" :sm="4" v-if="process.isCancel == '1'">
  154. <uni-row>
  155. <view class="button_container">
  156. <button type="warn" size="mini"
  157. @click.stop.prevent="handleToCancel(process)">撤回</button>
  158. </view>
  159. </uni-row>
  160. </uni-col>
  161. </uni-row>
  162. </view>
  163. </uni-card>
  164. </uni-col>
  165. </uni-row>
  166. </uni-card>
  167. <uni-collapse v-if="process.flowStepItem != undefined && process.flowStepItem.show">
  168. <uni-collapse-item class="no-title-collapse" :border="false" :show-arrow="false" :open="process.flowStepItem.show" title-border="none">
  169. <template v-slot:title>
  170. <view class="flow_step_section">
  171. <!-- <uni-section title="流转过程" type="line" titleFontSize="0.8rem"></uni-section> -->
  172. </view>
  173. </template>
  174. <view class="flow_step_container">
  175. <up-steps class="up_step_view" :current="process.flowStepItem.stepActive"
  176. activeColor="#18bc37" inactiveColor="#2979ff" direction="column">
  177. <view v-for="(step, index) in process.flowStepItem.options" :key="index">
  178. <up-steps-item :contentClass="'redcontent'" v-if="step.state == '3'"
  179. :title="step.title + ' 退回'" :desc="step.desc" error></up-steps-item>
  180. <up-steps-item :contentClass="'redcontent'" v-else-if="step.state == '0'"
  181. :title="step.title + ' 撤销'" :desc="step.desc" error></up-steps-item>
  182. <up-steps-item v-else-if="step.state == '8' || step.state == '9'"
  183. :title="step.title + ' 回收'" :desc="step.desc" error></up-steps-item>
  184. <up-steps-item v-else-if="index == process.flowStepItem.stepActive"
  185. :title="step.title" :desc="step.desc">
  186. <template #icon>
  187. <view class="active_step_circle">
  188. <text class="active_step_text">{{ Number(index) + 1 }}</text>
  189. </view>
  190. </template>
  191. </up-steps-item>
  192. <up-steps-item v-else :title="step.title" :desc="step.desc"></up-steps-item>
  193. </view>
  194. </up-steps>
  195. </view>
  196. </uni-collapse-item>
  197. </uni-collapse>
  198. </view>
  199. </z-paging>
  200. </view>
  201. </template>
  202. <script setup lang="ts">
  203. import { ref } from 'vue';
  204. import { getProcessFlow } from '@/api/process'
  205. import { useUserStore } from '@/store/user.js'
  206. const userStore = useUserStore()
  207. const iconDict = {
  208. '外协结算申请': 'icon-outsourcing',
  209. '用车申请': 'icon-apply-car',
  210. '出差申请': 'icon-apply-business',
  211. '外出申请': 'icon-apply-out',
  212. '费用报销申请': 'icon-apply-expense',
  213. '加班申请': 'icon-apply-overtime',
  214. '请假申请': 'icon-apply-leave',
  215. '采购申请': 'icon-apply-purchase',
  216. '补卡申请': 'icon-buka',
  217. '合同会签': 'icon-apply-sign-contract',
  218. '付款申请': 'icon-fukuan',
  219. '私车公用': 'icon-sichegongyong',
  220. '商务餐': 'icon-shangwucan',
  221. '离职申请': 'icon-lizhi',
  222. '人员变动': 'icon-renyuanbiandong',
  223. '公车维修': 'icon-gongcheweixiu',
  224. '办公用品': 'icon-bangongyongpin',
  225. '借款申请': 'icon-jiekuan',
  226. '签呈申请': 'icon-qiancheng',
  227. }
  228. const props = defineProps({
  229. contentHeight: { type: String, default: '85vh' },
  230. current: { type: Number, default: 0 }, // 消息类型
  231. pSize: { type: Number, default: 10 }, // 分页大小
  232. pageNo: { type: Number, default: 1 }, // 默认页
  233. })
  234. const emits = defineEmits([
  235. 'clickSegment', // 点击分段器
  236. 'clickItem', // 点击内容项
  237. 'scrollToBottom', // 到达底部
  238. 'clickCancel', // 点击取消按钮
  239. ])
  240. const paging = ref(null)
  241. // 加载完成 更新数据
  242. const list = ref([])
  243. const totalPage = ref(0)
  244. const isLoading = ref(false) // 添加加载状态
  245. /**
  246. * 根据标签名称获取背景色
  247. * @param label 标签名称(如"报销"、"申请")
  248. * @returns 背景色值
  249. */
  250. function getLabelBgColor(label) {
  251. if (!label) return '#f5f5f5';
  252. // 自定义标签颜色映射
  253. const colorMap = {
  254. '报销': '#e1f3ff',
  255. '申请': '#f0f9eb',
  256. '审批': '#fff7e6',
  257. '结算': '#fef0f0',
  258. '借款': '#f5fafe',
  259. '采购': '#f9f5ff',
  260. '付款': '#f9f5ff'
  261. };
  262. return colorMap[label] || '#f5f5f5';
  263. }
  264. /**
  265. * 解析标准JSON格式的process.insSubName2
  266. * 支持格式:[{"项目名称":"测试测试"},{"合计金额":"10000.00"}]
  267. * @param jsonStr 标准JSON字符串
  268. * @returns 键值对数组,格式如[{key: '项目名称', value: '测试测试'}, ...]
  269. */
  270. function parseInsSubName2(data) {
  271. //debugger
  272. if (!data || data == null) {
  273. return [];
  274. }
  275. let arr = data;
  276. // 统一处理数组,转为键值对格式
  277. return arr
  278. .filter(item => typeof item === 'object' && item !== null && Object.keys(item).length > 0)
  279. .map(item => {
  280. const key = Object.keys(item)[0];
  281. return {
  282. key: key || '',
  283. value: item[key] !== undefined ? item[key].toString() : ''
  284. };
  285. });
  286. }
  287. function handleCollapseChange(process, index) {
  288. // console.log('handleCollapseChange', process);
  289. if (list.value[index].flowStepItem == undefined) {
  290. getProcessFlow(userStore.user.useId, process).then(({ returnParams }) => {
  291. const flowStepItem = {
  292. options: [],
  293. stepActive: -1,
  294. show: true
  295. }
  296. flowStepItem.options = returnParams.list.map((item, index) => {
  297. const { tmodelName, name, createdate, finishdate, remark, state, task, groupName, positionName } = item
  298. if (state == 1) {
  299. flowStepItem.stepActive = index
  300. }
  301. const title = tmodelName + (name == '' ? '' : ' ( ' + name + ' ' + groupName + '-' + positionName + ' )')
  302. let desc = (finishdate == '' ? '\n' : '办理时间: ' + finishdate)
  303. if(task && task.length > 0) {
  304. desc += '抄送信息'
  305. task.forEach((taskitem) => {
  306. desc += '\n抄送对象:(' + taskitem.username + ') 抄送时间: ' + taskitem.createdate
  307. })
  308. }
  309. return {
  310. title,
  311. desc,
  312. state
  313. }
  314. })
  315. if (flowStepItem.stepActive === -1) flowStepItem.stepActive = returnParams.list.length
  316. list.value[index]['flowStepItem'] = flowStepItem
  317. })
  318. return
  319. }
  320. list.value[index].flowStepItem.show = !list.value[index].flowStepItem.show
  321. }
  322. function complete(dataList, total, pageNo) {
  323. totalPage.value = Math.ceil(total / props.pSize)
  324. if (pageNo <= totalPage.value) {
  325. list.value.push(...dataList);
  326. }
  327. // 第一页直接加载数据
  328. if (pageNo === 1) {
  329. list.value = dataList
  330. paging.value.complete(dataList)
  331. return
  332. }
  333. // 防止重复获取最后一次信息
  334. if (props.pSize * pageNo < total) {
  335. paging.value.complete(dataList)
  336. } else {
  337. paging.value.complete([])
  338. }
  339. }
  340. // 点击分段器
  341. function onClickItem() {
  342. // 重新加载数据 pageNo恢复为默认值
  343. list.value = []
  344. paging.value.reload()
  345. }
  346. // 刷新
  347. function queryData(pageNo, pSize, queryType) {
  348. switch (queryType) {
  349. case 0: // 下拉刷新
  350. case 1: // 初始加载
  351. reloadData()
  352. break
  353. case 3: // 上拉加载
  354. scrollQuery(pageNo, pSize)
  355. break
  356. default: // 默认刷新
  357. reloadData()
  358. break
  359. }
  360. }
  361. // 刷新数据
  362. function reloadData() {
  363. isLoading.value = true
  364. const params = {
  365. pSize: props.pSize,
  366. pageNo: props.pageNo,
  367. }
  368. emits('clickSegment', params, (dataList, total, pageNo) => {
  369. complete(dataList, total, pageNo)
  370. isLoading.value = false
  371. })
  372. }
  373. // 上拉加载
  374. function scrollQuery(pageNo, pSize) {
  375. isLoading.value = true
  376. const params = {
  377. pSize,
  378. pageNo,
  379. }
  380. emits('scrollToBottom', params, (dataList, total, pageNo) => {
  381. complete(dataList, total, pageNo)
  382. isLoading.value = false
  383. })
  384. }
  385. function handleToDetail(process) { // 跳转流程详情页
  386. emits('clickItem', process)
  387. }
  388. function handleToCancel(process) {
  389. emits('clickCancel', process)
  390. }
  391. defineExpose({
  392. onClickItem,
  393. });
  394. </script>
  395. <style lang="scss" scoped>
  396. // @import url("@/static/font/ygoa/iconfont.css");
  397. ::v-deep .no-title-collapse .uni-collapse-item__title {
  398. display: none !important;
  399. }
  400. // 简单的加载中样式
  401. .loading-container {
  402. display: flex;
  403. justify-content: center;
  404. align-items: center;
  405. height: 300rpx;
  406. }
  407. .loading-content {
  408. text-align: center;
  409. }
  410. .loading-text {
  411. font-size: 28rpx;
  412. color: #999;
  413. }
  414. .flow_step_section {
  415. .uni-section .uni-section-header {
  416. padding: 0px 0px;
  417. }
  418. }
  419. .flow_step_container {
  420. min-height: 100px;
  421. .up_step_view {
  422. ::v-deep .u-steps {
  423. .u-steps-item {
  424. padding-bottom: 11px;
  425. .redcontent {
  426. .u-text__value--content {
  427. color: #ff4500;
  428. }
  429. }
  430. .active_step_circle {
  431. width: 20px;
  432. height: 20px;
  433. box-sizing: border-box;
  434. flex-shrink: 0;
  435. border-radius: 100px;
  436. border-width: 1px;
  437. border-color: #A78BFA;
  438. background-color: #A78BFA;
  439. border-style: solid;
  440. display: flex;
  441. flex-direction: row;
  442. align-items: center;
  443. justify-content: center;
  444. transition: background-color .3s;
  445. .active_step_text {
  446. color: #fff;
  447. font-size: 0.6875rem;
  448. display: flex;
  449. flex-direction: row;
  450. align-items: center;
  451. justify-content: center;
  452. text-align: center;
  453. line-height: 0.6875rem;
  454. }
  455. }
  456. }
  457. .u-steps-item view:last-of-type {
  458. margin-top: 0 !important;
  459. .u-text__value--content {
  460. font-size: 1rem !important;
  461. }
  462. .u-text__value--main {
  463. font-size: 1rem !important;
  464. }
  465. }
  466. }
  467. }
  468. }
  469. .content {
  470. .icon_container {
  471. margin: 45% 0;
  472. }
  473. .process_title {
  474. // TODO 长度限制15字
  475. line-height: 1.5rem;
  476. font-weight: 500;
  477. font-size: 1.1rem;
  478. margin: 5px 0;
  479. color: #333;
  480. }
  481. .process_status {
  482. margin: 5px 0;
  483. height: 1.5rem;
  484. display: flex;
  485. align-items: center;
  486. justify-content: center;
  487. text-align: center;
  488. white-space: nowrap;
  489. overflow: hidden;
  490. }
  491. .process_contant {
  492. color: #777;
  493. }
  494. }
  495. // 左上角标签样式
  496. .abbreviation-label {
  497. margin: 5px 10px 0px 0px;
  498. height: 1.5rem;
  499. padding: 0px 2px;
  500. border-radius: 4px;
  501. color: #409eff;
  502. // font-weight: 500;
  503. display: flex;
  504. align-items: center;
  505. justify-content: center;
  506. text-align: center;
  507. white-space: nowrap; // 不换行
  508. }
  509. .info-label {
  510. color: #666;
  511. min-width: 70px;
  512. font-size: 28rpx;
  513. font-weight: 500;
  514. }
  515. .info-value {
  516. // color: #333;
  517. font-size: 28rpx;
  518. flex: 1;
  519. // word-break: break-all;
  520. overflow: hidden;
  521. }
  522. // 信息项样式
  523. .info-item {
  524. display: flex;
  525. align-items: center;
  526. font-size: 0.9rem;
  527. line-height: 1.8;
  528. }
  529. .amount-value {
  530. font-weight: 600;
  531. color: #e6a23c;
  532. }
  533. </style>