From 1540bd1a1bfb34d02a3ab186692c74760cd889ba Mon Sep 17 00:00:00 2001
From: zlwu <704541015@qq.com>
Date: Thu, 10 Jul 2025 13:59:41 +0800
Subject: [PATCH 1/5] =?UTF-8?q?fix(frontend):=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E9=99=84=E4=BB=B6=E7=BC=A9=E7=95=A5=E5=9B=BE=E8=AF=86=E5=88=AB?=
=?UTF-8?q?=20&=20=E5=BC=B9=E7=AA=97=E6=8D=A2=E8=A1=8C=E6=98=BE=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 isImageFile 通过文件后缀判断图片,解决编辑模式下缩略图不显示
- modal alert 系列支持 dangerouslyUseHTMLString,
换行生效
- 更新 showAttachmentDetail 换行
- 新增变更记录 docs/变更管理/code-adjust-record-2025-07-10-wuzilong.md
---
.../code-adjust-record-2025-07-10-wuzilong.md | 24 ++
web/src/plugins/modal.ts | 13 +-
web/src/views/hq/pm/repairOrder/index.vue | 249 ++++++++++++++++--
3 files changed, 254 insertions(+), 32 deletions(-)
create mode 100644 docs/变更管理/code-adjust-record-2025-07-10-wuzilong.md
diff --git a/docs/变更管理/code-adjust-record-2025-07-10-wuzilong.md b/docs/变更管理/code-adjust-record-2025-07-10-wuzilong.md
new file mode 100644
index 00000000..078f4a14
--- /dev/null
+++ b/docs/变更管理/code-adjust-record-2025-07-10-wuzilong.md
@@ -0,0 +1,24 @@
+# 2025-07-10 变更记录 (wuzilong)
+
+> 更新时间:2025-07-10 10:30:00
+
+## 修复内容
+
+1. **web/src/views/hq/pm/repairOrder/index.vue**
+ - 优化 `isImageFile` 方法:在缺少 MIME Type 时通过文件名/URL 后缀判定图片类型,解决编辑模式下附件缩略图不显示的问题。
+ - 调整 `showAttachmentDetail` 详情字符串,使用标准 `
` 换行符。
+2. **web/src/plugins/modal.ts**
+ - 在 `alert/alertError/alertSuccess/alertWarning` 方法中添加 `dangerouslyUseHTMLString: true`,支持 HTML 换行展示。
+
+## 影响范围
+
+- 物业报修工单编辑/详情弹窗显示。
+- 所有使用 `$modal.alert` 的提示框内容可正确解析换行。
+
+## 测试情况
+
+- [x] 编辑工单时,图片附件正常显示缩略图。
+- [x] 点击附件详情,弹窗内容按行分隔显示。
+- [x] 其它模块使用 `$modal.alert` 的弹窗显示正常。
+
+---
\ No newline at end of file
diff --git a/web/src/plugins/modal.ts b/web/src/plugins/modal.ts
index 83388ac6..b3504018 100644
--- a/web/src/plugins/modal.ts
+++ b/web/src/plugins/modal.ts
@@ -21,28 +21,33 @@ export default {
// 弹出提示
alert(content: any) {
ElMessageBox.alert(content, '系统提示', {
- confirmButtonClass: 'el-button--primary'
+ confirmButtonClass: 'el-button--primary',
+ // 允许在内容中使用
等 HTML
+ dangerouslyUseHTMLString: true
});
},
// 错误提示
alertError(content: any) {
ElMessageBox.alert(content, '系统提示', {
type: 'error',
- confirmButtonClass: 'el-button--primary'
+ confirmButtonClass: 'el-button--primary',
+ dangerouslyUseHTMLString: true
});
},
// 成功提示
alertSuccess(content: any) {
ElMessageBox.alert(content, '系统提示', {
type: 'success',
- confirmButtonClass: 'el-button--primary'
+ confirmButtonClass: 'el-button--primary',
+ dangerouslyUseHTMLString: true
});
},
// 警告提示
alertWarning(content: any) {
ElMessageBox.alert(content, '系统提示', {
type: 'warning',
- confirmButtonClass: 'el-button--primary'
+ confirmButtonClass: 'el-button--primary',
+ dangerouslyUseHTMLString: true
});
},
// 通知提示
diff --git a/web/src/views/hq/pm/repairOrder/index.vue b/web/src/views/hq/pm/repairOrder/index.vue
index ee66e863..e3f99ea8 100644
--- a/web/src/views/hq/pm/repairOrder/index.vue
+++ b/web/src/views/hq/pm/repairOrder/index.vue
@@ -24,9 +24,18 @@
-
-
-
+
@@ -83,7 +92,7 @@
-
+
@@ -200,15 +209,13 @@
新增报修
- 修改
+ 修改
- 删除
+ 删除
- 导出
+ 导出
@@ -268,7 +275,7 @@
-
-
+
@@ -361,7 +368,7 @@
@@ -517,6 +524,8 @@
v-model:file-list="attachmentList"
class="upload-demo"
drag
+ list-type="picture-card"
+ :on-preview="handleFilePreview"
:action="uploadAction"
:headers="uploadHeaders"
:data="uploadData"
@@ -527,15 +536,43 @@
:on-remove="handleRemoveAttachment"
:on-change="handleFileChange"
multiple
- :limit="5"
+ :limit="15"
:show-file-list="true"
accept="image/*,video/*,.pdf,.doc,.docx,.txt"
>
-
- 将文件拖到此处,或点击上传
+
- 支持图片、视频、PDF、Word文档等格式,单个文件大小不超过10MB,最多上传5个文件
- 新增时文件将在保存工单后上传
+
+ 支持图片、视频、PDF、Word文档等格式,单个文件大小不超过10MB,最多上传15个文件。
+ (新增时文件将在保存工单后上传)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ file.name }}
+
+
+
@@ -1187,6 +1224,7 @@ import {
import { useUserStore } from '@/store/modules/user';
import { getInfo } from '@/api/login'; // 添加获取用户详细信息的API
import { getToken } from '@/utils/auth'; // 修复:添加getToken导入
+import { vLoading } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -1253,7 +1291,7 @@ const pm_order_status = ref([
{ value: '3', label: '进行中' },
{ value: '4', label: '已完成' },
{ value: '5', label: '已评价' },
- { value: '6', label: '已归档' },
+ // { value: '6', label: '已归档' },
{ value: '9', label: '已取消' }
]);
@@ -1448,6 +1486,21 @@ const satisfactionLevelOptions = ref([
const getList = async () => {
loading.value = true;
queryParams.value.createTimeRange = daterangeCreateTime.value;
+
+ // 设置创建时间范围
+ queryParams.value.createTimeRange = daterangeCreateTime.value;
+
+ // 将创建时间范围写入 params,供后台 between 过滤使用
+ if (daterangeCreateTime.value && daterangeCreateTime.value.length === 2 && daterangeCreateTime.value[0] && daterangeCreateTime.value[1]) {
+ queryParams.value.params = {
+ ...queryParams.value.params,
+ beginCreateTime: daterangeCreateTime.value[0],
+ endCreateTime: daterangeCreateTime.value[1]
+ };
+ } else if (queryParams.value.params) {
+ delete queryParams.value.params.beginCreateTime;
+ delete queryParams.value.params.endCreateTime;
+ }
const res = await listRepairOrder(queryParams.value);
// 适配后端返回格式:支持新格式{total, rows, code, msg}和旧格式{code, msg, data: {total, rows}}
if ((res as any).rows && (res as any).total !== undefined) {
@@ -1992,13 +2045,13 @@ const canPreview = (attachment: any) => {
/** 显示附件详情 */
const showAttachmentDetail = (attachment: any) => {
- const details = `文件名称:${attachment.fileName}
-文件大小:${formatFileSize(attachment.fileSize)}
-文件类型:${getFileTypeLabel(attachment.fileType)}
-上传者:${attachment.uploaderName || '未知'}
-上传时间:${attachment.createTime}
-上传阶段:${getUploadStageText(attachment.uploadStage)}
-存储路径:${attachment.filePath}
+ const details = `文件名称:${attachment.fileName}
+文件大小:${formatFileSize(attachment.fileSize)}
+文件类型:${getFileTypeLabel(attachment.fileType)}
+上传者:${attachment.uploaderName || '未知'}
+上传时间:${attachment.createTime}
+上传阶段:${getUploadStageText(attachment.uploadStage)}
+存储路径:${attachment.filePath}
文件描述:${attachment.description || '无'}`;
proxy?.$modal.alert(details);
@@ -2600,6 +2653,56 @@ onMounted(() => {
getWorkerOptions();
getCurrentUserInfo();
});
+
+/** 卡片预览 */
+const handleFilePreview = (file: any) => {
+ // 如果文件已经上传成功,优先使用服务器返回的url
+ let url = file.url || (file.response && file.response.data && file.response.data.filePath);
+
+ // 若还未上传,则尝试使用本地预览地址
+ if (!url && file.raw) {
+ url = URL.createObjectURL(file.raw);
+ }
+
+ if (!url) return;
+
+ // 对图片使用图片预览,其余直接新窗口打开
+ const isImage = file.type ? file.type.startsWith('image') : file.raw && file.raw.type.startsWith('image');
+ if (isImage) {
+ // 利用 Element Plus ImageViewer 进行预览
+ const viewer = (proxy as any)?.$imageViewer;
+ viewer && viewer([url]);
+ } else {
+ window.open(url, '_blank');
+ }
+};
+
+/** 判断是否为图片文件 */
+const isImageFile = (file: any): boolean => {
+ // 优先使用文件 MIME type 判断
+ const type = file.type || (file.raw && file.raw.type) || '';
+ if (type && type.startsWith('image')) {
+ return true;
+ }
+
+ // 编辑场景下从数据库加载的附件通常缺少 MIME type
+ // 通过文件名或 URL 后缀进行判断
+ const name: string = file.name || file.response?.data?.fileName || '';
+ const url: string = file.url || file.response?.data?.filePath || '';
+ const source = (name || url).toLowerCase();
+
+ // 常见图片扩展名
+ const IMAGE_EXTS = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.tiff', '.ico'];
+
+ return IMAGE_EXTS.some((ext) => source.endsWith(ext));
+};
+
+/** 获取文件扩展名小写 */
+const getFileExt = (fileName: string): string => {
+ if (!fileName) return '';
+ const idx = fileName.lastIndexOf('.');
+ return idx !== -1 ? fileName.substring(idx + 1).toLowerCase() : '';
+};
From 4ef2208a0c49a2502e0a2ae30a1cd128ddd97daf Mon Sep 17 00:00:00 2001
From: zlwu <704541015@qq.com>
Date: Fri, 11 Jul 2025 06:36:45 +0800
Subject: [PATCH 2/5] feat(frontend): unify attachment management spec and
implement fetch+blob download\n\n- Implement forced download via fetch blob,
delayed delete etc in repairOrder module\n- Add Attachment Management Unified
Standard doctrine to cursorrules-frontend.md so future pages keep
consistency\n- Update change log for download preview fix
---
.cursor/cursorrules-frontend.md | 100 ++++
backend/hot-start.sh | 80 +--
.../HqPmRepairAttachmentController.java | 54 +-
.../service/IHqPmRepairAttachmentService.java | 10 +
.../impl/HqPmRepairAttachmentServiceImpl.java | 93 ++++
.../impl/HqPmRepairOrderServiceImpl.java | 10 +-
backend/start-all.sh | 92 ++++
...cord-2025-01-27-attachment-upload-logic.md | 513 ++++++++++++++++++
.../07-property-maintenance-module.sql | 2 +-
web/src/api/hq/pm/repairOrder.ts | 38 +-
web/src/views/hq/pm/repairOrder/index.vue | 434 ++++++++++++---
11 files changed, 1318 insertions(+), 108 deletions(-)
create mode 100644 backend/start-all.sh
create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-attachment-upload-logic.md
diff --git a/.cursor/cursorrules-frontend.md b/.cursor/cursorrules-frontend.md
index 16f524be..ac63da55 100644
--- a/.cursor/cursorrules-frontend.md
+++ b/.cursor/cursorrules-frontend.md
@@ -809,3 +809,103 @@ export default defineConfig({
1. **按钮规范**: 工具栏第一个新增按钮必须使用 `type="primary" plain`,其他按钮使用默认样式
2. **颜色系统**: 使用最新的 Art Design Pro 颜色变量
3. **文档同步**: 代码变更后必须同步更新相关文档
+
+## 4. 附件管理统一规范(强制性)
+
+> 适用范围:所有使用 **Element Plus** + **Vue 3 + TS** 的前端页面(弹窗、新建、编辑、详情、预览等)涉及“附件/文件”功能的模块,必须遵循本规范,以保持 UI 及交互的一致性。
+
+### 4.1 组件结构
+
+| 组件 | 说明 |
+|------|------|
+| `` | 核心上传组件,拖拽 + 点击上传,**单文件最大 10 MB**,**最多 15 个文件** |
+| 自定义 `#file` 模板 | 自绘「卡片样式」:缩略图 + 文件名 + 操作按钮组 |
+| `` | 卡片底部操作按钮:下载(success)、删除(danger) |
+| 自定义 CSS 类 | `.upload-file-card / thumb / name / actions` 等,详见样式规范 |
+
+### 4.2 功能实现
+
+1. **上传 Upload**
+ - `auto-upload="true"` + 立即上传
+ - `accept="image/*,video/*,.pdf,.doc,.docx,.txt"`
+ - 上传前 (`before-upload`) 校验大小 <10 MB
+ - 上传成功:
+ - 新建模式:将返回的 `id` 收集到 `tempAttachmentIds`
+ - 编辑模式:文件已自动关联到工单
+ - 上传失败:Element Plus message 提示
+
+2. **下载 Download (强制使用 fetch + blob)**
+ - 统一函数 `handleDownloadAttachment(file)` / `downloadAttachment(attachment)`
+ - 流程:`fetch(filePath) → blob → URL.createObjectURL → `
+ - 使用 `ElLoading.service()` 显示 *正在下载文件…*
+ - 下载完成后 `revokeObjectURL` 释放内存
+ - 解决浏览器对图片/PDF 默认预览导致无法下载的问题
+
+3. **删除 Delete(延迟删除机制)**
+ - 点击删除 → `proxy.$modal.confirm()` 二次确认
+ - 已上传文件:只把 `attachment.id` 放入 `pendingDeleteAttachmentIds`,**不立即调用后端**
+ - 表单保存时统一调用 `deleteAttachment` 批量删除
+ - 新增未上传文件:直接从 `tempFileList` / `attachmentList` 移除
+
+4. **预览 Preview**
+ - 判断 `fileType === 'image'` → 使用 `Element Plus ImageViewer` 预览
+ - 其他类型文件 `window.open(filePath,'_blank')`
+ - 详情弹窗附件列表同样遵循该预览逻辑
+
+### 4.3 样式规范(SCSS)
+
+```scss
+/* 让附件卡片一行展示 4 个 */
+:deep(.el-upload-list--picture-card) {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 9px;
+ margin-top: 12px;
+ .el-upload-list__item {
+ width: 166px !important;
+ min-height: 180px !important;
+ border-radius: 6px;
+ overflow: hidden;
+ }
+}
+
+/* 卡片整体布局 */
+.upload-file-card {
+ display: flex;
+ flex-direction: column;
+ min-height: 180px;
+}
+.upload-file-thumb { height: 120px; overflow: hidden; }
+.upload-file-name { font-size: 12px; padding: 8px 4px 4px; text-align: center; overflow: hidden; }
+.upload-file-card-actions {
+ padding: 6px 8px;
+ border-top: 1px solid #f0f0f0;
+ background: #fafafa;
+ display: flex; justify-content: center;
+}
+```
+
+### 4.4 统一变量 & Hook
+
+```ts
+const attachmentList = ref([]); // el-upload file-list
+const tempFileList = ref([]); // 新建时未上传的本地文件
+const tempAttachmentIds = ref([]); // 新建时已上传待关联附件 id
+const pendingDeleteAttachmentIds = ref([]); // 编辑时标记待删除附件 id
+```
+
+### 4.5 集成步骤(页面级)
+
+1. 在 `