BasicInfo.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. <script setup name="BasicInfo">
  2. import { storeToRefs } from 'pinia'
  3. import useFlowStore from '@/store/modules/flow'
  4. import { ref, nextTick, onMounted, computed, watch, onBeforeUnmount } from 'vue'
  5. import UseSelect from '@/components/scWorkflow/select'
  6. import flowDefinition from '@/api/flow/definition'
  7. import process from '@/api/flow/process'
  8. import { useRoute } from 'vue-router'
  9. const route = useRoute()
  10. // 缓存 start
  11. import { EVENT_ENUM, EVENT_BUS, cacheTriggerFunc } from '@/utils/cacheHelper'
  12. const flowStore = useFlowStore()
  13. const { flowName, flowProcessId } = storeToRefs(flowStore)
  14. const cacheLoading = ref(false)
  15. const cacheLoadingNum = ref(600)
  16. const cacheLoadingFr = 1000
  17. const cacheLoadingIndex = ref(null)
  18. const cacheIndex = ref(null) // 缓存索引
  19. const cacheUpdateFr = 3000 // 缓存更新频率
  20. // 缓存 end
  21. const visiblePopover = ref(false)
  22. const selectVisible = ref(false)
  23. const useSelectRef = ref(null)
  24. const nodeRoleList = ref([])
  25. const nodeRoleManageList = ref([])
  26. const currentNode = ref('nodeRoleList')
  27. const formRef = ref()
  28. const options = ref([])
  29. const flowInfo = ref({
  30. processKey: '', // 流程唯一标识 key
  31. processName: '', // 流程定义名称
  32. processIcon: 'https://lf3-ea.bytetos.com/obj/goofy/ee/approval/approval-admin/image/iconLib/v5/cart.svg', // 流程图标
  33. categoryId: '', // 流程组分类ID
  34. useScope: 0, // 使用范围 0,全员 1,指定人员(业务关联) 2,均不可提交
  35. processActorList: [
  36. {
  37. actorId: 0, // 参与者ID
  38. actorName: '', // 参与者
  39. actorType: 0 // 参与者类型 0,用户 1,部门 2,用户组
  40. }
  41. ], // 流程参与者,当使用范围为指定人员时候设置
  42. processPermissionList: [
  43. {
  44. userId: 0, // 用户ID
  45. userName: '', // 用户名
  46. operateApproval: 0, // 允许编辑/停用/删除审批 0,否 1,
  47. operateOwner: 0, // 允许添加/移除审批负责人 0,否 1,是
  48. operateData: 0 // 允许审批数据查询与操作 0,否 1,是
  49. }
  50. ] // 流程定义权限
  51. })
  52. const rules = {
  53. processIcon: [
  54. {
  55. required: true,
  56. message: '请选择图标'
  57. }
  58. ],
  59. processKey: [
  60. {
  61. required: true,
  62. message: '请输入唯一标识'
  63. }
  64. ],
  65. processName: [
  66. {
  67. required: true,
  68. message: '请输入名称'
  69. }
  70. ],
  71. categoryId: [
  72. {
  73. required: true,
  74. message: '请选择分组'
  75. }
  76. ]
  77. }
  78. const validate = () => {
  79. return new Promise((resolve, reject) => {
  80. formRef.value.validate((valid, errObj) => {
  81. if (valid) {
  82. console.error('valid 成功')
  83. // cb?.(form.value)
  84. resolve(form.value)
  85. // return form.value
  86. return
  87. }
  88. return reject(['基本信息', errObj])
  89. })
  90. })
  91. }
  92. const openApprovePerson = () => {
  93. selectVisible.value = !selectVisible.value
  94. }
  95. const selectHandle = (type, data, name) => {
  96. currentNode.value = name
  97. openApprovePerson()
  98. nextTick(() => {
  99. useSelectRef.value.open(type, data)
  100. })
  101. }
  102. const selectContentEv = item => {
  103. if (currentNode.value === 'nodeRoleList') {
  104. nodeRoleList.value = item
  105. const flag = !item.length
  106. flowInfo.value.useScope = flag ? 0 : 1
  107. let processActorList = []
  108. for (let i in item) {
  109. processActorList.push({
  110. actorId: item[i].id,
  111. actorName: item[i].name,
  112. actorType: 0
  113. })
  114. }
  115. flowInfo.value.processActorList = processActorList
  116. } else {
  117. let processPermissionList = []
  118. for (let i in item) {
  119. processPermissionList.push({
  120. userId: item[i].id,
  121. userName: item[i].name,
  122. operateApproval: 0,
  123. operateOwner: 0,
  124. operateData: 0
  125. })
  126. }
  127. nodeRoleManageList.value = item
  128. flowInfo.value.processPermissionList = processPermissionList
  129. }
  130. }
  131. const delRole = (index, itemName) => {
  132. if (itemName === 'nodeRoleList') {
  133. nodeRoleList.value.splice(index, 1)
  134. } else {
  135. nodeRoleManageList.value.splice(index, 1)
  136. }
  137. }
  138. const getGroupList = async () => {
  139. const data = await flowDefinition.flowDefinitionListCategoryApi({})
  140. options.value = data || []
  141. }
  142. const initBaseicInfEv = item => {
  143. // item = {
  144. // processId: '1728240012964925441',
  145. // processForm:yu
  146. // '[{"type":"input","field":"userName","title":"输入框","info":"","$required":false,"_fc_drag_tag":"input","hidden":false,"display":true}]',
  147. // processKey: '003key',
  148. // processName: '003名称',
  149. // processIcon: 'https://lf3-ea.bytetos.com/obj/goofy/ee/approval/approval-admin/image/iconLib/v5/cart.svg',
  150. // categoryId: '1725778939021541378',
  151. // remark: '003说明',
  152. // useScope: 1,
  153. // processActorList: [{ actorId: '1705067852272607233', actorName: 'lanceJiang', actorType: 0 }],
  154. // processPermissionList: [{ userId: '1705067852272607233', userName: 'lanceJiang', operateApproval: 0, operateOwner: 0, operateData: 0 }]
  155. // }
  156. const { processForm, modelContent, ...rest } = item
  157. flowInfo.value = rest
  158. flowStore.setProcessForm(processForm)
  159. flowStore.setModelContent(modelContent)
  160. // updateRemoteCache()
  161. flowProcessId.value = queryObj.value.id // 赋值流程id
  162. if (item.useScope === 1) {
  163. nodeRoleList.value = item.processActorList.map(item => {
  164. return {
  165. id: item.actorId,
  166. name: item.actorName,
  167. actorType: 0
  168. }
  169. })
  170. }
  171. if (item.processPermissionList && item.processPermissionList.length) {
  172. nodeRoleManageList.value = item.processPermissionList.map(item => {
  173. return {
  174. id: item.userId,
  175. name: item.userName,
  176. operateApproval: 0,
  177. operateOwner: 0,
  178. operateData: 0
  179. }
  180. })
  181. }
  182. }
  183. // 更新申请信息缓存
  184. const updateRemoteCache = async () => {
  185. try {
  186. const params = flowProcessId.value ? { ...flowInfo.value, processId: flowProcessId.value } : flowInfo.value
  187. const data = await process.progressCreateApi(params)
  188. flowProcessId.value = data
  189. Object.keys(flowInfo.value).forEach(key => {
  190. flowStore.setKeysBasicInfo(key, flowInfo.value[key])
  191. })
  192. console.log('updateRemoteCache 实际调用接口, params = ', params, new Date().getTime())
  193. } catch (e) {
  194. console.log(e)
  195. }
  196. }
  197. const handleRemoteCacheRefresh = () => {
  198. cacheIndex.value && clearTimeout(cacheIndex.value)
  199. cacheIndex.value = setTimeout(() => {
  200. // updateRemoteCache()
  201. }, cacheUpdateFr)
  202. }
  203. // 初始化缓存倒计时
  204. const handleCacheLoading = () => {
  205. cacheLoading.value = true
  206. cacheLoadingNum.value = 5
  207. cacheLoadingIndex.value && clearInterval(cacheLoadingIndex.value)
  208. cacheLoadingIndex.value = setInterval(() => {
  209. cacheLoadingNum.value = cacheLoadingNum.value - 1
  210. if (cacheLoadingNum.value <= 0) {
  211. cacheLoading.value = false
  212. cacheLoadingIndex.value && clearInterval(cacheLoadingIndex.value)
  213. bindCacheEvt()
  214. }
  215. }, cacheLoadingFr)
  216. }
  217. const closeCacheLoading = () => {
  218. console.log('组件销毁时: 关闭定时器---------')
  219. cacheLoadingIndex.value && clearInterval(cacheLoadingIndex.value)
  220. }
  221. const bindCacheEvt = () => {
  222. EVENT_BUS.off(EVENT_ENUM.UPDATE_CACHE)
  223. EVENT_BUS.on(EVENT_ENUM.UPDATE_CACHE, res => {
  224. console.log('dev.2 EVENT_ENUM.UPDATE_CACHE', res)
  225. handleRemoteCacheRefresh()
  226. })
  227. }
  228. // 根据id获取详情
  229. const getCurrentProcessDetailEv = id => {
  230. process.processDetailApi(id).then(res => {
  231. initBaseicInfEv(res)
  232. })
  233. }
  234. // ----- 缓存相关 start ------
  235. const flowBaseInfoWatcher = computed(() => {
  236. const { processKey, processName, processIcon, categoryId, remark, useScope, processActorList, processPermissionList } = flowInfo.value
  237. // 只关注参数相关的数据变更
  238. const _s = {
  239. processKey, // 流程显示名称
  240. processName, // 流程定义名称
  241. processIcon, // 流程图标
  242. categoryId, // 流程组分类ID
  243. remark, // 备注说明
  244. useScope, // 使用范围 0,全员 1,指定人员(业务关联) 2,均不可提交
  245. processActorList, // 流程参与者,当使用范围为指定人员时候设置
  246. processPermissionList // 流程定义权限
  247. }
  248. console.log('第一步:监听的对象属性 ------', _s)
  249. return _s
  250. })
  251. // 当前是否是编辑
  252. const queryObj = computed(() => route.query)
  253. watch(
  254. flowBaseInfoWatcher,
  255. (newVal, oldVal) => {
  256. console.log('第二步:执行同步emit方法 ------', newVal, oldVal)
  257. cacheTriggerFunc()
  258. },
  259. { deep: true }
  260. )
  261. // 流程名称变化
  262. watch(
  263. () => flowInfo.value.processName,
  264. newVal => {
  265. flowName.value = newVal
  266. }
  267. )
  268. // ----- 缓存相关 end ------
  269. onMounted(() => {
  270. getGroupList()
  271. handleCacheLoading()
  272. flowStore.$reset() // 重置缓存
  273. if (queryObj.value.id) {
  274. getCurrentProcessDetailEv(queryObj.value.id)
  275. }
  276. })
  277. onBeforeUnmount(() => {
  278. closeCacheLoading()
  279. })
  280. defineExpose({
  281. formRef,
  282. validate
  283. })
  284. </script>
  285. <template>
  286. <div class="base-info">
  287. <div v-if="false" style="font-size: 18px; position: absolute; left: 10px; top: 20px; z-index: 9999; color: red">
  288. {{ cacheLoadingNum }}秒之后开启自动缓存...
  289. </div>
  290. <div class="base-info-panel" style="position: relative">
  291. <div style="font-size: 18px; position: absolute; right: 10px; top: 20px; z-index: 9999">
  292. <el-button type="primary" @click="updateRemoteCache">暂存</el-button>
  293. </div>
  294. <el-form ref="formRef" :model="flowInfo" :rules="rules" label-position="top">
  295. <el-form-item label="图标" prop="processIcon">
  296. <el-space>
  297. <div class="icon-shower">
  298. <img :src="flowInfo.processIcon" alt="" />
  299. </div>
  300. </el-space>
  301. <el-popover placement="right-end" :width="450" trigger="focus" class="base-popover" :visible="visiblePopover">
  302. <div class="icon-selector__dialog__content">
  303. <div class="icon-selector-list">
  304. <div v-for="item in 27" :key="item" class="icon-selector-item" @click="visiblePopover = !visiblePopover">
  305. <img src="https://lf3-ea.bytetos.com/obj/goofy/ee/approval/approval-admin/image/iconLib/v5/cart.svg" alt="" />
  306. </div>
  307. </div>
  308. </div>
  309. <template #reference>
  310. <el-link :underline="false" @click="visiblePopover = !visiblePopover">修改</el-link>
  311. </template>
  312. </el-popover>
  313. </el-form-item>
  314. <el-form-item label="唯一标识 key" prop="processKey">
  315. <el-input v-model="flowInfo.processKey" clearable></el-input>
  316. </el-form-item>
  317. <el-form-item label="名称" prop="processName">
  318. <el-input v-model="flowInfo.processName" clearable></el-input>
  319. </el-form-item>
  320. <el-form-item label="说明" prop="remark">
  321. <el-input v-model="flowInfo.remark" type="textarea" clearable></el-input>
  322. </el-form-item>
  323. <el-form-item label="分组" prop="categoryId">
  324. <el-select v-model="flowInfo.categoryId">
  325. <el-option v-for="item in options" :key="item.categoryId" :label="item.categoryName" :value="item.categoryId" />
  326. </el-select>
  327. </el-form-item>
  328. <el-form-item label="谁可以发起该流程(不选择,默认全员)" prop="谁可以发起该流程">
  329. <div class="add-btn" @click="selectHandle(1, nodeRoleList, 'nodeRoleList')">
  330. <el-icon :size="18"><Plus /></el-icon>
  331. </div>
  332. <div class="tags-list" style="margin-top: 15px; width: 100%">
  333. <el-tag
  334. v-for="(role, index) in nodeRoleList"
  335. :key="role.id"
  336. type="info"
  337. closable
  338. style="margin-right: 8px"
  339. @close="delRole(index, 'nodeRoleList')"
  340. >{{ role.name }}</el-tag
  341. >
  342. </div>
  343. </el-form-item>
  344. <el-form-item label="管理员" prop="管理员">
  345. <div class="add-btn" @click="selectHandle(1, nodeRoleManageList, 'nodeRoleManageList')">
  346. <el-icon :size="18"><Plus /></el-icon>
  347. </div>
  348. <div class="tags-list" style="margin-top: 15px; width: 100%">
  349. <el-tag
  350. v-for="(role, index) in nodeRoleManageList"
  351. :key="role.id"
  352. type="info"
  353. closable
  354. style="margin-right: 8px"
  355. @close="delRole(index, 'nodeRoleManageList')"
  356. >{{ role.name }}</el-tag
  357. >
  358. </div>
  359. </el-form-item>
  360. </el-form>
  361. </div>
  362. <use-select v-if="selectVisible" ref="useSelectRef" @closed="selectVisible = false" @content-ev="selectContentEv"></use-select>
  363. </div>
  364. </template>
  365. <style scoped lang="scss">
  366. .basic-info-wrap {
  367. //background: var(--el-color-danger);
  368. }
  369. .flow-name {
  370. padding-right: 4px;
  371. color: var(--el-color-danger);
  372. }
  373. .base-info {
  374. margin: 0 auto;
  375. padding: 24px 0;
  376. height: 100%;
  377. text-align: center;
  378. overflow-y: auto;
  379. background-color: #eff0f1;
  380. position: relative;
  381. &-panel {
  382. display: inline-block;
  383. width: 1128px;
  384. padding: 24px 288px;
  385. background-color: #fff;
  386. text-align: left;
  387. }
  388. }
  389. .icon-selector__dialog__content {
  390. height: 384px;
  391. overflow-x: visible;
  392. overflow-y: scroll;
  393. scrollbar-width: none;
  394. -ms-overflow-style: none;
  395. .icon-selector-list {
  396. display: grid;
  397. grid-template-columns: repeat(5, 64px);
  398. grid-template-rows: repeat(auto, 64px);
  399. grid-gap: 16px;
  400. justify-content: flex-start;
  401. align-items: flex-start;
  402. flex-wrap: wrap;
  403. .icon-selector-item {
  404. width: 64px;
  405. height: 64px;
  406. padding: 8px;
  407. border: 1px solid #d0d3d6;
  408. box-sizing: border-box;
  409. border-radius: 6px;
  410. cursor: pointer;
  411. &:hover {
  412. border-color: #3370ff;
  413. }
  414. img {
  415. width: 100%;
  416. height: 100%;
  417. border-radius: 4px;
  418. overflow: hidden;
  419. }
  420. }
  421. }
  422. }
  423. .icon-shower {
  424. width: 48px;
  425. height: 48px;
  426. border-radius: 50%;
  427. overflow: hidden;
  428. img {
  429. width: 100%;
  430. height: 100%;
  431. object-fit: cover;
  432. }
  433. }
  434. .add-btn {
  435. width: 48px;
  436. height: 48px;
  437. font-family: PingFang SC, Microsoft YaHei;
  438. font-size: 14px;
  439. line-height: 20px;
  440. background-color: #eff0f1;
  441. border-radius: 50%;
  442. cursor: pointer;
  443. display: flex;
  444. justify-content: center;
  445. align-items: center;
  446. }
  447. </style>