mp_shList.html 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  6. <title>审核</title>
  7. <script src="/js/mp_base/base.js"></script>
  8. <style>
  9. /* 防止Vue模板闪烁 */
  10. [v-cloak] {
  11. display: none !important;
  12. }
  13. #app {
  14. background: #edf1f5;
  15. min-height: 100vh;
  16. display: flex;
  17. flex-direction: column;
  18. }
  19. .header-section {
  20. position: relative;
  21. padding: 10px 0;
  22. cursor: pointer;
  23. user-select: none;
  24. touch-action: none;
  25. transition: background-color 0.3s ease;
  26. }
  27. .header-section::after {
  28. width: 95%;
  29. height: 1px;
  30. content: " ";
  31. position: absolute;
  32. bottom: 0px;
  33. left: 50%;
  34. transform: translateX(-50%);
  35. background: #e2e4ec;
  36. }
  37. .header-section:hover {
  38. background: rgba(64, 172, 109, 0.05);
  39. }
  40. .header-section.dragging {
  41. background: rgba(64, 172, 109, 0.1);
  42. }
  43. </style>
  44. </head>
  45. <body>
  46. <div id="app" v-cloak>
  47. <div class="sh-list-container">
  48. <!-- 头部节点 -->
  49. <div class="header-section" @click="handleHeaderClick" @mousedown="handleDragStart"
  50. @touchstart="handleDragStart">
  51. <ss-verify-node v-if="rightHeader" class="header-node" :item="rightHeader" />
  52. </div>
  53. <!-- 审批链条 -->
  54. <div class="verify-section">
  55. <ss-verify v-if="rightGroupList && rightGroupList.length > 0" :verify-list="rightGroupList"
  56. class="fit-height-content" @toggle-group="handleToggleGroup" />
  57. </div>
  58. <!-- 空状态 -->
  59. <div v-else class="empty-state">
  60. <Icon name="icon-kongzhuangtai" size="64" color="#ccc" />
  61. <p>暂无审核节点数据</p>
  62. </div>
  63. </div>
  64. </div>
  65. <script>
  66. // 等待SS框架加载完成
  67. window.SS.ready(function () {
  68. // 使用SS框架的方式创建Vue实例
  69. window.SS.dom.initializeFormApp({
  70. el: '#app',
  71. data() {
  72. return {
  73. ssObjName:'',
  74. ssObjId:'',
  75. sqid: '',
  76. shid: '',
  77. bdlbm: '',
  78. dataType:'',
  79. encode_shid:'',
  80. pageParams: {},
  81. // 头部节点数据
  82. rightHeader: null,
  83. // 审批链条数据
  84. rightGroupList: [],
  85. loading: false,
  86. // 拖拽相关
  87. isDragging: false,
  88. dragStartY: 0,
  89. dragStartTime: 0,
  90. dragTimer: null,
  91. hasMoved: false,
  92. dragStarted: false,
  93. }
  94. },
  95. mounted() {
  96. // 获取URL参数
  97. this.pageParams = this.getUrlParams()
  98. console.log('🔗 mp_shList页面接收到参数:', this.pageParams)
  99. // 如果有sqid参数,调用selSh接口
  100. if (this.pageParams.sqid) {
  101. this.ssObjName = this.pageParams.ssObjName
  102. this.ssObjId = this.pageParams.ssObjId
  103. this.sqid = this.pageParams.sqid
  104. this.shid = this.pageParams.shid
  105. this.bdlbm = this.pageParams.bdlbm
  106. this.dataType = this.pageParams.dataType
  107. this.encode_shid = this.pageParams.encode_shid
  108. this.loadSelSh()
  109. }
  110. },
  111. methods: {
  112. // 获取URL参数
  113. getUrlParams() {
  114. const params = {}
  115. const urlSearchParams = new URLSearchParams(window.location.search)
  116. for (const [key, value] of urlSearchParams) {
  117. params[key] = decodeURIComponent(value)
  118. }
  119. return params
  120. },
  121. // 加载selSh数据
  122. async loadSelSh() {
  123. if (!this.sqid) {
  124. console.warn('⚠️ sqid为空,无法调用selSh接口')
  125. return
  126. }
  127. this.loading = true
  128. try {
  129. console.log('📋 调用selSh服务获取数据...', this.sqid)
  130. const selShResponse = await request.post(
  131. `/service?ssServ=selSh&sqid=${this.sqid}`,
  132. { loading: false, formData: true }
  133. )
  134. console.log('✅ selSh响应:', selShResponse)
  135. // 处理selSh数据
  136. if (selShResponse && selShResponse.data) {
  137. this.processShListData(selShResponse.data)
  138. }
  139. } catch (error) {
  140. console.error('❌ 加载selSh数据失败:', error)
  141. } finally {
  142. this.loading = false
  143. }
  144. },
  145. // 处理审核列表数据
  146. processShListData(data) {
  147. console.log('🔄 开始处理审核列表数据:', data)
  148. // 构建完整的审核记录列表
  149. const allRecords = []
  150. // 1. 添加申请人记录 (来自sq字段)
  151. if (data.sq) {
  152. allRecords.push({
  153. ...data.sq,
  154. isSqry: true // 标记为申请人
  155. })
  156. }
  157. // 2. 添加审核人员记录 (来自shList)
  158. if (data.shList && Array.isArray(data.shList)) {
  159. data.shList.forEach(group => {
  160. if (group.ryList && Array.isArray(group.ryList)) {
  161. group.ryList.forEach(ry => {
  162. allRecords.push({
  163. ...ry,
  164. isSqry: false // 标记为审核人
  165. })
  166. })
  167. }
  168. })
  169. }
  170. console.log('📋 所有审核记录:', allRecords)
  171. // 3. 设置申请人头部信息 (第一条记录)
  172. const sqry = allRecords.find(item => item.isSqry) || {}
  173. this.rightHeader = {
  174. thumb: this.getThumbUrl(sqry.zjzwj),
  175. name: sqry.xm || '',
  176. role: sqry.bmmc || '',
  177. description: sqry.sm || '发起审核',
  178. time: this.formatTime(sqry.shsj),
  179. link: false,
  180. }
  181. console.log('👤 申请人信息:', this.rightHeader)
  182. // 4. 按部门分组构建审核链条
  183. const shRecords = allRecords.filter(item => !item.isSqry)
  184. this.rightGroupList = this.groupByDepartment(shRecords)
  185. console.log('📊 审核链条:', this.rightGroupList)
  186. },
  187. // 按部门分组
  188. groupByDepartment(records) {
  189. const groups = {}
  190. records.forEach(item => {
  191. const groupKey = item.bmmc || '未知部门'
  192. if (!groups[groupKey]) {
  193. groups[groupKey] = {
  194. groupName: groupKey,
  195. open: true,
  196. children: []
  197. }
  198. }
  199. groups[groupKey].children.push({
  200. thumb: this.getThumbUrl(item.zjzwj),
  201. name: item.xm || '',
  202. role: item.bmmc || '',
  203. description: item.sm || '同意',
  204. time: this.formatTime(item.shsj),
  205. link: false
  206. })
  207. })
  208. return Object.values(groups)
  209. },
  210. // 获取头像URL
  211. getThumbUrl(path) {
  212. // 如果没有路径,返回默认头像
  213. if (!path) {
  214. return 'skin/easy/image/default-personalPhoto.png'
  215. }
  216. // 使用全局的 getImageUrl 方法处理图片路径
  217. if (typeof window.getImageUrl === 'function') {
  218. return window.getImageUrl(path)
  219. }
  220. // 如果 getImageUrl 不存在,返回原路径
  221. console.warn('⚠️ getImageUrl 方法不存在')
  222. return path
  223. },
  224. // 格式化时间
  225. formatTime(timeStr) {
  226. // 检查时间字符串是否有效
  227. if (!timeStr || timeStr === '' || timeStr === null || timeStr === undefined) {
  228. console.log('⚠️ formatTime: 时间为空')
  229. return ''
  230. }
  231. console.log('🕐 formatTime 输入:', timeStr)
  232. // 检查 dayjs 是否可用
  233. if (typeof dayjs === 'undefined') {
  234. console.error('❌ dayjs 未加载')
  235. return ''
  236. }
  237. try {
  238. // 清理字符串:移除特殊空格字符
  239. const cleanedDateStr = String(timeStr)
  240. .replace(/[\u202F\u00A0]/g, ' ')
  241. .replace(/\s+/g, ' ')
  242. .trim()
  243. console.log('清理后的字符串:', cleanedDateStr)
  244. // 使用原生 Date 解析
  245. const jsDate = new Date(cleanedDateStr)
  246. if (isNaN(jsDate.getTime())) {
  247. console.warn('⚠️ Date 解析失败')
  248. return ''
  249. }
  250. // 转换为 dayjs
  251. const date = dayjs(jsDate)
  252. if (!date.isValid()) {
  253. console.warn('⚠️ dayjs 无效')
  254. return ''
  255. }
  256. console.log('✅ dayjs 解析成功')
  257. // 格式化: HH:mm MM/DD
  258. const result = date.format('HH:mm MM/DD')
  259. console.log('🕐 formatTime 输出:', result)
  260. return result
  261. } catch (error) {
  262. console.error('❌ 时间格式化失败:', error, timeStr)
  263. return ''
  264. }
  265. },
  266. // 处理分组展开/收起
  267. handleToggleGroup(data) {
  268. console.log('🔄 分组状态变化:', data)
  269. },
  270. // ===== 与父页面通信方法 =====
  271. // 发送消息到父页面
  272. sendMessageToParent(type, data = {}) {
  273. try {
  274. // 通过postMessage向父页面发送消息
  275. if (window.parent && window.parent !== window) {
  276. window.parent.postMessage({
  277. type,
  278. data
  279. }, '*') // 在生产环境中应该使用具体的origin
  280. }
  281. } catch (error) {
  282. console.error('发送消息到父页面失败:', error)
  283. }
  284. },
  285. // 处理header-section点击
  286. handleHeaderClick(event) {
  287. // 如果正在拖拽中,不处理点击
  288. if (this.isDragging) {
  289. return
  290. }
  291. console.log('🖱️ header-section被点击')
  292. this.sendMessageToParent('header-section-click')
  293. },
  294. // 处理拖拽开始(长按检测)
  295. handleDragStart(event) {
  296. // 阻止默认行为,防止页面滚动
  297. event.preventDefault()
  298. // 只处理鼠标左键或触摸事件
  299. if (event.type === 'mousedown' && event.button !== 0) {
  300. return
  301. }
  302. console.log('🔄 开始长按检测')
  303. // 记录开始时间和位置
  304. this.dragStartTime = Date.now()
  305. this.dragStartY = event.type === 'mousedown' ? event.clientY : event.touches[0].clientY
  306. this.hasMoved = false
  307. this.dragStarted = false
  308. // 添加长按检测
  309. this.dragTimer = setTimeout(() => {
  310. if (!this.hasMoved && !this.dragStarted) {
  311. this.dragStarted = true
  312. console.log('🔄 开始拖拽header-section')
  313. this.isDragging = true
  314. // 添加拖拽样式
  315. document.querySelector('.header-section').classList.add('dragging')
  316. // 通知父页面开始拖拽
  317. this.sendMessageToParent('header-section-drag-start', {
  318. startY: this.dragStartY
  319. })
  320. // 添加全局事件监听器
  321. if (event.type === 'mousedown') {
  322. document.addEventListener('mousemove', this.handleDragMove)
  323. document.addEventListener('mouseup', this.handleDragEnd)
  324. } else {
  325. document.addEventListener('touchmove', this.handleDragMove, { passive: false })
  326. document.addEventListener('touchend', this.handleDragEnd)
  327. }
  328. }
  329. }, 500) // 500ms后开始拖拽
  330. // 添加临时事件监听器来检测移动
  331. const tempMouseMove = (e) => {
  332. const currentY = e.type === 'mousemove' ? e.clientY : e.touches[0].clientY
  333. const moveDistance = Math.abs(currentY - this.dragStartY)
  334. if (moveDistance > 10) { // 移动超过10px就认为不是长按
  335. this.hasMoved = true
  336. clearTimeout(this.dragTimer)
  337. // 移除临时监听器
  338. this.removeTempListeners(event.type, tempMouseMove, tempMouseUp)
  339. }
  340. }
  341. const tempMouseUp = (e) => {
  342. clearTimeout(this.dragTimer)
  343. // 如果没有开始拖拽且有移动,则触发点击事件
  344. if (!this.dragStarted && !this.hasMoved) {
  345. console.log('🖱️ 触发点击事件(mouseup)')
  346. // 延迟触发点击,避免与拖拽冲突
  347. setTimeout(() => {
  348. this.handleHeaderClick(e)
  349. }, 50)
  350. }
  351. // 移除临时监听器
  352. this.removeTempListeners(event.type, tempMouseMove, tempMouseUp)
  353. }
  354. // 添加临时事件监听器
  355. if (event.type === 'mousedown') {
  356. document.addEventListener('mousemove', tempMouseMove)
  357. document.addEventListener('mouseup', tempMouseUp)
  358. } else {
  359. document.addEventListener('touchmove', tempMouseMove, { passive: false })
  360. document.addEventListener('touchend', tempMouseUp)
  361. }
  362. },
  363. // 移除临时监听器的辅助方法
  364. removeTempListeners(eventType, mouseMoveHandler, mouseUpHandler) {
  365. if (eventType === 'mousedown') {
  366. document.removeEventListener('mousemove', mouseMoveHandler)
  367. document.removeEventListener('mouseup', mouseUpHandler)
  368. } else {
  369. document.removeEventListener('touchmove', mouseMoveHandler)
  370. document.removeEventListener('touchend', mouseUpHandler)
  371. }
  372. },
  373. // 处理拖拽移动
  374. handleDragMove(event) {
  375. if (!this.isDragging) return
  376. event.preventDefault()
  377. const currentY = event.type === 'mousemove'
  378. ? event.clientY
  379. : event.touches[0].clientY
  380. const deltaY = currentY - this.dragStartY
  381. // 通知父页面拖拽移动
  382. this.sendMessageToParent('header-section-drag-move', {
  383. deltaY
  384. })
  385. },
  386. // 处理拖拽结束
  387. handleDragEnd() {
  388. if (!this.isDragging) return
  389. console.log('🔚 结束拖拽')
  390. // 移除拖拽样式
  391. const headerSection = document.querySelector('.header-section')
  392. if (headerSection) {
  393. headerSection.classList.remove('dragging')
  394. }
  395. // 重置状态
  396. this.isDragging = false
  397. this.dragStartY = 0
  398. // 移除全局事件监听器
  399. document.removeEventListener('mousemove', this.handleDragMove)
  400. document.removeEventListener('mouseup', this.handleDragEnd)
  401. document.removeEventListener('touchmove', this.handleDragMove)
  402. document.removeEventListener('touchend', this.handleDragEnd)
  403. // 通知父页面拖拽结束
  404. this.sendMessageToParent('header-section-drag-end')
  405. },
  406. }
  407. })
  408. })
  409. </script>
  410. </body>