소스 검색

新增床位互换

unknown 2 달 전
부모
커밋
2b40be8a1f

+ 582 - 23
package-lock.json

@@ -39,6 +39,7 @@
         "echarts": "^5.5.0",
         "echarts-wordcloud": "^2.1.0",
         "element-plus": "2.9.1",
+        "exceljs": "^4.4.0",
         "fast-xml-parser": "^4.3.2",
         "file-saver": "^2.0.5",
         "highlight.js": "^11.9.0",
@@ -3364,6 +3365,47 @@
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
       }
     },
+    "node_modules/@fast-csv/format": {
+      "version": "4.3.5",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/@fast-csv/format/-/format-4.3.5.tgz",
+      "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "^14.0.1",
+        "lodash.escaperegexp": "^4.1.2",
+        "lodash.isboolean": "^3.0.3",
+        "lodash.isequal": "^4.5.0",
+        "lodash.isfunction": "^3.0.9",
+        "lodash.isnil": "^4.0.0"
+      }
+    },
+    "node_modules/@fast-csv/format/node_modules/@types/node": {
+      "version": "14.18.63",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/@types/node/-/node-14.18.63.tgz",
+      "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
+      "license": "MIT"
+    },
+    "node_modules/@fast-csv/parse": {
+      "version": "4.3.6",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/@fast-csv/parse/-/parse-4.3.6.tgz",
+      "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "^14.0.1",
+        "lodash.escaperegexp": "^4.1.2",
+        "lodash.groupby": "^4.6.0",
+        "lodash.isfunction": "^3.0.9",
+        "lodash.isnil": "^4.0.0",
+        "lodash.isundefined": "^3.0.1",
+        "lodash.uniq": "^4.5.0"
+      }
+    },
+    "node_modules/@fast-csv/parse/node_modules/@types/node": {
+      "version": "14.18.63",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/@types/node/-/node-14.18.63.tgz",
+      "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
+      "license": "MIT"
+    },
     "node_modules/@floating-ui/core": {
       "version": "1.6.9",
       "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.9.tgz",
@@ -6829,6 +6871,75 @@
         "node": ">= 8"
       }
     },
+    "node_modules/archiver": {
+      "version": "5.3.2",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/archiver/-/archiver-5.3.2.tgz",
+      "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
+      "license": "MIT",
+      "dependencies": {
+        "archiver-utils": "^2.1.0",
+        "async": "^3.2.4",
+        "buffer-crc32": "^0.2.1",
+        "readable-stream": "^3.6.0",
+        "readdir-glob": "^1.1.2",
+        "tar-stream": "^2.2.0",
+        "zip-stream": "^4.1.0"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/archiver-utils": {
+      "version": "2.1.0",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/archiver-utils/-/archiver-utils-2.1.0.tgz",
+      "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
+      "license": "MIT",
+      "dependencies": {
+        "glob": "^7.1.4",
+        "graceful-fs": "^4.2.0",
+        "lazystream": "^1.0.0",
+        "lodash.defaults": "^4.2.0",
+        "lodash.difference": "^4.5.0",
+        "lodash.flatten": "^4.4.0",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.union": "^4.6.0",
+        "normalize-path": "^3.0.0",
+        "readable-stream": "^2.0.0"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/archiver-utils/node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/archiver-utils/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "license": "MIT"
+    },
+    "node_modules/archiver-utils/node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
     "node_modules/argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -6961,8 +7072,7 @@
     "node_modules/async": {
       "version": "3.2.5",
       "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
-      "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
-      "dev": true
+      "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
     },
     "node_modules/async-validator": {
       "version": "4.2.5",
@@ -7200,8 +7310,7 @@
           "type": "consulting",
           "url": "https://feross.org/support"
         }
-      ],
-      "optional": true
+      ]
     },
     "node_modules/benz-amr-recorder": {
       "version": "1.1.5",
@@ -7216,6 +7325,15 @@
       "resolved": "https://registry.npmjs.org/benz-recorderjs/-/benz-recorderjs-1.0.5.tgz",
       "integrity": "sha512-EwedOQo9KLti7HxDi/eZY51PSRbAXnOdEZmLvJ6ro3QQSoF9Y3AXBt57MIllGvVz5vtFYMeikG+GD7qTm3+p9w=="
     },
+    "node_modules/big-integer": {
+      "version": "1.6.52",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/big-integer/-/big-integer-1.6.52.tgz",
+      "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
+      "license": "Unlicense",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
     "node_modules/big.js": {
       "version": "5.2.2",
       "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ -7225,6 +7343,19 @@
         "node": "*"
       }
     },
+    "node_modules/binary": {
+      "version": "0.3.0",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/binary/-/binary-0.3.0.tgz",
+      "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==",
+      "license": "MIT",
+      "dependencies": {
+        "buffers": "~0.1.1",
+        "chainsaw": "~0.1.0"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/binary-extensions": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -7241,7 +7372,6 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz",
       "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
-      "optional": true,
       "dependencies": {
         "buffer": "^5.5.0",
         "inherits": "^2.0.4",
@@ -7471,18 +7601,43 @@
           "url": "https://feross.org/support"
         }
       ],
-      "optional": true,
       "dependencies": {
         "base64-js": "^1.3.1",
         "ieee754": "^1.1.13"
       }
     },
+    "node_modules/buffer-crc32": {
+      "version": "0.2.13",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+      "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
       "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
       "dev": true
     },
+    "node_modules/buffer-indexof-polyfill": {
+      "version": "1.0.2",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
+      "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/buffers": {
+      "version": "0.1.1",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/buffers/-/buffers-0.1.1.tgz",
+      "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==",
+      "engines": {
+        "node": ">=0.2.0"
+      }
+    },
     "node_modules/cac": {
       "version": "6.7.14",
       "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@@ -7641,6 +7796,27 @@
         "node": ">=0.8"
       }
     },
+    "node_modules/chainsaw": {
+      "version": "0.1.0",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/chainsaw/-/chainsaw-0.1.0.tgz",
+      "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==",
+      "license": "MIT/X11",
+      "dependencies": {
+        "traverse": ">=0.3.0 <0.4"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/chainsaw/node_modules/traverse": {
+      "version": "0.3.9",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/traverse/-/traverse-0.3.9.tgz",
+      "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==",
+      "license": "MIT/X11",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/chalk": {
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
@@ -7984,6 +8160,21 @@
       "resolved": "https://registry.npmmirror.com/component-event/-/component-event-0.2.1.tgz",
       "integrity": "sha512-wGA++isMqiDq1jPYeyv2as/Bt/u+3iLW0rEa+8NQ82jAv3TgqMiCM+B2SaBdn2DfLilLjjq736YcezihRYhfxw=="
     },
+    "node_modules/compress-commons": {
+      "version": "4.1.2",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/compress-commons/-/compress-commons-4.1.2.tgz",
+      "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer-crc32": "^0.2.13",
+        "crc32-stream": "^4.0.2",
+        "normalize-path": "^3.0.0",
+        "readable-stream": "^3.6.0"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
     "node_modules/compute-scroll-into-view": {
       "version": "1.0.20",
       "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
@@ -8192,6 +8383,19 @@
         "node": ">=0.8"
       }
     },
+    "node_modules/crc32-stream": {
+      "version": "4.0.3",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/crc32-stream/-/crc32-stream-4.0.3.tgz",
+      "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
+      "license": "MIT",
+      "dependencies": {
+        "crc-32": "^1.2.0",
+        "readable-stream": "^3.4.0"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
     "node_modules/crelt": {
       "version": "1.0.6",
       "resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz",
@@ -9232,6 +9436,45 @@
       "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
       "dev": true
     },
+    "node_modules/duplexer2": {
+      "version": "0.1.4",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/duplexer2/-/duplexer2-0.1.4.tgz",
+      "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "readable-stream": "^2.0.2"
+      }
+    },
+    "node_modules/duplexer2/node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/duplexer2/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "license": "MIT"
+    },
+    "node_modules/duplexer2/node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
     "node_modules/eastasianwidth": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -9405,7 +9648,6 @@
       "version": "1.4.4",
       "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz",
       "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
-      "optional": true,
       "dependencies": {
         "once": "^1.4.0"
       }
@@ -10152,6 +10394,35 @@
       "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
       "dev": true
     },
+    "node_modules/exceljs": {
+      "version": "4.4.0",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/exceljs/-/exceljs-4.4.0.tgz",
+      "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==",
+      "license": "MIT",
+      "dependencies": {
+        "archiver": "^5.0.0",
+        "dayjs": "^1.8.34",
+        "fast-csv": "^4.3.1",
+        "jszip": "^3.10.1",
+        "readable-stream": "^3.6.0",
+        "saxes": "^5.0.1",
+        "tmp": "^0.2.0",
+        "unzipper": "^0.10.11",
+        "uuid": "^8.3.0"
+      },
+      "engines": {
+        "node": ">=8.3.0"
+      }
+    },
+    "node_modules/exceljs/node_modules/uuid": {
+      "version": "8.3.2",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/uuid/-/uuid-8.3.2.tgz",
+      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+      "license": "MIT",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
     "node_modules/execa": {
       "version": "8.0.1",
       "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
@@ -10276,6 +10547,19 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/fast-csv": {
+      "version": "4.3.6",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/fast-csv/-/fast-csv-4.3.6.tgz",
+      "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==",
+      "license": "MIT",
+      "dependencies": {
+        "@fast-csv/format": "4.3.5",
+        "@fast-csv/parse": "4.3.6"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -10646,8 +10930,7 @@
     "node_modules/fs-constants": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz",
-      "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
-      "optional": true
+      "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
     },
     "node_modules/fs-extra": {
       "version": "10.1.0",
@@ -10682,6 +10965,35 @@
         "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
       }
     },
+    "node_modules/fstream": {
+      "version": "1.0.12",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/fstream/-/fstream-1.0.12.tgz",
+      "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
+      "deprecated": "This package is no longer supported.",
+      "license": "ISC",
+      "dependencies": {
+        "graceful-fs": "^4.1.2",
+        "inherits": "~2.0.0",
+        "mkdirp": ">=0.5 0",
+        "rimraf": "2"
+      },
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/fstream/node_modules/rimraf": {
+      "version": "2.7.1",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/rimraf/-/rimraf-2.7.1.tgz",
+      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+      "deprecated": "Rimraf versions prior to v4 are no longer supported",
+      "license": "ISC",
+      "dependencies": {
+        "glob": "^7.1.3"
+      },
+      "bin": {
+        "rimraf": "bin.js"
+      }
+    },
     "node_modules/function-bind": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -11009,8 +11321,7 @@
     "node_modules/graceful-fs": {
       "version": "4.2.11",
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
-      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
-      "dev": true
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
     },
     "node_modules/graphemer": {
       "version": "1.4.0",
@@ -11335,8 +11646,7 @@
           "type": "consulting",
           "url": "https://feross.org/support"
         }
-      ],
-      "optional": true
+      ]
     },
     "node_modules/ignore": {
       "version": "5.3.1",
@@ -12357,6 +12667,48 @@
         "node": "*"
       }
     },
+    "node_modules/lazystream": {
+      "version": "1.0.1",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/lazystream/-/lazystream-1.0.1.tgz",
+      "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
+      "license": "MIT",
+      "dependencies": {
+        "readable-stream": "^2.0.5"
+      },
+      "engines": {
+        "node": ">= 0.6.3"
+      }
+    },
+    "node_modules/lazystream/node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/lazystream/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "license": "MIT"
+    },
+    "node_modules/lazystream/node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
     "node_modules/levn": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -12445,6 +12797,12 @@
         "url": "https://opencollective.com/lint-staged"
       }
     },
+    "node_modules/listenercount": {
+      "version": "1.0.1",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/listenercount/-/listenercount-1.0.1.tgz",
+      "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==",
+      "license": "ISC"
+    },
     "node_modules/listr2": {
       "version": "8.2.2",
       "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.2.tgz",
@@ -12630,21 +12988,74 @@
       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
       "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
     },
+    "node_modules/lodash.defaults": {
+      "version": "4.2.0",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+      "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.difference": {
+      "version": "4.5.0",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/lodash.difference/-/lodash.difference-4.5.0.tgz",
+      "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.escaperegexp": {
+      "version": "4.1.2",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
+      "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.flatten": {
+      "version": "4.4.0",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
+      "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
+      "license": "MIT"
+    },
     "node_modules/lodash.foreach": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
       "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ=="
     },
+    "node_modules/lodash.groupby": {
+      "version": "4.6.0",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
+      "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isboolean": {
+      "version": "3.0.3",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+      "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+      "license": "MIT"
+    },
     "node_modules/lodash.isequal": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
       "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
     },
+    "node_modules/lodash.isfunction": {
+      "version": "3.0.9",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
+      "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isnil": {
+      "version": "4.0.0",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/lodash.isnil/-/lodash.isnil-4.0.0.tgz",
+      "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==",
+      "license": "MIT"
+    },
     "node_modules/lodash.isplainobject": {
       "version": "4.0.6",
       "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
-      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
-      "dev": true
+      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+    },
+    "node_modules/lodash.isundefined": {
+      "version": "3.0.1",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz",
+      "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==",
+      "license": "MIT"
     },
     "node_modules/lodash.kebabcase": {
       "version": "4.1.1",
@@ -12691,11 +13102,16 @@
       "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==",
       "dev": true
     },
+    "node_modules/lodash.union": {
+      "version": "4.6.0",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/lodash.union/-/lodash.union-4.6.0.tgz",
+      "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
+      "license": "MIT"
+    },
     "node_modules/lodash.uniq": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
-      "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
-      "dev": true
+      "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="
     },
     "node_modules/lodash.upperfirst": {
       "version": "4.3.1",
@@ -13250,7 +13666,6 @@
       "version": "1.2.8",
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
       "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
-      "devOptional": true,
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
@@ -13305,6 +13720,18 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/mkdirp": {
+      "version": "0.5.6",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/mkdirp/-/mkdirp-0.5.6.tgz",
+      "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+      "license": "MIT",
+      "dependencies": {
+        "minimist": "^1.2.6"
+      },
+      "bin": {
+        "mkdirp": "bin/cmd.js"
+      }
+    },
     "node_modules/mkdirp-classic": {
       "version": "0.5.3",
       "resolved": "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
@@ -13589,7 +14016,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
       "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -15007,7 +15433,6 @@
       "version": "3.6.2",
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
       "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
-      "devOptional": true,
       "dependencies": {
         "inherits": "^2.0.3",
         "string_decoder": "^1.1.1",
@@ -15017,6 +15442,27 @@
         "node": ">= 6"
       }
     },
+    "node_modules/readdir-glob": {
+      "version": "1.1.3",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/readdir-glob/-/readdir-glob-1.1.3.tgz",
+      "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "minimatch": "^5.1.0"
+      }
+    },
+    "node_modules/readdir-glob/node_modules/minimatch": {
+      "version": "5.1.9",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/minimatch/-/minimatch-5.1.9.tgz",
+      "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/readdirp": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -15649,7 +16095,6 @@
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
       "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-      "devOptional": true,
       "funding": [
         {
           "type": "github",
@@ -15732,6 +16177,18 @@
       "integrity": "sha512-xUOiiFbc3Ow7p8KMxwsGICPx46ZQvy3+qfNVhrkwfz3Vvq45eGt98Ft5IQaA1R/7Tb5B5MKh9fUR9x3c3nDTxw==",
       "dev": true
     },
+    "node_modules/saxes": {
+      "version": "5.0.1",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/saxes/-/saxes-5.0.1.tgz",
+      "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==",
+      "license": "ISC",
+      "dependencies": {
+        "xmlchars": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/scroll-into-view-if-needed": {
       "version": "2.2.31",
       "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz",
@@ -16301,7 +16758,6 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
       "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
-      "devOptional": true,
       "dependencies": {
         "safe-buffer": "~5.2.0"
       }
@@ -17107,7 +17563,6 @@
       "version": "2.2.0",
       "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz",
       "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
-      "optional": true,
       "dependencies": {
         "bl": "^4.0.3",
         "end-of-stream": "^1.4.1",
@@ -17193,6 +17648,15 @@
       "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
       "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
     },
+    "node_modules/tmp": {
+      "version": "0.2.5",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/tmp/-/tmp-0.2.5.tgz",
+      "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14.14"
+      }
+    },
     "node_modules/to-object-path": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
@@ -17879,6 +18343,60 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/unzipper": {
+      "version": "0.10.14",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/unzipper/-/unzipper-0.10.14.tgz",
+      "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==",
+      "license": "MIT",
+      "dependencies": {
+        "big-integer": "^1.6.17",
+        "binary": "~0.3.0",
+        "bluebird": "~3.4.1",
+        "buffer-indexof-polyfill": "~1.0.0",
+        "duplexer2": "~0.1.4",
+        "fstream": "^1.0.12",
+        "graceful-fs": "^4.2.2",
+        "listenercount": "~1.0.1",
+        "readable-stream": "~2.3.6",
+        "setimmediate": "~1.0.4"
+      }
+    },
+    "node_modules/unzipper/node_modules/bluebird": {
+      "version": "3.4.7",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/bluebird/-/bluebird-3.4.7.tgz",
+      "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==",
+      "license": "MIT"
+    },
+    "node_modules/unzipper/node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/unzipper/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "license": "MIT"
+    },
+    "node_modules/unzipper/node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
     "node_modules/update-browserslist-db": {
       "version": "1.0.16",
       "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz",
@@ -18814,6 +19332,12 @@
         "node": ">=12"
       }
     },
+    "node_modules/xmlchars": {
+      "version": "2.2.0",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/xmlchars/-/xmlchars-2.2.0.tgz",
+      "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+      "license": "MIT"
+    },
     "node_modules/y18n": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
@@ -18906,6 +19430,41 @@
       "dev": true,
       "peer": true
     },
