mp_shList.html 17 KB

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