+    "node_modules/zip-stream": {
+      "version": "4.1.1",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/zip-stream/-/zip-stream-4.1.1.tgz",
+      "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
+      "license": "MIT",
+      "dependencies": {
+        "archiver-utils": "^3.0.4",
+        "compress-commons": "^4.1.2",
+        "readable-stream": "^3.6.0"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/zip-stream/node_modules/archiver-utils": {
+      "version": "3.0.4",
+      "resolved": "https://repo.huaweicloud.com/repository/npm/archiver-utils/-/archiver-utils-3.0.4.tgz",
+      "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
+      "license": "MIT",
+      "dependencies": {
+        "glob": "^7.2.3",
+        "graceful-fs": "^4.2.0",
+        "lazystream": "^1.0.0",
+        "lodash.defaults": "^4.2.0",
+        "lodash.difference": "^4.5.0",
+        "lodash.flatten": "^4.4.0",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.union": "^4.6.0",
+        "normalize-path": "^3.0.0",
+        "readable-stream": "^3.6.0"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
     "node_modules/zrender": {
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.5.0.tgz",

+ 1 - 0
package.json

@@ -57,6 +57,7 @@
     "echarts": "^5.5.0",
     "echarts-wordcloud": "^2.1.0",
     "element-plus": "2.9.1",
+    "exceljs": "^4.4.0",
     "fast-xml-parser": "^4.3.2",
     "file-saver": "^2.0.5",
     "highlight.js": "^11.9.0",

+ 18 - 0
src/api/elderly/elder/bed-change/index.ts

@@ -23,6 +23,14 @@ export const getBedChangeRecordById = (id, status,isDetail) => {
   })
 }
 
+// 获取详情
+export const getBedChangeRecordElderlyById = (id) => {
+  return request.get({
+    url: `/elderly/bedChangeRecord/getElderlyById?elderId=${id}`,
+  })
+}
+
+
 // 新增
 export const bedChangeCreate = (data) => {
   return request.post({
@@ -31,6 +39,16 @@ export const bedChangeCreate = (data) => {
   })
 }
 
+// 对调
+export const bedChangeRecord = (data) => {
+  return request.post({
+    url: `/elderly/bedChangeRecord/swap`,
+    data
+  })
+}
+
+//
+
 // 检测是否可以包房
 export const checkPrivateRoom = (id,elderId=undefined) => {
   return request.get({

+ 8 - 8
src/api/social-work/index.ts

@@ -53,15 +53,15 @@ export const activityServiceRecordUpdate = async (data) => {
 
 // ==================== 楼层活动记录表 ====================
 export const floorActivityRecordGetInfo = async (id) => {
-  return await request.get({ url: '/floor-activity-record/get?id=' + id })
+  return await request.get({ url: '/elderly-build-floor-activity-text/get?id=' + id })
 }
 
 export const floorActivityRecordDelete = async (id) => {
-  return await request.delete({ url: '/floor-activity-record/delete?id=' + id })
+  return await request.delete({ url: '/elderly-build-floor-activity-text/delete?id=' + id })
 }
 
 export const floorActivityRecordGetPage = async (params) => {
-  return await request.get({ url: '/floor-activity-record/page', params })
+  return await request.get({ url: '/elderly-build-floor-activity-text/page', params })
 }
 
 export const floorActivityRecordCreate = async (data) => {
@@ -69,20 +69,20 @@ export const floorActivityRecordCreate = async (data) => {
 }
 
 export const floorActivityRecordUpdate = async (data) => {
-  return await request.put({ url: '', data: data })
+  return await request.put({ url: '/elderly-build-floor-activity-text/update', data: data })
 }
 
 // ==================== 楼层活动照片记录表 ====================
 export const floorActivityPhotoRecordGetInfo = async (id) => {
-  return await request.get({ url: '/floor-activity-photo-record/get?id=' + id })
+  return await request.get({ url: '/elderly-build-floor-activity-image/get?id=' + id })
 }
 
 export const floorActivityPhotoRecordDelete = async (id) => {
-  return await request.delete({ url: '/floor-activity-photo-record/delete?id=' + id })
+  return await request.delete({ url: '/elderly-build-floor-activity-image/delete?id=' + id })
 }
 
 export const floorActivityPhotoRecordGetPage = async (params) => {
-  return await request.get({ url: '/floor-activity-photo-record/page', params })
+  return await request.get({ url: '/elderly-build-floor-activity-image/page', params })
 }
 
 export const floorActivityPhotoRecordCreate = async (data) => {
@@ -90,7 +90,7 @@ export const floorActivityPhotoRecordCreate = async (data) => {
 }
 
 export const floorActivityPhotoRecordUpdate = async (data) => {
-  return await request.put({ url: '/floor-activity-photo-record/update', data: data })
+  return await request.put({ url: '/elderly-build-floor-activity-image/update', data: data })
 }
 
 // ==================== 评估服务量表 ====================

+ 1 - 1
src/main.ts

@@ -51,7 +51,7 @@ import { Flex } from 'ant-design-vue'
 import 'ant-design-vue/dist/reset.css'
 import fetchPlugin from './config/axios/fetch';
 //版本号
-export const MAIN_VERSION = '4.3.0'
+export const MAIN_VERSION = '4.3.1'
 
 // 创建实例
 const setupAll = async () => {

+ 26 - 0
src/utils/dateUtil.ts

@@ -182,6 +182,32 @@ export function getCurrentMonthRange(date = new Date()) {
   return [formatToYYYYMMDD(firstDay), formatToYYYYMMDD(lastDay)];
 }
 
+
+/**
+ * 获取当前月份的第一天和最后一天
+ * @param date - 日期对象,默认为当前日期
+ * @returns 包含第一天和最后一天的字符串数组,格式为 [YYYY-MM-DD, YYYY-MM-DD]
+ */
+export function getCurrentMonthRange_1(date = new Date()) {
+  const year = date.getFullYear();
+  const month = date.getMonth(); // 0-11
+
+  // 本月第一天
+  const firstDay = new Date(year, month, 1);
+  // 本月最后一天
+  const lastDay = new Date(year, month + 1, 1);
+
+  // 格式化为 YYYY-MM-DD 字符串
+  const formatToYYYYMM = (d: Date) => {
+    const year = d.getFullYear();
+    const month = String(d.getMonth() + 1).padStart(2, '0');
+    return `${year}-${month}`;
+  };
+
+  return [formatToYYYYMM(firstDay), formatToYYYYMM(lastDay)];
+}
+
+
 /**
  * 将 13 位时间戳转换为指定格式的日期字符串
  * @param timestamp - 13 位时间戳

+ 90 - 0
src/views/elderly/elder/bed-change/ExchangeForm.vue

@@ -0,0 +1,90 @@
+<template>
+  <Dialog
+    v-model="dialogVisible"
+    title="新增"
+    width="80%"
+    class="form-tag-dialog"
+    @close="handleClosed"
+  >
+    <ExchangeProcessForm ref="processFormRef" />
+    <template #footer>
+      <el-button @click="handleClosed">关闭</el-button>
+      <el-button v-loading="formLoading" type="primary" @click="submitForm">提交</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import ExchangeProcessForm from './ExchangeProcessForm.vue'
+import {bedChangeCreate, bedChangeRecord} from '@/api/elderly/elder/bed-change'
+import {ElMessageBox} from "element-plus";
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+defineOptions({ name: 'BedChangeForm' })
+
+const dialogVisible = ref(false) // 弹窗
+const processFormRef = ref()
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+/** 打开弹窗 */
+const open = async (tId, id, detail, status,elderly=undefined) => {
+  dialogVisible.value = true
+  await nextTick(() => {
+    processFormRef.value.setTenantId(tId)
+    processFormRef.value.init(id, detail, status, elderly)
+  })
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+const handleClosed = () => {
+  processFormRef.value && processFormRef.value.resetForm()
+  dialogVisible.value = false
+}
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+
+
+  await ElMessageBox.confirm('床位对调暂未设置审核人员,请认真核对信息后再提交,确定要提交吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+
+
+  if(formLoading.value){
+    return
+  }
+  formLoading.value = true
+
+  try {
+    let { valid, dataForm } = await processFormRef.value.submitForm()
+    if (!valid) {
+      return
+    }
+
+
+    // let result = true
+    // // 如果是包房先检验
+    // if (dataForm.isPrivateRoom == 1) {
+    //   result = await checkPrivateRoom(dataForm.expectId,dataForm.elderlyId)
+    // }
+    // if (!result) {
+    //   message.error('当前房间不可以包房')
+    //   return
+    // }
+    // 新增
+    const res = await bedChangeRecord(dataForm)
+    if (res) {
+      message.success(t('common.updateSuccess'))
+      // 发送操作成功的事件
+      emit('success')
+      handleClosed()
+    }
+  } finally {
+    setTimeout(()=>{
+      formLoading.value = false
+    },500)
+  }
+
+}
+</script>

+ 820 - 0
src/views/elderly/elder/bed-change/ExchangeProcessForm.vue

@@ -0,0 +1,820 @@
+<template>
+  <el-form
+    ref="formRef"
+    :model="dataForm"
+    label-width="110px"
+    :rules="dataRule"
+    class="bed-change-form"
+    :toggleType="isDetail"
+  >
+    <div class="info-wrap" v-if="!isDetail">
+      <el-row :gutter="20">
+<!--        /////////////////长者A-->
+        <el-col :span="12" :xs="24">
+          <el-form-item label="长者姓名" prop="elderlyId">
+            <SelectElder v-model="dataForm.elderlyId" @elder="handleSelectElderA" type="1" :tId="dataForm.tenantId"/>
+          </el-form-item>
+
+          <el-form-item label="原床位" prop="originalName">
+            <TgInput v-model="dataForm.originalName" disabled />
+          </el-form-item>
+
+          <el-form-item label="原床位类型" prop="overheadChargeId">
+            <TgSelect
+              v-model="dataForm.overheadChargeId"
+              placeholder="请选择"
+              disabled
+              :list="bedList"
+              dict-label="chargeName"
+              dict-value="id"
+            >
+              <el-option v-for="t in bedList" :key="t.id" :label="t.chargeName" :value="t.id" />
+            </TgSelect>
+          </el-form-item>
+
+          <el-form-item label="变更生效日期" prop="changeDate">
+            <TgDatePicker v-model="dataForm.changeDate" type="date" placeholder="变更生效日期" />
+          </el-form-item>
+
+          <el-form-item label="新床位" prop="expectId">
+            <SelectRoom v-model="dataForm.expectId"  @bed="bedChangeA"  :disabled="true" status="0" :tId="dataForm.tenantId"/>
+          </el-form-item>
+
+          <el-form-item label="是否包房" prop="isPrivateRoom">
+            <TgRadio
+              v-model="dataForm.isPrivateRoom"
+              :list="getDictOptions(DICT_TYPE.PRIVATE_ROOM)"
+              inline
+            >
+              <el-radio
+                v-for="(item, index) in getIntDictOptions(DICT_TYPE.PRIVATE_ROOM)"
+                :label="item.label"
+                :value="item.value"
+                :key="index"
+              >{{ item.label }}</el-radio
+              >
+            </TgRadio>
+            <el-tooltip class="item" effect="dark" placement="top-start">
+              <template #content
+              >包房:当前床位所在的房间的其他床位将被锁定,无法再为其他人办理入住。包房金额以页面显示金额为准,本房间其他床位的金额不计算在内。<br />
+                非包房:仅锁定当前床位,并不影响房间内的其他床位办理入住。</template
+              >
+              <Icon icon="ep:info-filled" />
+            </el-tooltip>
+          </el-form-item>
+
+          <el-form-item label="新床位类型" prop="expectOverheadChargeId">
+            <div style="display: flex;align-items: center;flex-direction: row">
+              <TgSelect
+                v-model="dataForm.expectOverheadChargeId"
+                placeholder="--"
+                disabled=""
+                style="width: 160px;"
+                @change="handleChangeA"
+                :list="bedList"
+                dict-label="chargeName"
+                dict-value="id"
+              >
+                <el-option v-for="t in bedList" :key="t.id" :label="t.chargeName" :value="t.id" />
+              </TgSelect>
+              <div style="margin-left: 26px">床位费原价:</div>
+              <div class="price" >¥{{ formatNum(dataForm.amount) }}元/月</div>
+              <div style="margin-left: 26px">是否打折:</div>
+              <TgSwitch style="width: 100px;" v-model="dataForm.isDiscount" @change="handleSwitchA"/>
+            </div>
+
+          </el-form-item>
+          <template v-if="dataForm.isDiscount==1">
+            <el-col :span="12" :xs="24">
+              <el-form-item label="折后金额(元)" prop="discountAmount">
+                <TgInput v-model="discountAmount" append-text="¥" @blur="handleBlurA(1)" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12" :xs="24">
+              <el-form-item label="折扣率" prop="discount">
+                <TgInputNumber disabled v-model="discount" @blur="handleBlurA(2)" />
+              </el-form-item>
+            </el-col>
+          </template>
+        </el-col>
+
+<!--        /////////////////长者B-->
+        <el-col :span="12" :xs="24">
+          <el-form-item label="长者姓名" prop="elderlyIdB">
+            <SelectElder v-model="dataForm.elderlyIdB" @elder="handleSelectElderB" type="1" :tId="dataForm.tenantId"/>
+          </el-form-item>
+
+          <el-form-item label="原床位" prop="originalNameB">
+            <TgInput v-model="dataForm.originalNameB" disabled />
+          </el-form-item>
+
+          <el-form-item label="原床位类型" prop="overheadChargeIdB">
+            <TgSelect
+              v-model="dataForm.overheadChargeIdB"
+              placeholder="请选择"
+              disabled
+              :list="bedList"
+              dict-label="chargeName"
+              dict-value="id"
+            >
+              <el-option v-for="t in bedList" :key="t.id" :label="t.chargeName" :value="t.id" />
+            </TgSelect>
+          </el-form-item>
+
+          <el-form-item label="变更生效日期" prop="changeDateB">
+            <TgDatePicker v-model="dataForm.changeDate" type="date" placeholder="变更生效日期" disabled />
+          </el-form-item>
+
+          <el-form-item label="新床位" prop="expectIdB">
+            <SelectRoom v-model="dataForm.expectIdB"  @bed="bedChangeB"  :disabled="true" status="0" :tId="dataForm.tenantId"/>
+          </el-form-item>
+
+          <el-form-item label="是否包房" prop="isPrivateRoomB">
+            <TgRadio
+              v-model="dataForm.isPrivateRoomB"
+              :list="getDictOptions(DICT_TYPE.PRIVATE_ROOM)"
+              inline
+            >
+              <el-radio
+                v-for="(item, index) in getIntDictOptions(DICT_TYPE.PRIVATE_ROOM)"
+                :label="item.label"
+                :value="item.value"
+                :key="index"
+              >{{ item.label }}</el-radio
+              >
+            </TgRadio>
+            <el-tooltip class="item" effect="dark" placement="top-start">
+              <template #content
+              >包房:当前床位所在的房间的其他床位将被锁定,无法再为其他人办理入住。包房金额以页面显示金额为准,本房间其他床位的金额不计算在内。<br />
+                非包房:仅锁定当前床位,并不影响房间内的其他床位办理入住。</template
+              >
+              <Icon icon="ep:info-filled" />
+            </el-tooltip>
+          </el-form-item>
+
+          <el-form-item label="新床位类型" prop="expectOverheadChargeIdB">
+            <div style="display: flex;align-items: center;flex-direction: row">
+              <TgSelect
+                v-model="dataForm.expectOverheadChargeIdB"
+                placeholder="--"
+                disabled=""
+                style="width: 160px;"
+                @change="handleChangeB"
+                :list="bedList"
+                dict-label="chargeName"
+                dict-value="id"
+              >
+                <el-option v-for="t in bedList" :key="t.id" :label="t.chargeName" :value="t.id" />
+              </TgSelect>
+              <div style="margin-left: 26px">床位费原价:</div>
+              <div class="price" >¥{{ formatNum(dataForm.amountB) }}元/月</div>
+              <div style="margin-left: 26px">是否打折:</div>
+              <TgSwitch style="width: 100px;" v-model="dataForm.isDiscountB" @change="handleSwitchB"/>
+            </div>
+
+          </el-form-item>
+          <template v-if="dataForm.isDiscountB==1">
+            <el-col :span="12" :xs="24">
+              <el-form-item label="折后金额(元)" prop="discountAmountB">
+                <TgInput v-model="discountAmountB" append-text="¥" @blur="handleBlurB(1)" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12" :xs="24">
+              <el-form-item label="折扣率" prop="discountB">
+                <TgInputNumber disabled v-model="discountB" @blur="handleBlurB(2)" />
+              </el-form-item>
+            </el-col>
+          </template>
+
+
+        </el-col>
+        <el-col :span="24" :xs="24">
+          <el-form-item label="变更原因" prop="reason">
+            <TgTextarea v-model="dataForm.reason" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24" :xs="24">
+          <el-form-item prop="changeFiles" label="附件">
+            <SelectUpload
+              fun-name="附件"
+              v-model="dataForm.changeFiles"
+              :elder="{ elderId: dataForm.elderlyId, elderName: dataForm.elderName }"
+            />
+          </el-form-item>
+        </el-col>
+
+      </el-row>
+    </div>
+    <div v-else>
+      <div class="info-title">长者信息</div>
+      <div class="info-wrap">
+        <el-row :gutter="20">
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item label="长者姓名" prop="elderlyId">
+              <SelectElder v-model="dataForm.elderlyId" type="1" :toggleType="isDetail"/>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item label="证件号码" prop="idCard">
+              <TgInput v-model="dataForm.idCard" disabled :toggleType="isDetail"/>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item label="变更生效日期" prop="changeDate">
+              <TgDatePicker v-model="dataForm.changeDate" type="date" placeholder="变更生效日期" :toggleType="isDetail"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </div>
+      <div class="info-title">变更前</div>
+      <div class="info-wrap">
+        <el-row :gutter="20">
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item label="床位类型" prop="overheadChargeId">
+              <TgSelect
+                v-model="dataForm.overheadChargeId"
+                placeholder="请选择"
+                clearable
+                @change="handleChange"
+                :list="bedList"
+                dict-label="chargeName"
+                dict-value="id"
+                :toggleType="isDetail"
+              >
+                <el-option v-for="t in bedList" :key="t.id" :label="t.chargeName" :value="t.id" />
+              </TgSelect>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item label="床位号" prop="originalName">
+              <TgInput v-model="dataForm.originalName" :toggleType="isDetail"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item label="床位价格" prop="originalAmount">
+              <TgInput v-model="dataForm.originalAmount" :toggleType="isDetail"/>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item label="是否打折" prop="originalIsDiscount">
+              <TgSwitch v-model="dataForm.originalIsDiscount" :toggleType="isDetail"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </div>
+      <div class="info-title">变更后</div>
+      <div class="info-wrap">
+        <el-row :gutter="20">
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item label="床位类型" prop="expectOverheadChargeId">
+              <TgSelect
+                v-model="dataForm.expectOverheadChargeId"
+                placeholder="请选择"
+                clearable
+                @change="handleChange"
+                :list="bedList"
+                dict-label="chargeName"
+                dict-value="id"
+                :toggleType="isDetail"
+              >
+                <el-option v-for="t in bedList" :key="t.id" :label="t.chargeName" :value="t.id" />
+              </TgSelect>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item label="床位号" prop="expectId">
+              <TgInput v-model="dataForm.bedName" :toggleType="isDetail"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item label="床位价格" prop="amount">
+              {{ formatNum(dataForm.amount) }}
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item :label="(diffValueStr(dataForm)==0?'差额':diffValueStr(dataForm)>0?'需补缴':'需退款')" prop="actualAmount">
+              <el-text style="font-weight: bold;color: red">{{ Math.abs(diffValueStr(dataForm)).toFixed(2) }}</el-text>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item label="是否包房" prop="isPrivateRoomB">
+              <TgRadio
+                v-model="dataForm.isPrivateRoomB"
+                :list="getDictOptions(DICT_TYPE.PRIVATE_ROOM)"
+                inline
+                :toggleType="isDetail"
+              >
+                <el-radio
+                  v-for="(item, index) in getIntDictOptions(DICT_TYPE.PRIVATE_ROOM)"
+                  :label="item.label"
+                  :value="item.value"
+                  :key="index"
+                  >{{ item.label }}</el-radio
+                >
+              </TgRadio>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="processType == 2 ? 24 : 8">
+            <el-form-item label="是否打折" prop="originalIsDiscount">
+              <TgSwitch v-model="dataForm.originalIsDiscount" :toggleType="isDetail"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="6" :xs="24" v-show="dataForm.isDiscount==1">
+            <el-form-item label="折后价格" prop="actualAmount">
+              <span class="price">{{ formatNum(dataForm.actualAmount) }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-col :span="24">
+          <el-form-item label="变更原因" prop="reason">
+            <TgTextarea v-model="dataForm.reason" :toggleType="isDetail"/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item prop="changeFiles" label="附件">
+            <SelectUpload
+              v-model="dataForm.changeFiles"
+              fun-name="附件"
+              :elder="{ elderId: dataForm.elderlyId, elderName: dataForm.elderName }"
+              :is-detail="isDetail"
+            />
+          </el-form-item>
+        </el-col>
+      </div>
+    </div>
+  </el-form>
+</template>
+<script setup lang="ts">
+import { getOverheadListByType } from '@/api/elderly/fee/overheadCharge'
+import { getChargeCategoryTree } from '@/api/elderly/fee/chargeCategory'
+import { filteredTreeData, findTreeNode } from '@/utils/tree'
+import { formatTime, getParentNodesById } from '@/utils'
+import { getIntDictOptions, DICT_TYPE, getDictOptions, getDictLabel } from '@/utils/dict'
+import { formatNum } from '@/utils/formatter'
+import { getBusinessId } from '@/api/elderly/common'
+import { getBedChangeRecordById } from '@/api/elderly/elder/bed-change'
+import { bedChangeFormType } from '../types'
+import { getTenantId } from '@/utils/auth'
+import { ElMessage } from 'element-plus'
+defineOptions({ name: 'BedChangeProcessForm' })
+
+const formRef = ref() // 表单 Ref
+const state = reactive<bedChangeFormType>({
+  dataForm: {
+    id: '',
+    elderlyId: '',
+    elderName: '',
+    idCard: '',
+    originalId: '',
+    originalName: '',
+    changeDate: formatTime(Date.now(), 'yyyy-MM-dd'),
+    expectId: '',
+    expectName: '',
+    isPrivateRoom: '',
+    overheadChargeId: '',
+    expectOverheadChargeId: '',
+    amount: 0,
+    discountAmount: '',
+    discount: undefined,
+    actualAmount: 0,
+    isDiscount: '',
+    categoryName: '',
+    originalAmount: '',
+    originalIsDiscount: '',
+    changeFiles: [],
+    reason: '',
+    startTenantId: '',
+    tenantId: undefined,
+    elderlyIdB: '',
+    elderNameB: '',
+    idCardB: '',
+    originalIdB: '',
+    originalNameB: '',
+    expectIdB: '',
+    expectNameB: '',
+    isPrivateRoomB: '',
+    overheadChargeIdB: '',
+    expectOverheadChargeIdB: '',
+    amountB: 0,
+    discountAmountB: '',
+    discountB: undefined,
+    actualAmountB: 0,
+    isDiscountB: '',
+    categoryNameB: '',
+    originalAmountB: '',
+    originalIsDiscountB: '',
+    changeFilesB: [],
+    reasonB: ''
+  },
+  dataRule: {
+    elderlyId: [{ required: true, message: '长者不能为空', trigger: 'blur' }],
+    changeDate: [{ required: true, message: '变更生效日期不能为空', trigger: 'blur' }],
+    expectId: [{ required: true, message: '新床位不能为空', trigger: 'blur' }],
+    expectOverheadChargeId: [{ required: true, message: '床位类型不能为空', trigger: 'blur' }],
+    isPrivateRoom: [{ required: true, message: '是否包房不能为空', trigger: 'blur' }],
+    discountAmount: [{ required: true, message: '折扣金额不能为空', trigger: 'blur' }],
+    discount: [{ required: true, message: '折扣率不能为空', trigger: 'blur' }],
+    elderlyIdB: [{ required: true, message: '长者B不能为空', trigger: 'blur' }],
+    expectIdB: [{ required: true, message: '长者B新床位不能为空', trigger: 'blur' }],
+    expectOverheadChargeIdB: [{ required: true, message: '长者B床位类型不能为空', trigger: 'blur' }],
+    isPrivateRoomB: [{ required: true, message: '长者B是否包房不能为空', trigger: 'blur' }]
+  }
+})
+const { dataForm, dataRule } = toRefs(state)
+const resetFormField =  reactive({ ...dataForm.value })
+const loading = ref(false)
+const discount = ref()
+const discountAmount = ref()
+const discountB = ref()
+const discountAmountB = ref()
+const processStatus = ref('')
+
+const isDetail = ref(false)
+/** 打开弹窗 */
+const init = async (id, detail, status,elderly=undefined) => {
+  dataForm.value.startTenantId = getTenantId()
+  getTreeData()
+  isDetail.value = detail
+  if(elderly){
+    //等新接口,查询后赋值
+
+
+  }
+  if (id) {
+    const res = await getBedChangeRecordById(id, status,isDetail.value)
+    dataForm.value = res
+    dataForm.value.changeFiles = res.changeFiles ? JSON.parse(res.changeFiles) : []
+    discount.value = formatNum(res.discount)
+    discountAmount.value = formatNum(res.discountAmount)
+    if(!res.startTenantId){
+      dataForm.value.startTenantId = getTenantId()
+    }
+  }
+  getOverheadList()
+}
+
+/** 提交表单 */
+const submitForm = async () => {
+  if (!formRef.value) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  return {
+    valid,
+    dataForm: {
+      elderIdA: dataForm.value.elderlyId,
+      elderIdB: dataForm.value.elderlyIdB,
+      bedIdA: dataForm.value.expectId,
+      bedIdB: dataForm.value.expectIdB,
+      startDate: dataForm.value.changeDate,
+      isPrivateRoom: dataForm.value.isPrivateRoom,
+      isPrivateRoomB: dataForm.value.isPrivateRoomB,
+      originalExpenseItemIdA: dataForm.value.expenseItemIdA,
+      originalExpenseItemIdB: dataForm.value.expenseItemIdB,
+      newItemIdA: dataForm.value.overheadChargeIdB,
+      newItemIdB: dataForm.value.overheadChargeId,
+      isDiscountA: dataForm.value.isDiscount == 1 && dataForm.value.discountAmount ? 1 : 0,
+      isDiscountB: dataForm.value.isDiscountB == 1 && dataForm.value.discountAmountB ? 1 : 0,
+      discountAmountA: dataForm.value.discountAmount ? Number(dataForm.value.discountAmount) : undefined,
+      discountAmountB: dataForm.value.discountAmountB ? Number(dataForm.value.discountAmountB) : undefined,
+      actualAmountA: Number(dataForm.value.amount),
+      actualAmountB: Number(dataForm.value.amountB),
+      reason: dataForm.value.reason,
+      changeFiles: JSON.stringify(dataForm.value.changeFiles),
+      type: 4
+    }
+  }
+}
+
+const diffValueStr = (dataForm) =>{
+  try {
+    return parseFloat((dataForm.actualAmount - dataForm.originalAmount).toFixed(2))
+  }catch (_) {}
+  return 0
+}
+
+const bedList = ref<{ chargeName: string; id: string; price: number; superiorsId: string }[]>([])
+const getOverheadList = async () => {
+  const res = await getOverheadListByType(1, dataForm.value.startTenantId)
+  bedList.value = res
+}
+
+const bedChange = () => {
+  
+}
+
+const bedChangeA = () => {
+  const item = bedList.value.find((item) => item.id == dataForm.value.expectOverheadChargeId)
+  dataForm.value.amount = Number(item?.price)
+  if (!dataForm.value.discount) {
+    dataForm.value.actualAmount = dataForm.value.amount
+  } else {
+    handleBlurA(1)
+  }
+  for (const bedItem of bedList.value) {
+    if (bedItem.id == dataForm.value.expectOverheadChargeId) {
+      dataForm.value.expectName = bedItem.chargeName
+      break
+    }
+  }
+}
+
+const bedChangeB = () => {
+  const item = bedList.value.find((item) => item.id == dataForm.value.expectOverheadChargeIdB)
+  dataForm.value.amountB = Number(item?.price)
+  if (!dataForm.value.discountB) {
+    dataForm.value.actualAmountB = dataForm.value.amountB
+  } else {
+    handleBlurB(1)
+  }
+  for (const bedItem of bedList.value) {
+    if (bedItem.id == dataForm.value.expectOverheadChargeIdB) {
+      dataForm.value.expectNameB = bedItem.chargeName
+      break
+    }
+  }
+}
+
+// 项目类别
+const treeList = ref()
+const getTreeData = async () => {
+  try {
+    loading.value = true
+    const data = await getChargeCategoryTree()
+    treeList.value = filteredTreeData(data, 'childrenList')
+  } catch (err) {
+    console.log('err', err)
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleChange = () => {
+  const item = bedList.value.find((item) => item.id == dataForm.value.expectOverheadChargeId)
+  dataForm.value.amount = Number(item?.price)
+  let node = findTreeNode(treeList.value, item?.superiorsId, 'id', 'childrenList')
+  const res = getParentNodesById(treeList.value, node.parentId, node.id)
+  if(res.length - 2 > 0){
+    for(let i = 0; i <= res.length - 2; i++){
+      res.pop()
+    }
+  }
+  dataForm.value.categoryName = res.reverse().join('-')
+  if(!dataForm.value.discount){
+    dataForm.value.actualAmount = dataForm.value.amount
+  }else{
+    handleBlurA(1)
+  }
+  for (const item of bedList.value){
+    if(item.id==dataForm.value.expectOverheadChargeId){
+      dataForm.value.expectName = item.chargeName
+      break
+    }
+  }
+}
+
+const handleChangeA = () => {
+  const item = bedList.value.find((item) => item.id == dataForm.value.expectOverheadChargeId)
+  dataForm.value.amount = Number(item?.price)
+  let node = findTreeNode(treeList.value, item?.superiorsId, 'id', 'childrenList')
+  const res = getParentNodesById(treeList.value, node.parentId, node.id)
+  if(res.length - 2 > 0){
+    for(let i = 0; i <= res.length - 2; i++){
+      res.pop()
+    }
+  }
+  dataForm.value.categoryName = res.reverse().join('-')
+  if(!dataForm.value.discount){
+    dataForm.value.actualAmount = dataForm.value.amount
+  }else{
+    handleBlurA(1)
+  }
+  for (const item of bedList.value){
+    if(item.id==dataForm.value.expectOverheadChargeId){
+      dataForm.value.expectName = item.chargeName
+      break
+    }
+  }
+}
+
+const handleChangeB = () => {
+  const item = bedList.value.find((item) => item.id == dataForm.value.expectOverheadChargeIdB)
+  dataForm.value.amountB = Number(item?.price)
+  let node = findTreeNode(treeList.value, item?.superiorsId, 'id', 'childrenList')
+  const res = getParentNodesById(treeList.value, node.parentId, node.id)
+  if(res.length - 2 > 0){
+    for(let i = 0; i <= res.length - 2; i++){
+      res.pop()
+    }
+  }
+  dataForm.value.categoryNameB = res.reverse().join('-')
+  if(!dataForm.value.discountB){
+    dataForm.value.actualAmountB = dataForm.value.amountB
+  }else{
+    handleBlurB(1)
+  }
+  for (const item of bedList.value){
+    if(item.id==dataForm.value.expectOverheadChargeIdB){
+      dataForm.value.expectNameB = item.chargeName
+      break
+    }
+  }
+}
+
+// 是否启用打折
+const handleSwitch = () => {
+  dataForm.value.actualAmount = dataForm.value.amount
+  dataForm.value.discountAmount = ''
+  dataForm.value.discount = undefined
+  discount.value = undefined
+  discountAmount.value = ''
+}
+
+const handleSwitchA = () => {
+  dataForm.value.actualAmount = dataForm.value.amount
+  dataForm.value.discountAmount = ''
+  dataForm.value.discount = undefined
+  discount.value = undefined
+  discountAmount.value = ''
+}
+
+const handleSwitchB = () => {
+  dataForm.value.actualAmountB = dataForm.value.amountB
+  dataForm.value.discountAmountB = ''
+  dataForm.value.discountB = undefined
+  discountB.value = undefined
+  discountAmountB.value = ''
+}
+
+// 折扣和折扣率换算
+const handleBlur = (item, type = 0) => {
+  if (type == 1) {
+    item.discountAmount = discountAmount.value
+    item.actualAmount = item.amount - discountAmount.value
+    item.discount = item.discountAmount ? (1000 - (item.discountAmount / item.amount) * 1000) * 10 / 1000 : ''
+    discount.value = item.discount
+  } else if (type == 2) {
+    item.discount = discount.value
+    item.actualAmount = item.amount * (((item.discount / 100) * 10 * 100) / 100)
+    item.discountAmount = ((item.amount * (1 - item.discount / 10)) / 10) * 10
+    discountAmount.value = formatNum(((item.amount * (1 - item.discount / 10)) / 10) * 10)
+  }
+  item.totalAmount = item.isDiscount
+    ? item.discount
+      ? item.actualAmount
+      : item.amount
+    : item.amount
+}
+
+const handleBlurA = (type = 0) => {
+  if (type == 1) {
+    dataForm.value.discountAmount = discountAmount.value
+    dataForm.value.actualAmount = dataForm.value.amount - discountAmount.value
+    dataForm.value.discount = dataForm.value.discountAmount ? (1000 - (discountAmount.value / dataForm.value.amount) * 1000) * 10 / 1000 : ''
+    discount.value = dataForm.value.discount
+  } else if (type == 2) {
+    dataForm.value.discount = discount.value
+    dataForm.value.actualAmount = dataForm.value.amount * (((discount.value / 100) * 10 * 100) / 100)
+    dataForm.value.discountAmount = ((dataForm.value.amount * (1 - discount.value / 10)) / 10) * 10
+    discountAmount.value = formatNum(((dataForm.value.amount * (1 - discount.value / 10)) / 10) * 10)
+  }
+}
+
+const handleBlurB = (type = 0) => {
+  if (type == 1) {
+    dataForm.value.discountAmountB = discountAmountB.value
+    dataForm.value.actualAmountB = dataForm.value.amountB - discountAmountB.value
+    dataForm.value.discountB = dataForm.value.discountAmountB ? (1000 - (discountAmountB.value / dataForm.value.amountB) * 1000) * 10 / 1000 : ''
+    discountB.value = dataForm.value.discountB
+  } else if (type == 2) {
+    dataForm.value.discountB = discountB.value
+    dataForm.value.actualAmountB = dataForm.value.amountB * (((discountB.value / 100) * 10 * 100) / 100)
+    dataForm.value.discountAmountB = ((dataForm.value.amountB * (1 - discountB.value / 10)) / 10) * 10
+    discountAmountB.value = formatNum(((dataForm.value.amountB * (1 - discountB.value / 10)) / 10) * 10)
+  }
+}
+
+//let overheadChargeId = 0;
+const handleSelectElder = (item) => {
+  dataForm.value.idCard = item.idCard
+  dataForm.value.elderName = item.elderName
+  dataForm.value.overheadChargeId = item.overheadChargeId
+  dataForm.value.originalId = item.bedId
+  dataForm.value.originalName = item.bedName
+  dataForm.value.originalAmount = item.actualAmount
+  //overheadChargeId = item.overheadChargeId
+}
+
+const handleSelectElderA = (item) => {
+  if (dataForm.value.elderlyIdB && dataForm.value.elderlyIdB === item.id) {
+    ElMessage.warning('不能选择同一个长者')
+    dataForm.value.elderlyId = ''
+    return
+  }
+  dataForm.value.isDiscountB = 0
+  dataForm.value.idCard = item.idCard
+  dataForm.value.elderName = item.elderName
+  dataForm.value.expenseItemIdA = item.expenseItemId
+  dataForm.value.overheadChargeId = item.overheadChargeId
+  dataForm.value.originalId = item.bedId
+  dataForm.value.originalName = item.bedName
+  dataForm.value.originalAmount = item.actualAmount
+  if (dataForm.value.elderlyIdB) {
+    handleSwapBed()
+  }
+
+}
+
+const handleSelectElderB = (item) => {
+  if (dataForm.value.elderlyId && dataForm.value.elderlyId === item.id) {
+    ElMessage.warning('不能选择同一个长者')
+    dataForm.value.elderlyIdB = ''
+    return
+  }
+  dataForm.value.isDiscount = 0
+  dataForm.value.idCardB = item.idCard
+  dataForm.value.elderNameB = item.elderName
+  dataForm.value.expenseItemIdB = item.expenseItemId
+  dataForm.value.overheadChargeIdB = item.overheadChargeId
+  dataForm.value.originalIdB = item.bedId
+  dataForm.value.originalNameB = item.bedName
+  dataForm.value.originalAmountB = item.actualAmount
+  if (dataForm.value.elderlyId) {
+    handleSwapBed()
+  }
+
+}
+
+const handleSwapBed = () => {
+  const tempIsPrivateRoom = dataForm.value.isPrivateRoom
+  
+  dataForm.value.expectId = dataForm.value.originalIdB
+  dataForm.value.expectOverheadChargeId = dataForm.value.overheadChargeIdB
+  dataForm.value.amount = Number(dataForm.value.originalAmountB) || 0
+  dataForm.value.actualAmount = Number(dataForm.value.originalAmountB) || 0
+  dataForm.value.isDiscount = 0
+  dataForm.value.isPrivateRoom = dataForm.value.isPrivateRoomB
+
+  dataForm.value.expectIdB = dataForm.value.originalId
+  dataForm.value.expectOverheadChargeIdB = dataForm.value.overheadChargeId
+  dataForm.value.amountB = Number(dataForm.value.originalAmount) || 0
+  dataForm.value.actualAmountB = Number(dataForm.value.originalAmount) || 0
+  dataForm.value.isDiscountB = 0
+  dataForm.value.isPrivateRoomB = tempIsPrivateRoom
+}
+
+const handleChangeBed = () => {
+  for (const item of bedList.value){
+    if(item.id==dataForm.value.expectOverheadChargeId){
+      dataForm.value.expectName = item.chargeName
+      break
+    }
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  dataForm.value = {...resetFormField}
+  formRef.value?.resetFields()
+}
+
+const disabledDate = (time) =>{
+  const now = new Date()
+  const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
+  const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
+  return time < startOfMonth || time > endOfMonth
+}
+
+const processType = ref()
+const getProcess = async (id, type, status) => {
+  const res = await getBusinessId(id)
+  init(res.businessId, true, status)
+  processType.value = type
+}
+
+const setTenantId = (tId) => {
+  dataForm.value.tenantId = tId
+}
+
+defineExpose({ init, submitForm, getProcess, resetForm, setTenantId }) // 提供 open 方法,用于打开弹窗
+</script>
+<style lang="scss" scoped>
+.bed-change-form {
+  .info {
+    position: relative;
+
+    .right {
+      position: absolute;
+      right: 10px;
+      top: 20px;
+    }
+  }
+
+  .price {
+    color: #ff801d;
+  }
+}
+</style>

+ 10 - 3
src/views/elderly/elder/bed-change/ProcessForm.vue

@@ -279,7 +279,7 @@ import { formatTime, getParentNodesById } from '@/utils'
 import { getIntDictOptions, DICT_TYPE, getDictOptions, getDictLabel } from '@/utils/dict'
 import { formatNum } from '@/utils/formatter'
 import { getBusinessId } from '@/api/elderly/common'
-import { getBedChangeRecordById } from '@/api/elderly/elder/bed-change'
+import {getBedChangeRecordById, getBedChangeRecordElderlyById} from '@/api/elderly/elder/bed-change'
 import { bedChangeFormType } from '../types'
 import { getTenantId } from '@/utils/auth'
 defineOptions({ name: 'BedChangeProcessForm' })
@@ -337,9 +337,16 @@ const init = async (id, detail, status,elderly=undefined) => {
   isDetail.value = detail
   if(elderly){
     //等新接口,查询后赋值
-
-
+    const item = await getBedChangeRecordElderlyById(elderly.elderId)
+    dataForm.value.elderlyId = item.id
+    dataForm.value.idCard = item.idCard
+    dataForm.value.elderName = item.elderName
+    dataForm.value.overheadChargeId = item.overheadChargeId
+    dataForm.value.originalId = item.bedId
+    dataForm.value.originalName = item.bedName
+    dataForm.value.originalAmount = item.actualAmount
   }
+  //http://192.168.100.21:48080/admin-api/build/getBedList?bedName=&status=0&tenantIds=0&orgType=1
   if (id) {
     const res = await getBedChangeRecordById(id, status,isDetail.value)
     dataForm.value = res

+ 23 - 1
src/views/elderly/elder/bed-change/index.vue

@@ -35,7 +35,18 @@
 
   <!-- 列表 -->
   <ContentWrap>
-    <TabBarBtn @add="openForm()" />
+    <TabBarBtn @add="openForm()" >
+<!--      <template #next>-->
+<!--        <el-button-->
+<!--          type="warning"-->
+<!--          plain-->
+<!--          @click="openExchangeForm()"-->
+<!--          v-hasPermi="['bed-change:exchange']"-->
+<!--        >-->
+<!--          <Icon icon="ep:plus" class="mr-5px" /> 床位对调-->
+<!--        </el-button>-->
+<!--      </template>-->
+    </TabBarBtn>
     <Table2
       v-loading="loading"
       :data="list"
@@ -56,6 +67,7 @@
   </ContentWrap>
 
   <Form ref="formRef" @success="getList" />
+
   <Detail ref="detailRef" />
 </template>
 <script lang="ts" setup>
@@ -64,6 +76,7 @@ import { getBedChangePage } from '@/api/elderly/elder/bed-change'
 import { BedChangeColumns } from '../column'
 import { cancelprocessInstance } from '@/api/elderly/common'
 import Form from './Form.vue'
+import ExchangeForm from './ExchangeForm.vue'
 import Detail from './Detail.vue'
 import { useUserStore } from '@/store/modules/user'
 defineOptions({ name: 'BedChange' })
@@ -110,6 +123,15 @@ const resetQuery = () => {
   handleQuery()
 }
 
+// const formExchangeRef = ref()
+// const openExchangeForm = (row={}) => {
+//   if(queryParams.tenantIds.length == 0 || queryParams.tenantIds.length > 1 && !row.tenantId){
+//     message.error('新增只能选择一个机构')
+//     return
+//   }
+//   formExchangeRef.value.open(row.tenantId || queryParams.tenantIds[0], row.id, false, row.status)
+// }
+
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (row: any = {}) => {

+ 25 - 0
src/views/elderly/elder/types.ts

@@ -434,6 +434,8 @@ export interface bedChangeFormType {
     expectName: string
     isPrivateRoom: string
     overheadChargeId: string
+    expenseItemIdA: string
+    expenseItemIdB: string
     expectOverheadChargeId: string
     amount: number
     discountAmount: string
@@ -450,6 +452,29 @@ export interface bedChangeFormType {
     reason: string
     startTenantId: string
     tenantId: undefined | number
+    elderlyIdB: string
+    elderNameB: string
+    idCardB: string
+    originalIdB: string
+    originalNameB: string
+    expectIdB: string
+    expectNameB: string
+    isPrivateRoomB: string
+    overheadChargeIdB: string
+    expectOverheadChargeIdB: string
+    amountB: number
+    discountAmountB: string
+    discountB: undefined
+    actualAmountB: number
+    isDiscountB: string
+    categoryNameB: string
+    originalAmountB: string
+    originalIsDiscountB: string
+    changeFilesB: {
+      fileUrl: string
+      fileName: string
+    }[]
+    reasonB: string
   }
   dataRule: FormRules
 }

+ 15 - 1
src/views/elderly/kanban/room-diagram/index.vue

@@ -82,6 +82,10 @@
                 </el-checkbox-group>
               </el-form-item>
               <el-form-item>
+                <el-button type="warning" @click="openExchangeForm">床位对调</el-button>
+              </el-form-item>
+
+              <el-form-item>
                 <el-button @click="handleQuery" type="primary">
                   <Icon icon="ep:search" class="mr-5px" /> 搜索
                 </el-button>
@@ -210,6 +214,7 @@
   </el-row>
 
   <Form ref="bedChangeFormRef"   />
+  <ExchangeForm ref="formExchangeRef"  />
 
 </template>
 <script setup lang="ts">
@@ -223,6 +228,8 @@ import { cancelRequest } from '@/config/axios/service';
 import { generateUUID } from '@/utils'
 import { values } from 'lodash-es'
 import Form from "@/views/elderly/elder/bed-change/Form.vue";
+import {getTenantId} from "@/utils/auth";
+import ExchangeForm from "@/views/elderly/elder/bed-change/ExchangeForm.vue";
 const router = useRouter()
 const buildList = ref<{ id: string, buildName: string, floorList: [] }[]>([])
 const floorList = ref<{id: string, floorName: string}[]>([])
@@ -309,6 +316,12 @@ const resetQuery = () => {
   handleQuery()
 }
 
+
+const formExchangeRef = ref()
+const openExchangeForm = () => {
+  formExchangeRef.value.open(props.tenantId, undefined, false, undefined)
+}
+
 const getList = async () => {
   if (props.tenantId != undefined){
     queryParams.value.tenantIds = [parseInt(props.tenantId)]
@@ -326,7 +339,8 @@ let tempElder = {}
 const bedChangeClick = (i) => {
   tempElder = i
   if(i){
-    bedChangeFormRef.value.open(props.tenantId, undefined,false, undefined,i)
+
+    bedChangeFormRef.value.open(getTenantId(), undefined,false, undefined,i)
   }
   console.log( i)
 }

+ 139 - 118
src/views/social-worker/floor-activity-record/photo/AddForm.vue

@@ -12,43 +12,40 @@
       <div class="info-wrap">
         <el-row>
           <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
-            <el-form-item label="记录年月" prop="recordYear">
+            <el-form-item label="记录年月" prop="recordMonth">
               <el-date-picker
                 v-if="!isDetail"
-                v-model="dataForm.recordYear"
+                v-model="dataForm.recordMonth"
                 type="month"
                 placeholder="选择年月"
                 value-format="YYYY-MM"
                 style="width: 100%"
-                @change="handleYearChange"
               />
-              <el-text v-else>{{dataForm.recordYear}}</el-text>
+              <el-text v-else>{{dataForm.recordMonth}}</el-text>
             </el-form-item>
           </el-col>
           <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
-            <el-form-item label="第几周" prop="weekNumber">
-              <el-select v-if="!isDetail" v-model="dataForm.weekNumber" placeholder="选择周次" style="width: 100%">
-                <el-option v-for="w in 5" :key="w" :label="'第' + ['一', '二', '三', '四', '五'][w-1] + '周'" :value="w" />
+            <el-form-item label="第几周" prop="week">
+              <el-select v-if="!isDetail" v-model="dataForm.week" placeholder="选择周次" style="width: 100%">
+                <el-option v-for="(w,index) in 5" :key="w" :label="'第' + ['一', '二', '三', '四', '五'][w-1] + '周'" :value="(index+1)" />
               </el-select>
-              <el-text v-else>第{{['一', '二', '三', '四', '五'][dataForm.weekNumber-1]}}周</el-text>
+              <el-text v-else>第{{dataForm.week}}周</el-text>
             </el-form-item>
           </el-col>
           <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
-            <el-form-item label="楼栋" prop="buildName">
-              <el-select v-if="!isDetail" v-model="dataForm.buildName" placeholder="选择楼栋" style="width: 100%">
-                <el-option @click="handleBuildChange(item)" v-for="(item,index) in buildList" :key="index" :label="item.buildName" :value="item.id" />
+            <el-form-item label="楼栋" prop="buildId">
+              <el-select :disabled="isDetail" v-model="dataForm.buildId" placeholder="选择楼栋" style="width: 100%" @change="handleBuildChange">
+                <el-option  v-for="(item,index) in buildList" :key="index" :label="item.buildName" :value="item.id" />
               </el-select>
-              <el-text v-else>{{dataForm.buildName}}</el-text>
             </el-form-item>
           </el-col>
         </el-row>
         <el-row>
           <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
-            <el-form-item label="楼层" prop="floorName">
-              <el-select v-if="!isDetail" v-model="dataForm.floorName" placeholder="选择楼层" style="width: 100%">
+            <el-form-item label="楼层" prop="floorId">
+              <el-select :disabled="isDetail" v-model="dataForm.floorId" placeholder="选择楼层" style="width: 100%" @change="handleFloorChange">
                 <el-option v-for="(item,index) in floorList" :key="index" :label="item.floorName" :value="item.id" />
               </el-select>
-              <el-text v-else>{{dataForm.floorName}}</el-text>
             </el-form-item>
           </el-col>
           <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
@@ -68,7 +65,8 @@
         <el-row>
           <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
             <el-form-item label="参与长者">
-              <el-input type="textarea" placeholder="需要注意,这里的长者会用于列表页的长者名称模糊搜索" maxlength="500" show-word-limit :rows="4">{{ dateRangeText }}</el-input>
+              <el-input v-if="!isDetail" v-model="dataForm.elderName" type="textarea" placeholder="输入参与长者,逗号分开" maxlength="500" show-word-limit :rows="4" />
+              <el-text v-else>{{ dataForm.elderName }}</el-text>
             </el-form-item>
           </el-col>
         </el-row>
@@ -90,11 +88,11 @@
             <!-- 所属楼层和活动日期 -->
             <tr>
               <td class="label-cell">所属楼层</td>
-              <td class="value-cell">{{ dataForm.floorName || 'X' }}楼</td>
+              <td class="value-cell">{{ dataForm.belongFloor || 'X' }}</td>
             </tr>
             <tr>
               <td class="label-cell">活动日期</td>
-              <td class="value-cell">{{ dateRangeText }}</td>
+              <td class="value-cell">{{ activityDate }}</td>
             </tr>
             <!-- 活动内容 -->
             <tr>
@@ -138,11 +136,11 @@
                     <div class="photo-upload-wrapper">
                       <div style="width: 160px;" v-if="!isDetail">
                         <SelectUpload
-                          v-if="!isDetail" :limit="1" v-model="dataForm.changeFilesList" fun-name="附件" :is-detail="isDetail"
+                          v-if="!isDetail" :limit="1" v-model="photo.url" fun-name="附件" :is-detail="isDetail"
                         />
                       </div>
                       <div v-else class="photo-preview-wrapper">
-                        <img v-if="photo.url" :src="photo.url" class="photo-preview"  alt=""/>
+                        <img v-if="photo.url && photo.url.length > 0 && photo.url[0].fileUrl" :src="photo.url[0].fileUrl" class="photo-preview"  alt=""/>
                         <div v-else class="no-photo">暂无照片</div>
                       </div>
                       <!-- 照片描述 -->
@@ -215,85 +213,63 @@ const uploadHeaders = {
   'tenant-id': getTenantId()
 }
 
-// 照片列表
-const photoList = ref<Array<{ url: string; description: string }>>([
-  { url: '', description: '' }
-])
-
 // 表单数据
 let dataForm = ref({
   id: undefined,
-  recordMonth: dayjs().format('YYYY-MM'), // 记录年
+  recordMonth: dayjs().format('YYYY-MM'), // 记录年月
   week: 1, // 第几周
-  elderName: '', //
-  buildId: '', // 楼栋
-  floorId: '', // 楼层
-  belongFloor: '', // 楼层
-  activityDate: '', // 楼层
-  activityContent: '', // 楼层
+  elderName: '', // 长者姓名
+  buildId: undefined as number | undefined, // 楼栋id
+  floorId: undefined as number | undefined, // 楼层id
+  belongFloor: '', //所属楼层
+  activityContent: '', // 活动内容
   recordTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), // 记录时间
-  photos: '', // 照片JSON
-  tenantId: undefined
+  tenantId: undefined as number | undefined
 })
 
-// 表单规则
-const dataRule = reactive<FormRules>({
-  recordYear: [{ required: true, message: '记录年份不能为空', trigger: 'change' }],
-  weekNumber: [{ required: true, message: '周次不能为空', trigger: 'change' }],
-  buildName: [{ required: true, message: '楼栋不能为空', trigger: 'change' }],
-  floorName: [{ required: true, message: '楼层不能为空', trigger: 'change' }],
-  recordTime: [{ required: true, message: '记录时间不能为空', trigger: 'blur' }],
-  activityContent: [{ required: true, message: '活动内容不能为空', trigger: 'blur' }]
-})
+// 照片列表(用于页面展示,提交时转换为JSON保存到activityImage)
+// url 是数组格式 [{fileUrl: 'xxx', fileName: 'xxx'}],用于 SelectUpload 组件
+const photoList = ref<Array<{ url: any[]; description: string }>>([
+  { url: [], description: '' }
+])
 
-// 计算日期范围文本
-const dateRangeText = computed(() => {
-  if (!dataForm.value.startDate || !dataForm.value.endDate) {
-    return 'X月X日-X月X日'
+// 计算活动日期(根据年月和周次)
+const activityDate = computed(() => {
+  const yearMonth = dataForm.value.recordMonth
+  const weekNum = dataForm.value.week
+  
+  if (!yearMonth || !weekNum) {
+    return ''
   }
-  return `${dataForm.value.startDate}-${dataForm.value.endDate}`
-})
-
-// 获取指定年月第几周的起止日期
-const getWeekDateRange = (year: number, month: number, weekNum: number) => {
+  
+  const [year, month] = yearMonth.split('-').map(Number)
   // 获取该月第一天
   const firstDayOfMonth = dayjs(`${year}-${month}-01`)
   // 获取该月第一天是星期几 (0=周日, 1=周一...)
   const firstDayWeek = firstDayOfMonth.day()
   // 计算第一周的起始日期(周日开始)
   let weekStart = firstDayOfMonth.subtract(firstDayWeek, 'day')
-
+  
   // 调整到指定的周
   weekStart = weekStart.add((weekNum - 1) * 7, 'day')
   const weekEnd = weekStart.add(6, 'day')
+  
+  return `${weekStart.format('M月D日')}-${weekEnd.format('M月D日')}`
+})
 
-  return {
-    start: weekStart.format('M月D日'),
-    end: weekEnd.format('M月D日')
-  }
-}
-
-// 更新周日期显示
-const updateWeekDays = () => {
-  const yearMonth = dataForm.value.recordYear
-  const weekNum = dataForm.value.weekNumber
-
-  if (yearMonth && weekNum) {
-    const [year, month] = yearMonth.split('-').map(Number)
-    const range = getWeekDateRange(year, month, weekNum)
-    dataForm.value.startDate = range.start
-    dataForm.value.endDate = range.end
-  }
-}
-
-// 年份变化
-const handleYearChange = () => {
-  updateWeekDays()
-}
+// 表单规则
+const dataRule = reactive<FormRules>({
+  recordMonth: [{ required: true, message: '记录年份不能为空', trigger: 'change' }],
+  week: [{ required: true, message: '周次不能为空', trigger: 'change' }],
+  buildId: [{ required: true, message: '楼栋不能为空', trigger: 'change' }],
+  floorId: [{ required: true, message: '楼层不能为空', trigger: 'change' }],
+  recordTime: [{ required: true, message: '记录时间不能为空', trigger: 'blur' }],
+  activityContent: [{ required: true, message: '活动内容不能为空', trigger: 'blur' }]
+})
 
-// 监听周次变化
-watch(() => dataForm.value.weekNumber, () => {
-  updateWeekDays()
+// 监听周次变化(activityDate是计算属性,会自动更新)
+watch(() => dataForm.value.week, () => {
+  // 周次变化时,activityDate计算属性会自动重新计算
 })
 
 // 计算窗口大小
@@ -306,18 +282,12 @@ const labelWidth = computed(() => {
 const buildList = ref([])
 const floorList = ref([])
 
-const handleBuildChange = (e) => {
-  floorList.value = e.floorList
-}
 
-// 照片上传成功
+
+// 照片上传成功(SelectUpload 组件通过 v-model 自动更新,这里不需要额外处理)
 const handlePhotoSuccess = (res: any, index: number) => {
-  if (res.code === 0) {
-    photoList.value[index].url = res.data
-    message.success('上传成功')
-  } else {
-    message.error(res.msg || '上传失败')
-  }
+  // SelectUpload 组件会自动更新 photoList.value[index].url
+  message.success('上传成功')
 }
 
 // 照片上传前校验
@@ -339,7 +309,7 @@ const beforePhotoUpload = (file: File) => {
 
 // 添加照片
 const addPhoto = () => {
-  photoList.value.push({ url: '', description: '' })
+  photoList.value.push({ url: [], description: '' })
 }
 
 // 删除照片
@@ -347,6 +317,25 @@ const removePhoto = (index: number) => {
   photoList.value.splice(index, 1)
 }
 
+
+const handleBuildChange = (buildId: any) => {
+  const selectedBuild = buildList.value.find((item: any) => item.id === buildId)
+  if (selectedBuild) {
+    floorList.value = selectedBuild.floorList || []
+    // 清空楼层选择
+    dataForm.value.floorId = undefined
+    dataForm.value.belongFloor = ''
+  }
+}
+
+const handleFloorChange = (floorId: any) => {
+  const selectedFloor = floorList.value.find((item: any) => item.id === floorId)
+  if (selectedFloor) {
+    dataForm.value.belongFloor = selectedFloor.floorName
+  }
+}
+
+
 /** 打开弹窗 */
 const open = async (tenantId, id?: any, detail: boolean = false) => {
   resetForm()
@@ -365,27 +354,48 @@ const open = async (tenantId, id?: any, detail: boolean = false) => {
     title.value = "新增-楼层活动照片记录"
   }
 
-  // 初始化周日期
-  updateWeekDays()
-
   if (id) {
     try {
       loading.value = true
       const res = await floorActivityPhotoRecordGetInfo(id)
+
+      try {
+        const selectedBuild = buildList.value.find((item: any) => item.id === res.buildId)
+        console.log("AA",buildList.value,selectedBuild)
+        if (selectedBuild) {
+          floorList.value = selectedBuild.floorList || []
+          console.log("AA",floorList.value)
+        }
+      }catch (e) {}
+
       // 解析后端返回的数据
       dataForm.value = {
-        ...res,
-        recordYear: res.recordYear ? `${res.recordYear}-${String(res.recordMonth).padStart(2, '0')}` : dayjs().format('YYYY-MM'),
+        id: res.id,
+        recordMonth: res.recordMonth,
+        week: res.week,
+        elderName: res.elderName || '',
+        buildId: res.buildId,
+        floorId: res.floorId,
+        belongFloor: res.belongFloor || '',
+        activityContent: res.activityContent || '',
+        recordTime: res.recordTime || dayjs().format('YYYY-MM-DD HH:mm:ss'),
+        tenantId: res.tenantId
       }
-      // 解析照片数据
-      if (res.photos) {
+      // 解析照片数据(从activityImage字段解析JSON)
+      if (res.activityImage) {
         try {
-          photoList.value = JSON.parse(res.photos)
+          const parsedList = JSON.parse(res.activityImage)
+          // 转换数据格式:将字符串 url 转换为数组格式
+          photoList.value = parsedList.map((item: any) => ({
+            url: item.url ? (Array.isArray(item.url) ? item.url : [{ fileUrl: item.url, fileName: '' }]) : [],
+            description: item.description || ''
+          }))
         } catch (e) {
-          photoList.value = [{ url: '', description: '' }]
+          photoList.value = [{ url: [], description: '' }]
         }
+      } else {
+        photoList.value = [{ url: [], description: '' }]
       }
-      updateWeekDays()
       loading.value = false
     } catch (err) {
       loading.value = false
@@ -410,20 +420,33 @@ const submitForm = async () => {
     const valid = await formRef.value.validate()
     if (!valid) return
 
-    // 过滤掉空照片
-    const validPhotos = photoList.value.filter(p => p.url || p.description)
+    // 过滤掉空照片(检查 url 数组是否有内容)
+    const validPhotos = photoList.value.filter(p => (p.url && p.url.length > 0) || p.description)
     if (validPhotos.length === 0) {
       message.error('请至少上传一张照片')
       return
     }
 
     // 构建提交参数
-    const [year, month] = dataForm.value.recordYear.split('-').map(Number)
+    // 转换 photoList 格式:将 url 数组转换为字符串,便于后台存储
+    const photosForSubmit = photoList.value.map(photo => ({
+      url: photo.url && photo.url.length > 0 ? photo.url[0].fileUrl : '',
+      description: photo.description
+    }))
+    
     const tempParams = {
-      ...dataForm.value,
-      recordYear: year,
-      recordMonth: month,
-      photos: JSON.stringify(photoList.value)
+      id: dataForm.value.id,
+      activityContent: dataForm.value.activityContent,
+      activityDate: activityDate.value, // 活动日期范围
+      activityImage: JSON.stringify(photosForSubmit), // 照片列表转为JSON字符串
+      belongFloor: dataForm.value.belongFloor,
+      buildId: dataForm.value.buildId,
+      elderName: dataForm.value.elderName,
+      floorId: dataForm.value.floorId,
+      recordMonth: dataForm.value.recordMonth,
+      recordTime: dataForm.value.recordTime,
+      tenantId: dataForm.value.tenantId,
+      week: dataForm.value.week
     }
 
     if (dataForm.value.id) {
@@ -454,19 +477,17 @@ const submitForm = async () => {
 const resetForm = () => {
   dataForm.value = {
     id: undefined,
-    recordYear: dayjs().format('YYYY-MM'),
-    recordMonth: dayjs().month() + 1,
-    weekNumber: 1,
-    buildName: '',
-    floorName: '',
-    recordTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
-    startDate: '',
-    endDate: '',
-    activityContent: '',
-    photos: '',
+    recordMonth: dayjs().format('YYYY-MM'), // 记录年月
+    week: 1, // 第几周
+    elderName: '', // 长者姓名
+    buildId: undefined, // 楼栋id
+    floorId: undefined, // 楼层id
+    belongFloor: '', //所属楼层
+    activityContent: '', // 活动内容
+    recordTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), // 记录时间
     tenantId: undefined
   }
-  photoList.value = [{ url: '', description: '' }]
+  photoList.value = [{ url: [], description: '' }]
   formRef.value?.resetFields()
   floorList.value = []
 }

+ 279 - 112
src/views/social-worker/floor-activity-record/photo/index.vue

@@ -18,21 +18,15 @@
       </el-form-item>
 
       <el-form-item label="楼栋">
-        <el-input
-          v-model="queryParams.elderName"
-          placeholder="输入楼栋"
-          class="!w-200px"
-          clearable
-        />
+        <el-select v-model="queryParams.buidId" placeholder="选择楼栋" style="width: 180px" @change="handleBuildChange">
+          <el-option v-for="(item,index) in buildList" :key="index" :label="item.buildName" :value="item.id" />
+        </el-select>
       </el-form-item>
 
       <el-form-item label="楼层">
-        <el-input
-          v-model="queryParams.elderName"
-          placeholder="输入楼层"
-          class="!w-200px"
-          clearable
-        />
+        <el-select  v-model="queryParams.floorId" placeholder="选择楼层" style="width: 180px" >
+          <el-option v-for="(item,index) in floorList" :key="index" :label="item.floorName" :value="item.id" />
+        </el-select>
       </el-form-item>
 
       <el-form-item label="记录年月">
@@ -40,7 +34,7 @@
           size="default"
           ref="selectRef"
           class="!w-240px"
-          v-model="queryParams.recordTime"
+          v-model="queryParams.recordMonthRange"
           type="monthrange"
           :clearable="true"
           :editable="false"
@@ -75,15 +69,15 @@
         </template>
       </el-table-column>
 
-<!--      <el-table-column prop="elderName" header-align="center" align="center" label="长者姓名" min-width="150" show-overflow-tooltip/>-->
-      <!--      <el-table-column prop="bedInfo" header-align="center" align="center" label="床位号" min-width="200" show-overflow-tooltip/>-->
+
+      <el-table-column prop="activityDate" header-align="center" align="center" label="活动日期" min-width="200" show-overflow-tooltip/>
       <el-table-column prop="recordTime" header-align="center" align="center" label="记录时间" min-width="150" show-overflow-tooltip>
         <template #default="scope">
           {{(scope.row.recordTime)}}
         </template>
       </el-table-column>
-      <el-table-column prop="templateName" header-align="center" align="center" label="表名" min-width="220" show-overflow-tooltip/>
-      <el-table-column prop="creator" header-align="center" align="center" label="操作人" min-width="150" show-overflow-tooltip/>
+      <el-table-column prop="activityContent" header-align="center" align="center" label="活动内容" min-width="220" show-overflow-tooltip/>
+
 
       <el-table-column label="操作" align="center" min-width="200" >
         <template #default="scope">
@@ -98,7 +92,7 @@
           <el-button
             link
             type="warning"
-            @click="openFormDetail(scope.row.id)"
+            @click="openFormDetail(scope.row, scope.row.id)"
           >
             详情
           </el-button>
@@ -123,34 +117,9 @@
   </ContentWrap>
 
   <AddForm ref="formRef" @success="getList" />
-  <DetailForm ref="detailRef" @success="getList" />
-
-  <!-- 通用批量导出弹窗 -->
-  <BatchExportDialog
-    :show="showBatchExport"
-    :title="exportConfig.title"
-    :loading="exportLoading"
-    :batch-min="exportConfig.batchMin"
-    :batch-max="exportConfig.batchMax"
-    :count-min="exportConfig.countMin"
-    :count-max="exportConfig.countMax"
-    :default-batch="exportConfig.defaultBatch"
-    :default-count="exportConfig.defaultCount"
-    :description="exportConfig.description"
-    @update:show="showBatchExport = $event"
-    @confirm="handleBatchExport"
-    @cancel="showBatchExport = false"
-  />
-
-  <div style="position: fixed; left: -9999px; top: -9999px; width: 0; height: 0; overflow: hidden; visibility: hidden;">
-    <form-create
-      id="element-to-print"
-      v-model:api="fApi"
-      v-model="taskForm.value"
-      :option="taskForm.option"
-      :rule="taskForm.rule"
-    />
-  </div>
+
+
+
 
 </template>
 
@@ -158,19 +127,18 @@
 import AddForm from "./AddForm.vue";
 import ButtonAdd from "@/components/ButtonAdd/src/ButtonAdd.vue";
 import ButtonImport from "@/components/ButtonImport/src/ButtonImport.vue";
-import DetailForm from "@/views/preSalesManage/Appointment/DetailForm.vue";
 import { useUserStore } from '@/store/modules/user'
-import {getCurrentMonthRange} from "@/utils/dateUtil";
+import {getCurrentMonthRange_1} from "@/utils/dateUtil";
 import {
-  activityServiceRecordDelete,
-  activityServiceRecordGetPage,
-  adaptServiceRecordDelete,
-  adaptServiceRecordGetPage
+  floorActivityPhotoRecordDelete,
+  floorActivityPhotoRecordGetPage,
 } from "@/api/social-work";
 import {ref} from "vue";
 import type {ApiAttrs} from "@form-create/element-ui/types/config";
-import {batchGeneratePDFsAndZipWithProcess} from "@/utils/outBedCard";
-import {setConfAndFields2} from "@/utils/formCreate";
+import {getBuildList} from "@/api/system/badManage";
+import { getAccessToken } from '@/utils/auth'
+import { saveAs } from 'file-saver';
+import ExcelJS from 'exceljs';
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const userStore = useUserStore()
@@ -182,17 +150,28 @@ let queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   elderName: '',
-  recordTime: getCurrentMonthRange(),
+  buidId: '',
+  floorId: '',
+  recordMonthRange: getCurrentMonthRange_1(),
   tenantId: userStore.orgTenantId[0]
 })
 const queryFormRef = ref() // 搜索的表单
 
+
+
+const handleBuildChange = (buildId: any) => {
+  const selectedBuild = buildList.value.find((item: any) => item.id === buildId)
+  if (selectedBuild) {
+    floorList.value = selectedBuild.floorList || []
+  }
+}
+
 /** 查询列表 */
 const getList = async () => {
   loading.value = true
   try {
-    let queryP = {...queryParams,recordTime:queryParams.recordTime?[queryParams.recordTime[0]+" 00:00:00",queryParams.recordTime[1]+" 23:59:59"]:null}
-    const data = await activityServiceRecordGetPage(queryP)
+
+    const data = await floorActivityPhotoRecordGetPage(queryParams)
     list.value = data.list
     total.value = data.total
   } finally {
@@ -206,6 +185,8 @@ const handleQuery = async () => {
   const valid = await queryFormRef.value.validate()
   if (!valid) return
   queryParams.pageNo = 1
+  queryParams.buidId = ''
+  queryParams.floorId = ''
   await getList()
 }
 
@@ -213,7 +194,7 @@ const handleQuery = async () => {
 const resetQuery = () => {
   queryParams.elderName = ''
   queryParams.tenantId = userStore.orgTenantId[0]
-  queryParams.recordTime= getCurrentMonthRange()
+  queryParams.recordMonthRange= getCurrentMonthRange_1()
   queryFormRef.value.resetFields()
   handleQuery()
 }
@@ -235,8 +216,8 @@ const openFormEdit = (row: any = {}, id?: number) => {
 
 
 
-const openFormDetail = (id?: number) => {
-  formRef.value.open(undefined,id,true)
+const openFormDetail = (row: any = {},id?: number) => {
+  formRef.value.open(row.tenantId,id,true)
 }
 
 
@@ -249,7 +230,7 @@ const openClose = async (item) => {
     if (res == 'confirm') {
       // 发起
       try {
-        const res = await activityServiceRecordDelete(item.id)
+        const res = await floorActivityPhotoRecordDelete(item.id)
         if (res){
           message.success(t('common.updateSuccess'))
         }
@@ -260,13 +241,18 @@ const openClose = async (item) => {
   } catch {}
 }
 
-
+const buildList = ref([])
+const floorList = ref([])
 
 const route = useRoute()
 /** 初始化 **/
-onMounted(() => {
+onMounted(async () => {
+
+  await getList()
+  try {
+    buildList.value = await getBuildList({tenantIds: queryParams.tenantId})
+  }catch (e) {}
 
-  getList()
 
 })
 
@@ -292,68 +278,100 @@ const exportConfig = ref({
 const exportLoading = ref(false)
 
 // 打开导出弹窗
-const handleImportCard = () => {
-  exportConfig.value = {
-    title: "批量导出",
-    batchMin: 1,
-    batchMax: 999,
-    countMin: 1,
-    countMax: 500,
-    defaultBatch: 1,
-    defaultCount: 100,
-    description: [
-      '1. 请输入需要导出的数量',
-      '2. 一次导不完的话,输入批次 2 再次导出,以此类推。',
-      '3. 建议单次导出数量不超过100,以免影响系统性能',
-      '4. 导出需要较长时间,请耐心等待。'
-    ]
-  }
-  showBatchExport.value = true
-}
-
-// 处理批量导出
-const handleBatchExport = async (batch: number, count: number) => {
+const handleImportCard = async () => {
   exportLoading.value = true
-  console.log(batch,count)
-
   try {
-    let queryP = {pageNo:batch,pageSize:count,recordTime:null,elderName:'',tenantId: userStore.orgTenantId[0]}
-    const data = await activityServiceRecordGetPage(queryP)
-
-    await batchGeneratePDFsAndZipWithProcess({
-      dataList: data.list,
-      elementId: 'element-to-print',
-      processItem: async (item: any, index: number) => {
-        setConfAndFields2(taskForm, item.jsonOption, item.activityPlan, '2')
-      },
-      getFilename: (item: any, index: number) => {
-        return `${item.elderName || '记录表'}_${item.id}`
-      },
-      zipFileName: `楼层活动记录_${batch}.zip`,
-      pdfOptions: {
-        margin: [10, 10, 10, 10],
-        image: { type: 'jpeg', quality: 1 },
-        html2canvas: { scale: 1, useCORS: true },
-        jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
-      },
-      onProgress: (current: number, total: number) => {
-        console.log(`正在处理第 ${current}/${total} 个文件`)
+    // 获取所有数据(不分页)
+    const queryP = {
+      pageNo: 1,
+      pageSize: 9999,
+      elderName: queryParams.elderName,
+      buidId: queryParams.buidId,
+      floorId: queryParams.floorId,
+      recordMonthRange: queryParams.recordMonthRange,
+      tenantId: queryParams.tenantId
+    }
+    const data = await floorActivityPhotoRecordGetPage(queryP)
+    const exportData = data.list || []
+
+    if (exportData.length === 0) {
+      message.warning('暂无数据可导出')
+      return
+    }
+
+    // 获取楼栋列表用于匹配名称
+    let buildList: any[] = []
+    try {
+      buildList = await getBuildList({tenantIds: queryParams.tenantId})
+    } catch (e) {}
+
+    // 处理数据,解析 items 和 excuteRecord,并匹配楼栋楼层名称
+    const processedData = exportData.map((item: any) => {
+      // 根据 buidId 匹配楼栋名称
+      let buildName = item.buildName || ''
+      let floorName = item.floorName || ''
+      if (item.buildId && buildList.length > 0) {
+        const matchedBuild = buildList.find((build: any) => build.id === item.buildId)
+        if (matchedBuild) {
+          buildName = matchedBuild.buildName || ''
+          // 根据 floorId 匹配楼层名称
+          if (!floorName && item.floorId && matchedBuild.floorList) {
+            const matchedFloor = matchedBuild.floorList.find((floor: any) => floor.id === item.floorId)
+            if (matchedFloor) {
+              floorName = matchedFloor.floorName || ''
+            }
+          }
+        }
       }
+
+      const processedItem: any = {
+        id: item.id,
+        recordMonth: item.recordMonth,
+        week: item.week,
+        weekText: `第${item.week}周`,
+        buildName: buildName,
+        floorName: floorName,
+        elderName: item.elderName || '',
+        recordTime: item.recordTime,
+        belongFloor: item.belongFloor || '',
+        activityDate: item.activityDate || '',
+        activityContent: item.activityContent || '',
+        // 解析活动图片
+        photos: []
+      }
+
+      // 解析 activityImage JSON
+      if (item.activityImage) {
+        try {
+          const photos = JSON.parse(item.activityImage)
+          if (Array.isArray(photos)) {
+            processedItem.photos = photos
+          }
+        } catch (e) {
+          processedItem.photos = []
+        }
+      }
+
+      return processedItem
     })
 
-    message.success(`成功生成 ${data.list.length} 个 PDF 文件并打包下载`)
+    // 使用 exceljs 导出带图片的 Excel
+    await exportPhotoRecordsToExcel(processedData, buildList)
+    message.success('导出成功')
+
 
   } catch (error) {
-    console.error('批量导出失败:', error)
-    message.error('批量导出失败,请重试')
+    console.error('导出失败:', error)
+    message.error('导出失败,请重试')
   } finally {
     exportLoading.value = false
   }
-
 }
 
 
 
+
+
 // 表头格式
 const tableHeaderColor = ({ rowIndex }: any) => {
   if (rowIndex === 0) {
@@ -364,6 +382,155 @@ const tableHeaderColor = ({ rowIndex }: any) => {
     }
   }
 }
+
+/**
+ * 导出楼层活动照片记录到 Excel(带图片)
+ * @param data 处理后的数据
+ * @param buildList 楼栋列表
+ */
+const exportPhotoRecordsToExcel = async (data: any[], buildList: any[]) => {
+  const workbook = new ExcelJS.Workbook()
+  const worksheet = workbook.addWorksheet('楼层活动照片记录')
+
+  // 设置列宽
+  worksheet.columns = [
+    { header: '记录年月', key: 'recordMonth', width: 15 },
+    { header: '第几周', key: 'weekText', width: 12 },
+    { header: '楼栋', key: 'buildName', width: 15 },
+    { header: '楼层', key: 'floorName', width: 15 },
+    { header: '参与长者', key: 'elderName', width: 20 },
+    { header: '记录时间', key: 'recordTime', width: 20 },
+    { header: '所属楼层', key: 'belongFloor', width: 15 },
+    { header: '活动日期', key: 'activityDate', width: 20 },
+    { header: '活动内容', key: 'activityContent', width: 50 },
+    { header: '照片1', key: 'photo1', width: 30 },
+    { header: '照片1描述', key: 'photo1Desc', width: 40 },
+    { header: '照片2', key: 'photo2', width: 30 },
+    { header: '照片2描述', key: 'photo2Desc', width: 40 },
+    { header: '照片3', key: 'photo3', width: 30 },
+    { header: '照片3描述', key: 'photo3Desc', width: 40 },
+    { header: '照片4', key: 'photo4', width: 30 },
+    { header: '照片4描述', key: 'photo4Desc', width: 40 },
+    { header: '照片5', key: 'photo5', width: 30 },
+    { header: '照片5描述', key: 'photo5Desc', width: 40 }
+  ]
+
+  // 设置表头样式
+  const headerRow = worksheet.getRow(1)
+  headerRow.font = { bold: true, size: 12 }
+  headerRow.fill = {
+    type: 'pattern',
+    pattern: 'solid',
+    fgColor: { argb: 'FFE0E0E0' }
+  }
+  headerRow.alignment = { horizontal: 'center', vertical: 'middle' }
+
+  // 添加数据行
+  for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
+    const item = data[rowIndex]
+    const row = worksheet.addRow({
+      id: item.id,
+      recordMonth: item.recordMonth,
+      weekText: item.weekText,
+      buildName: item.buildName,
+      floorName: item.floorName,
+      elderName: item.elderName,
+      recordTime: item.recordTime,
+      belongFloor: item.belongFloor,
+      activityDate: item.activityDate,
+      activityContent: item.activityContent
+    })
+
+    // 设置行高(为图片预留空间)
+    row.height = 120
+
+    // 处理照片
+    if (item.photos && item.photos.length > 0) {
+      for (let photoIndex = 0; photoIndex < Math.min(item.photos.length, 5); photoIndex++) {
+        const photo = item.photos[photoIndex]
+        const photoUrl = photo.url || ''
+        const photoDesc = photo.description || ''
+
+        // 设置照片描述(从第12列开始:photo1Desc)
+        row.getCell(12 + photoIndex * 2).value = photoDesc
+
+        // 尝试加载并插入图片
+        if (photoUrl) {
+          try {
+            // 获取图片数据
+            const imageBuffer = await fetchImageAsBuffer(photoUrl)
+            if (imageBuffer) {
+              // 添加图片到 workbook
+              const imageId = workbook.addImage({
+                buffer: imageBuffer,
+                extension: getImageExtension(photoUrl)
+              })
+
+              // 在单元格中插入图片
+              await worksheet.addImage(imageId, {
+                tl: {col: 10 + photoIndex * 2, row: rowIndex + 1},
+                ext: {width: 100, height: 100}
+              })
+            }
+          } catch (e) {
+            console.error('加载图片失败:', photoUrl, e)
+            row.getCell(10 + photoIndex * 2).value = '图片加载失败'
+          }
+        }
+      }
+    }
+
+    // 设置单元格对齐方式
+    row.alignment = { vertical: 'top', wrapText: true }
+  }
+
+  // 生成并下载文件
+  const buffer = await workbook.xlsx.writeBuffer()
+  const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+  saveAs(blob, `楼层活动照片记录_${new Date().getTime()}.xlsx`)
+}
+
+/**
+ * 获取图片数据为 Buffer
+ * @param url 图片URL
+ * @returns ArrayBuffer | null
+ */
+const fetchImageAsBuffer = async (url: string): Promise<ArrayBuffer | null> => {
+  try {
+    // 处理相对路径,转换为完整URL
+    const fullUrl = url.startsWith('http') ? url : `${import.meta.env.VITE_BASE_URL}${url}`
+
+    const response = await fetch(fullUrl, {
+      headers: {
+        Authorization: 'Bearer ' + getAccessToken()
+      }
+    })
+
+    if (!response.ok) {
+      throw new Error(`HTTP error! status: ${response.status}`)
+    }
+
+    return await response.arrayBuffer()
+  } catch (e) {
+    console.error('获取图片失败:', url, e)
+    return null
+  }
+}
+
+/**
+ * 获取图片扩展名
+ * @param url 图片URL
+ * @returns 'png' | 'jpeg' | 'jpg'
+ */
+const getImageExtension = (url: string): 'png' | 'jpeg' | 'jpg' => {
+  const ext = url.split('.').pop()?.toLowerCase()
+  if (ext === 'png') return 'png'
+  if (ext === 'jpg' || ext === 'jpeg') return 'jpeg'
+  return 'jpeg' // 默认
+}
+
+
+
 </script>
 
 <style scoped lang="scss"></style>

+ 162 - 42
src/views/social-worker/floor-activity-record/text/AddForm.vue

@@ -13,7 +13,7 @@
       <div class="info-wrap">
         <el-row>
           <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
-            <el-form-item label="记录年月" prop="recordYear">
+            <el-form-item label="记录年月" prop="recordMonth">
               <el-date-picker
                 v-if="!isDetail"
                 v-model="dataForm.recordMonth"
@@ -21,7 +21,7 @@
                 placeholder="选择年月"
                 value-format="YYYY-MM"
                 style="width: 100%"
-                @change="handleYearChange"
+                @change="handleMonthChange"
               />
               <el-text v-else>{{dataForm.recordMonth}}</el-text>
             </el-form-item>
@@ -35,21 +35,19 @@
             </el-form-item>
           </el-col>
           <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
-            <el-form-item label="楼栋" prop="floorName">
-              <el-select  v-if="!isDetail" v-model="dataForm.buildName" placeholder="选择楼栋" style="width: 100%">
-                <el-option @click="handleBuildChange(item)" v-for="(item,index) in buildList" :key="index" :label="item.buildName" :value="item.id" />
+            <el-form-item label="楼栋" prop="buidId">
+              <el-select :disabled="isDetail" v-model="dataForm.buidId" placeholder="选择楼栋" style="width: 100%" @change="handleBuildChange">
+                <el-option  v-for="(item,index) in buildList" :key="index" :label="item.buildName" :value="item.id" />
               </el-select>
-              <el-text v-else>{{dataForm.buildName}}</el-text>
             </el-form-item>
           </el-col>
         </el-row>
         <el-row>
           <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
-            <el-form-item label="楼层" prop="weekNumber">
-              <el-select v-if="!isDetail" v-model="dataForm.floorName" placeholder="选择楼层" style="width: 100%">
+            <el-form-item label="楼层" prop="floorId">
+              <el-select :disabled="isDetail" v-model="dataForm.floorId" placeholder="选择楼层" style="width: 100%" @change="handleFloorChange">
                 <el-option v-for="(item,index) in floorList" :key="index" :label="item.floorName" :value="item.id" />
               </el-select>
-              <el-text v-else>{{dataForm.floorName}}</el-text>
             </el-form-item>
           </el-col>
           <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
@@ -68,10 +66,10 @@
         </el-row>
 
         <el-row>
-
           <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-            <el-form-item label="参与长者">
-              <el-input type="textarea" placeholder="输入参与长者,逗号分开" maxlength="500" show-word-limit :rows="4">{{ dateRangeText }}</el-input>
+            <el-form-item label="参与长者" prop="elderName">
+              <el-input v-if="!isDetail" v-model="dataForm.elderName" type="textarea" placeholder="输入参与长者,逗号分开" maxlength="500" show-word-limit :rows="4" />
+              <el-text v-else>{{ dataForm.elderName }}</el-text>
             </el-form-item>
           </el-col>
         </el-row>
@@ -186,14 +184,14 @@
 
                   <div class="other-remark">
                     <el-checkbox v-if="!isDetail" v-model="dataForm.executionRecord[index].hasOther">其他请注明</el-checkbox>
-                    <el-text v-else v-show="dataForm.executionRecord[index].hasOther">其他请注明</el-text>
+                    <el-text v-else v-show="dataForm.executionRecord[index].otherRemark">其他请注明</el-text>
                     <el-input
                       v-if="!isDetail"
                       v-model="dataForm.executionRecord[index].otherRemark"
                       placeholder="请输入"
                       style="width: 120px; margin-left: 8px;"
                     />
-                    <el-text v-else v-show="dataForm.executionRecord[index].hasOther" style="margin-left: 8px;">{{ dataForm.executionRecord[index].otherRemark }}</el-text>
+                    <el-text v-else v-show="dataForm.executionRecord[index].otherRemark" style="margin-left: 8px;">{{ dataForm.executionRecord[index].otherRemark }}</el-text>
                   </div>
                 </div>
               </td>
@@ -237,7 +235,16 @@ const formLoading = ref(false) // 表单的加载中
 const weekDayNames = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
 
 // 计算日期范围
-const weekDays = ref<{ name: string; date: string }[]>([])
+const weekDays = ref<{ name: string; date: string; fullDate: string }[]>([])
+
+// 执行记录选项配置
+const executionOptions = [
+  { key: 'suitablePhysiology', label: '适宜长者生理、心理特征' },
+  { key: 'improveSecurity', label: '有助于提高长者安全感、归属感' },
+  { key: 'maintainMemory', label: '有助于维系长者认知和记忆' },
+  { key: 'improveCoordination', label: '有助于提高长者手眼协调能力' },
+  { key: 'improveCommunication', label: '有助于提高长者沟通与表达能力' },
+]
 
 // 初始化执行记录
 const initExecutionRecord = () => {
@@ -252,6 +259,37 @@ const initExecutionRecord = () => {
   }
 }
 
+// 将执行记录对象转换为中文文本数组
+const convertExecutionRecordToText = (record: any): string[] => {
+  const result: string[] = []
+  if (record.suitablePhysiology) result.push('适宜长者生理、心理特征')
+  if (record.improveSecurity) result.push('有助于提高长者安全感、归属感')
+  if (record.maintainMemory) result.push('有助于维系长者认知和记忆')
+  if (record.improveCoordination) result.push('有助于提高长者手眼协调能力')
+  if (record.improveCommunication) result.push('有助于提高长者沟通与表达能力')
+  if (record.otherRemark) result.push(`其他:${record.otherRemark}`)
+  return result
+}
+
+// 将中文文本数组解析为执行记录对象
+const parseExecutionRecordFromText = (textArray: string[]): any => {
+  const record = initExecutionRecord()
+  if (!Array.isArray(textArray)) return record
+  
+  textArray.forEach((text: string) => {
+    if (text === '适宜长者生理、心理特征') record.suitablePhysiology = true
+    else if (text === '有助于提高长者安全感、归属感') record.improveSecurity = true
+    else if (text === '有助于维系长者认知和记忆') record.maintainMemory = true
+    else if (text === '有助于提高长者手眼协调能力') record.improveCoordination = true
+    else if (text === '有助于提高长者沟通与表达能力') record.improveCommunication = true
+    else if (text.startsWith('其他:')) {
+      record.hasOther = true
+      record.otherRemark = text.replace('其他:', '')
+    }
+  })
+  return record
+}
+
 // 表单数据
 let dataForm = ref({
   id: undefined,
@@ -275,11 +313,12 @@ let dataForm = ref({
 
 // 表单规则
 const dataRule = reactive<FormRules>({
-  recordYear: [{ required: true, message: '记录年份不能为空', trigger: 'change' }],
-  recordMonth: [{ required: true, message: '记录月份不能为空', trigger: 'change' }],
-  weekNumber: [{ required: true, message: '周次不能为空', trigger: 'change' }],
-  floorName: [{ required: true, message: '养护楼层不能为空', trigger: 'blur' }],
+  recordMonth: [{ required: true, message: '记录年月不能为空', trigger: 'change' }],
+  week: [{ required: true, message: '周次不能为空', trigger: 'change' }],
+  buidId: [{ required: true, message: '楼栋不能为空', trigger: 'change' }],
+  floorId: [{ required: true, message: '楼层不能为空', trigger: 'change' }],
   recordTime: [{ required: true, message: '记录时间不能为空', trigger: 'blur' }],
+  elderName: [{ required: true, message: '参与长者不能为空', trigger: 'blur' }],
 })
 
 // 计算日期范围文本
@@ -312,8 +351,8 @@ const getWeekDateRange = (year: number, month: number, weekNum: number) => {
 
 // 更新周日期显示
 const updateWeekDays = () => {
-  const yearMonth = dataForm.value.recordYear // 格式: YYYY-MM
-  const weekNum = dataForm.value.weekNumber
+  const yearMonth = dataForm.value.recordMonth // 格式: YYYY-MM
+  const weekNum = dataForm.value.week
   
   if (yearMonth && weekNum) {
     const [year, month] = yearMonth.split('-').map(Number)
@@ -323,7 +362,8 @@ const updateWeekDays = () => {
     
     weekDays.value = weekDayNames.map((name, index) => ({
       name,
-      date: range.dates[index]
+      date: range.dates[index],
+      fullDate: `${year}-${range.dates[index]}`
     }))
   }
 }
@@ -339,7 +379,7 @@ const handleMonthChange = () => {
 }
 
 // 监听周次变化
-watch(() => dataForm.value.weekNumber, () => {
+watch(() => dataForm.value.week, () => {
   updateWeekDays()
 })
 
@@ -356,8 +396,22 @@ const labelWidth = computed(() => {
 const buildList = ref([])
 const floorList = ref([])
 
-const handleBuildChange = (e) => {
-  floorList.value= e.floorList
+const handleBuildChange = (buildId: any) => {
+  const selectedBuild = buildList.value.find((item: any) => item.id === buildId)
+  if (selectedBuild) {
+    dataForm.value.buildName = selectedBuild.buildName
+    floorList.value = selectedBuild.floorList || []
+    // 清空楼层选择
+    dataForm.value.floorId = ''
+    dataForm.value.floorName = ''
+  }
+}
+
+const handleFloorChange = (floorId: any) => {
+  const selectedFloor = floorList.value.find((item: any) => item.id === floorId)
+  if (selectedFloor) {
+    dataForm.value.floorName = selectedFloor.floorName
+  }
 }
 
 
@@ -386,14 +440,59 @@ const open = async (tenantId, id?: any, detail: boolean = false) => {
     try {
       loading.value = true
       const res = await floorActivityRecordGetInfo(id)
-      // 解析后端返回的数据
+      // 解析后端返回的数据 - 将后端 items 数组转换为前端展示格式
+      const items = res.items || []
+      const morningExercise: string[] = ['', '', '', '', '', '', '']
+      const morningActivity: string[] = ['', '', '', '', '', '', '']
+      const afternoonActivity: string[] = ['', '', '', '', '', '', '']
+      const executorName: string[] = ['', '', '', '', '', '', '']
+      const executionRecord: any[] = Array(7).fill(null).map(() => initExecutionRecord())
+      
+      // 解析 items 数据
+      items.forEach((item: any, index: number) => {
+        if (index < 7) {
+          morningExercise[index] = item.morningOne || ''
+          morningActivity[index] = item.morningTwo || ''
+          afternoonActivity[index] = item.afternonn || ''
+          executorName[index] = item.excuteName || ''
+          // 解析执行情况记录 - 后端存储的是中文文本数组
+          if (item.excuteRecord) {
+            try {
+              const recordArray = JSON.parse(item.excuteRecord)
+              executionRecord[index] = parseExecutionRecordFromText(recordArray)
+            } catch (e) {
+              executionRecord[index] = initExecutionRecord()
+            }
+          }
+        }
+      })
+
+      try {
+        const selectedBuild = buildList.value.find((item: any) => item.id === res.buidId)
+        if (selectedBuild) {
+          dataForm.value.buildName = selectedBuild.buildName
+          floorList.value = selectedBuild.floorList || []
+
+        }
+      }catch (e) {}
+
+
       dataForm.value = {
-        ...res,
-        morningExercise: res.morningExercise ? JSON.parse(res.morningExercise) : ['', '', '', '', '', '', ''],
-        morningActivity: res.morningActivity ? JSON.parse(res.morningActivity) : ['', '', '', '', '', '', ''],
-        afternoonActivity: res.afternoonActivity ? JSON.parse(res.afternoonActivity) : ['', '', '', '', '', '', ''],
-        executorName: res.executorName ? JSON.parse(res.executorName) : ['', '', '', '', '', '', ''],
-        executionRecord: res.executionRecord ? JSON.parse(res.executionRecord) : Array(7).fill(null).map(() => initExecutionRecord()),
+        id: res.id,
+        recordMonth: res.recordMonth,
+        week: res.week,
+        buidId: res.buidId,
+        buildName: res.buildName || '',
+        floorId: res.floorId,
+        floorName: res.floorName || '',
+        elderName: res.elderName || '',
+        recordTime: res.recordTime,
+        tenantId: res.tenantId,
+        morningExercise,
+        morningActivity,
+        afternoonActivity,
+        executorName,
+        executionRecord,
       }
       updateWeekDays()
       loading.value = false
@@ -422,14 +521,31 @@ const submitForm = async () => {
     const valid = await formRef.value.validate()
     if (!valid) return
 
-    // 构建提交参数
+    // 构建提交参数 - 将前端数组转换为后端需要的 items 格式
+    const items: any[] = []
+    for (let i = 0; i < 7; i++) {
+      items.push({
+        activityDate: weekDays.value[i]?.fullDate, // 日期格式:YYYY-MM-DD
+        morningOne: dataForm.value.morningExercise[i] || undefined,
+        morningTwo: dataForm.value.morningActivity[i] || undefined,
+        lunch: undefined, // 午餐字段,如有需要可添加
+        afternonn: dataForm.value.afternoonActivity[i] || undefined,
+        excuteName: dataForm.value.executorName[i] || undefined,
+        excuteRecord: JSON.stringify(convertExecutionRecordToText(dataForm.value.executionRecord[i])),
+        tenantId: dataForm.value.tenantId
+      })
+    }
+    
     const tempParams = {
-      ...dataForm.value,
-      morningExercise: JSON.stringify(dataForm.value.morningExercise),
-      morningActivity: JSON.stringify(dataForm.value.morningActivity),
-      afternoonActivity: JSON.stringify(dataForm.value.afternoonActivity),
-      executorName: JSON.stringify(dataForm.value.executorName),
-      executionRecord: JSON.stringify(dataForm.value.executionRecord),
+      id: dataForm.value.id,
+      buidId: dataForm.value.buidId,
+      floorId: dataForm.value.floorId,
+      elderName: dataForm.value.elderName,
+      recordMonth: dataForm.value.recordMonth,
+      recordTime: dataForm.value.recordTime,
+      tenantId: dataForm.value.tenantId,
+      week: dataForm.value.week,
+      items: items
     }
 
     if (dataForm.value.id) {
@@ -460,10 +576,13 @@ const submitForm = async () => {
 const resetForm = () => {
   dataForm.value = {
     id: undefined,
-    recordYear: dayjs().format('YYYY-MM'),
-    recordMonth: dayjs().month() + 1,
-    weekNumber: 1,
+    recordMonth: dayjs().format('YYYY-MM'),
+    week: 1,
+    buidId: '',
+    buildName: '',
+    floorId: '',
     floorName: '',
+    elderName: '',
     recordTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
     startDate: '',
     endDate: '',
@@ -474,6 +593,7 @@ const resetForm = () => {
     executionRecord: Array(7).fill(null).map(() => initExecutionRecord()),
     tenantId: undefined
   }
+  floorList.value = []
   formRef.value?.resetFields()
   weekDays.value = []
 }

+ 150 - 82
src/views/social-worker/floor-activity-record/text/index.vue

@@ -18,21 +18,15 @@
       </el-form-item>
 
       <el-form-item label="楼栋">
-        <el-input
-          v-model="queryParams.elderName"
-          placeholder="输入楼栋"
-          class="!w-200px"
-          clearable
-        />
+        <el-select v-model="queryParams.buidId" placeholder="选择楼栋" style="width: 180px" @change="handleBuildChange">
+          <el-option v-for="(item,index) in buildList" :key="index" :label="item.buildName" :value="item.id" />
+        </el-select>
       </el-form-item>
 
       <el-form-item label="楼层">
-        <el-input
-          v-model="queryParams.elderName"
-          placeholder="输入楼层"
-          class="!w-200px"
-          clearable
-        />
+        <el-select  v-model="queryParams.floorId" placeholder="选择楼层" style="width: 180px" >
+          <el-option v-for="(item,index) in floorList" :key="index" :label="item.floorName" :value="item.id" />
+        </el-select>
       </el-form-item>
 
       <el-form-item label="记录年月">
@@ -40,7 +34,7 @@
           size="default"
           ref="selectRef"
           class="!w-240px"
-          v-model="queryParams.recordTime"
+          v-model="queryParams.recordMonthRange"
           type="monthrange"
           :clearable="true"
           :editable="false"
@@ -75,15 +69,14 @@
         </template>
       </el-table-column>
 
-<!--      <el-table-column prop="elderName" header-align="center" align="center" label="长者姓名" min-width="150" show-overflow-tooltip/>-->
-      <!--      <el-table-column prop="bedInfo" header-align="center" align="center" label="床位号" min-width="200" show-overflow-tooltip/>-->
+      <el-table-column prop="elderName" header-align="center" align="center" label="长者姓名" min-width="220" show-overflow-tooltip/>
+      <el-table-column prop="recordMonth" header-align="center" align="center" label="活动年月" min-width="220" show-overflow-tooltip/>
       <el-table-column prop="recordTime" header-align="center" align="center" label="记录时间" min-width="150" show-overflow-tooltip>
         <template #default="scope">
           {{(scope.row.recordTime)}}
         </template>
       </el-table-column>
-      <el-table-column prop="templateName" header-align="center" align="center" label="表名" min-width="220" show-overflow-tooltip/>
-      <el-table-column prop="creator" header-align="center" align="center" label="操作人" min-width="150" show-overflow-tooltip/>
+
 
       <el-table-column label="操作" align="center" min-width="200" >
         <template #default="scope">
@@ -98,7 +91,7 @@
           <el-button
             link
             type="warning"
-            @click="openFormDetail(scope.row.id)"
+            @click="openFormDetail(scope.row,scope.row.id)"
           >
             详情
           </el-button>
@@ -133,17 +126,15 @@
 import AddForm from "./AddForm.vue";
 import ButtonAdd from "@/components/ButtonAdd/src/ButtonAdd.vue";
 import ButtonImport from "@/components/ButtonImport/src/ButtonImport.vue";
-import DetailForm from "@/views/preSalesManage/Appointment/DetailForm.vue";
 import { useUserStore } from '@/store/modules/user'
-import {getCurrentMonthRange} from "@/utils/dateUtil";
+import { getCurrentMonthRange_1} from "@/utils/dateUtil";
 import {
-  activityServiceRecordDelete,
-  activityServiceRecordGetPage,
+   floorActivityRecordDelete, floorActivityRecordGetPage,
 } from "@/api/social-work";
 import {ref} from "vue";
 import type {ApiAttrs} from "@form-create/element-ui/types/config";
-import {batchGeneratePDFsAndZipWithProcess} from "@/utils/outBedCard";
-import {setConfAndFields2} from "@/utils/formCreate";
+import {getBuildList} from "@/api/system/badManage";
+import { exportWithExpandedObjectArrays } from "@/utils/excel-export";
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const userStore = useUserStore()
@@ -155,17 +146,28 @@ let queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   elderName: '',
-  recordTime: getCurrentMonthRange(),
+  buidId: '',
+  floorId: '',
+  recordMonthRange: getCurrentMonthRange_1(),
   tenantId: userStore.orgTenantId[0]
 })
 const queryFormRef = ref() // 搜索的表单
 
+
+
+const handleBuildChange = (buildId: any) => {
+  const selectedBuild = buildList.value.find((item: any) => item.id === buildId)
+  if (selectedBuild) {
+    floorList.value = selectedBuild.floorList || []
+  }
+}
+
 /** 查询列表 */
 const getList = async () => {
   loading.value = true
   try {
-    let queryP = {...queryParams,recordTime:queryParams.recordTime?[queryParams.recordTime[0]+" 00:00:00",queryParams.recordTime[1]+" 23:59:59"]:null}
-    const data = await activityServiceRecordGetPage(queryP)
+
+    const data = await floorActivityRecordGetPage(queryParams)
     list.value = data.list
     total.value = data.total
   } finally {
@@ -179,6 +181,8 @@ const handleQuery = async () => {
   const valid = await queryFormRef.value.validate()
   if (!valid) return
   queryParams.pageNo = 1
+  queryParams.floorId = ''
+  queryParams.buidId = ''
   await getList()
 }
 
@@ -186,7 +190,7 @@ const handleQuery = async () => {
 const resetQuery = () => {
   queryParams.elderName = ''
   queryParams.tenantId = userStore.orgTenantId[0]
-  queryParams.recordTime= getCurrentMonthRange()
+  queryParams.recordMonthRange= getCurrentMonthRange_1()
   queryFormRef.value.resetFields()
   handleQuery()
 }
@@ -208,8 +212,8 @@ const openFormEdit = (row: any = {}, id?: number) => {
 
 
 
-const openFormDetail = (id?: number) => {
-  formRef.value.open(undefined,id,true)
+const openFormDetail = (row: any = {},id?: number) => {
+  formRef.value.open(row.tenantId,id,true)
 }
 
 
@@ -222,7 +226,7 @@ const openClose = async (item) => {
     if (res == 'confirm') {
       // 发起
       try {
-        const res = await activityServiceRecordDelete(item.id)
+        const res = await floorActivityRecordDelete(item.id)
         if (res){
           message.success(t('common.updateSuccess'))
         }
@@ -233,13 +237,18 @@ const openClose = async (item) => {
   } catch {}
 }
 
-
+const buildList = ref([])
+const floorList = ref([])
 
 const route = useRoute()
 /** 初始化 **/
-onMounted(() => {
+onMounted(async () => {
+
+  await getList()
+  try {
+    buildList.value = await getBuildList({tenantIds: queryParams.tenantId})
+  }catch (e) {}
 
-  getList()
 
 })
 
@@ -265,68 +274,127 @@ const exportConfig = ref({
 const exportLoading = ref(false)
 
 // 打开导出弹窗
-const handleImportCard = () => {
-  exportConfig.value = {
-    title: "批量导出",
-    batchMin: 1,
-    batchMax: 999,
-    countMin: 1,
-    countMax: 500,
-    defaultBatch: 1,
-    defaultCount: 100,
-    description: [
-      '1. 请输入需要导出的数量',
-      '2. 一次导不完的话,输入批次 2 再次导出,以此类推。',
-      '3. 建议单次导出数量不超过100,以免影响系统性能',
-      '4. 导出需要较长时间,请耐心等待。'
-    ]
-  }
-  showBatchExport.value = true
-}
-
-// 处理批量导出
-const handleBatchExport = async (batch: number, count: number) => {
+const handleImportCard = async () => {
   exportLoading.value = true
-  console.log(batch,count)
-
   try {
-    let queryP = {pageNo:batch,pageSize:count,recordTime:null,elderName:'',tenantId: userStore.orgTenantId[0]}
-    const data = await activityServiceRecordGetPage(queryP)
-
-    await batchGeneratePDFsAndZipWithProcess({
-      dataList: data.list,
-      elementId: 'element-to-print',
-      processItem: async (item: any, index: number) => {
-        setConfAndFields2(taskForm, item.jsonOption, item.activityPlan, '2')
-      },
-      getFilename: (item: any, index: number) => {
-        return `${item.elderName || '记录表'}_${item.id}`
-      },
-      zipFileName: `楼层活动记录_${batch}.zip`,
-      pdfOptions: {
-        margin: [10, 10, 10, 10],
-        image: { type: 'jpeg', quality: 1 },
-        html2canvas: { scale: 1, useCORS: true },
-        jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
-      },
-      onProgress: (current: number, total: number) => {
-        console.log(`正在处理第 ${current}/${total} 个文件`)
+    // 获取所有数据(不分页)
+    const queryP = {
+      pageNo: 1,
+      pageSize: 9999,
+      elderName: queryParams.elderName,
+      buidId: queryParams.buidId,
+      floorId: queryParams.floorId,
+      recordMonthRange: queryParams.recordMonthRange,
+      tenantId: queryParams.tenantId
+    }
+    const data = await floorActivityRecordGetPage(queryP)
+    const exportData = data.list || []
+
+    if (exportData.length === 0) {
+      message.warning('暂无数据可导出')
+      return
+    }
+
+    // 获取楼栋列表用于匹配名称
+    let buildList: any[] = []
+    try {
+      buildList = await getBuildList({tenantIds: queryParams.tenantId})
+    } catch (e) {}
+
+    // 处理数据,解析 items 和 excuteRecord,并匹配楼栋楼层名称
+    const processedData = exportData.map((item: any) => {
+      // 根据 buidId 匹配楼栋名称
+      let buildName = item.buildName || ''
+      let floorName = item.floorName || ''
+      
+      if (!buildName && item.buidId && buildList.length > 0) {
+        const matchedBuild = buildList.find((build: any) => build.id === item.buidId)
+        if (matchedBuild) {
+          buildName = matchedBuild.buildName || ''
+          // 根据 floorId 匹配楼层名称
+          if (!floorName && item.floorId && matchedBuild.floorList) {
+            const matchedFloor = matchedBuild.floorList.find((floor: any) => floor.id === item.floorId)
+            if (matchedFloor) {
+              floorName = matchedFloor.floorName || ''
+            }
+          }
+        }
       }
+
+      const processedItem: any = {
+        id: item.id,
+        recordMonth: item.recordMonth,
+        week: item.week,
+        buildName: buildName,
+        floorName: floorName,
+        elderName: item.elderName || '',
+        recordTime: item.recordTime
+      }
+
+      // 处理 items 数组(7天的记录)
+      const items = item.items || []
+      items.forEach((dayItem: any, index: number) => {
+        const dayNum = index + 1
+        processedItem[`day${dayNum}_date`] = dayItem.activityDate || ''
+        processedItem[`day${dayNum}_morningOne`] = dayItem.morningOne || ''
+        processedItem[`day${dayNum}_morningTwo`] = dayItem.morningTwo || ''
+        processedItem[`day${dayNum}_afternoon`] = dayItem.afternonn || ''
+        processedItem[`day${dayNum}_executor`] = dayItem.excuteName || ''
+        
+        // 解析执行情况记录 JSON
+        let executionRecordText = ''
+        if (dayItem.excuteRecord) {
+          try {
+            const recordArray = JSON.parse(dayItem.excuteRecord)
+            if (Array.isArray(recordArray)) {
+              executionRecordText = recordArray.join(';')
+            }
+          } catch (e) {
+            executionRecordText = dayItem.excuteRecord
+          }
+        }
+        processedItem[`day${dayNum}_executionRecord`] = executionRecordText
+      })
+
+      return processedItem
     })
 
-    message.success(`成功生成 ${data.list.length} 个 PDF 文件并打包下载`)
+    // 构建表头配置
+    const headers: any[] = [
+      { key: 'recordMonth', title: '记录年月' },
+      { key: 'week', title: '第几周' },
+      { key: 'buildName', title: '楼栋' },
+      { key: 'floorName', title: '楼层' },
+      { key: 'elderName', title: '参与长者' },
+      { key: 'recordTime', title: '记录时间' }
+    ]
+
+    // 添加7天的表头
+    const weekDayNames = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
+    for (let i = 1; i <= 7; i++) {
+      headers.push({ key: `day${i}_date`, title: `${weekDayNames[i - 1]}日期` })
+      headers.push({ key: `day${i}_morningOne`, title: `${weekDayNames[i - 1]}早报/早操(8:00-9:00)` })
+      headers.push({ key: `day${i}_morningTwo`, title: `${weekDayNames[i - 1]}上午活动(9:00-10:30)` })
+      headers.push({ key: `day${i}_afternoon`, title: `${weekDayNames[i - 1]}下午活动(15:00-16:30)` })
+      headers.push({ key: `day${i}_executor`, title: `${weekDayNames[i - 1]}执行人` })
+      headers.push({ key: `day${i}_executionRecord`, title: `${weekDayNames[i - 1]}执行情况记录` })
+    }
 
+    // 导出Excel
+    exportWithExpandedObjectArrays(processedData, headers, `楼层活动文字记录_${new Date().getTime()}.xlsx`, '楼层活动记录')
+    message.success('导出成功')
   } catch (error) {
-    console.error('批量导出失败:', error)
-    message.error('批量导出失败,请重试')
+    console.error('导出失败:', error)
+    message.error('导出失败,请重试')
   } finally {
     exportLoading.value = false
   }
-
 }
 
 
 
+
+
 // 表头格式
 const tableHeaderColor = ({ rowIndex }: any) => {
   if (rowIndex === 0) {