feat(APP): 记录页实现编辑记录弹窗,替代占位的「返回详情页」提示
- 点击✏️直接弹出编辑弹窗:修改备注文本 + 图片预览/删除/新增上传
- 保存走 PUT /records/{id} 更新,本地列表同步刷新
- 删除图片同样走5秒倒计时双重确认
This commit is contained in:
@ -61,6 +61,26 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- ✏️ 编辑记录弹窗 -->
|
||||||
|
<view v-if="editVisible" class="edit-overlay" @tap="closeEditPopup">
|
||||||
|
<view class="edit-popup" @tap.stop>
|
||||||
|
<text class="edit-title">✏️ 编辑记录</text>
|
||||||
|
<textarea v-model="editForm.remark" class="edit-textarea" placeholder="填写备注说明" :maxlength="2000" />
|
||||||
|
<view class="edit-imgs">
|
||||||
|
<view v-for="(img, i) in editForm.images" :key="i" class="edit-img-cell">
|
||||||
|
<image :src="imageUrl(img)" mode="aspectFill" class="edit-img" @tap="previewEditImage(i)" />
|
||||||
|
<text class="edit-img-del" @tap.stop="removeEditImage(i)">✕</text>
|
||||||
|
</view>
|
||||||
|
<view v-for="n in editForm.pendingCount" :key="'p'+n" class="edit-img-cell edit-img-loading"><text class="edit-img-loading-text">⏳</text></view>
|
||||||
|
</view>
|
||||||
|
<button v-if="editForm.images.length + editForm.pendingCount < 9" class="edit-upload" @tap="handleChooseImage" :disabled="isUploading">{{ isUploading ? '上传中...' : '📷 拍照/选图' }}</button>
|
||||||
|
<view class="edit-btns">
|
||||||
|
<button class="edit-btn edit-cancel" @tap="closeEditPopup">取消</button>
|
||||||
|
<button class="edit-btn edit-save" :disabled="editSaving || isUploading" @tap="doSaveEdit">{{ editSaving ? '保存中...' : '保存' }}</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -75,11 +95,16 @@ export default {
|
|||||||
task: null,
|
task: null,
|
||||||
records: [],
|
records: [],
|
||||||
currentUser: null,
|
currentUser: null,
|
||||||
// 🛡️ 双重确认删除(5 秒倒计时防误触)
|
// 🛡️ 双重确认(5 秒倒计时防误触)
|
||||||
confirmDlg: { visible: false, title: "", content: "", target: null },
|
confirmDlg: { visible: false, title: "", content: "", action: null },
|
||||||
confirming: "",
|
confirming: "",
|
||||||
confirmCount: 5,
|
confirmCount: 5,
|
||||||
confirmTimer: null,
|
confirmTimer: null,
|
||||||
|
// ✏️ 编辑记录弹窗
|
||||||
|
editVisible: false,
|
||||||
|
editForm: { recordId: null, remark: "", images: [], pendingCount: 0 },
|
||||||
|
editSaving: false,
|
||||||
|
isUploading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -157,19 +182,79 @@ export default {
|
|||||||
return map[s] || s;
|
return map[s] || s;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ✏️ 编辑记录:直接在当前页弹窗编辑备注与图片
|
||||||
openEditRecord(rec) {
|
openEditRecord(rec) {
|
||||||
// 复用 detail.vue 的编辑流程 — 通过全局事件或直接跳回
|
this.editForm = { recordId: rec.id, remark: rec.remark || "", images: rec.images || [], pendingCount: 0 };
|
||||||
// 简单方案:在当前页弹窗编辑
|
this.editVisible = true;
|
||||||
uni.showToast({ title: "编辑功能请返回详情页操作", icon: "none", duration: 2000 });
|
this.isUploading = false;
|
||||||
|
},
|
||||||
|
closeEditPopup() {
|
||||||
|
this.editVisible = false;
|
||||||
|
this.editForm = { recordId: null, remark: "", images: [], pendingCount: 0 };
|
||||||
|
},
|
||||||
|
previewEditImage(i) {
|
||||||
|
uni.previewImage({ urls: this.editForm.images.map((u) => this.imageUrl(u)), current: i });
|
||||||
|
},
|
||||||
|
removeEditImage(i) {
|
||||||
|
this.openConfirmDlg({ title: "删除图片", content: "确定删除这张图片吗?", action: () => { this.editForm.images.splice(i, 1); } });
|
||||||
|
},
|
||||||
|
async handleChooseImage() {
|
||||||
|
const maxSlots = 9 - (this.editForm.images.length + this.editForm.pendingCount);
|
||||||
|
if (maxSlots <= 0) return;
|
||||||
|
const chooseRes = await new Promise((resolve, reject) => {
|
||||||
|
uni.chooseImage({ count: maxSlots, sizeType: ["compressed"], sourceType: ["camera", "album"], success: resolve, fail: reject });
|
||||||
|
}).catch(() => null);
|
||||||
|
if (!chooseRes || !chooseRes.tempFilePaths || !chooseRes.tempFilePaths.length) return;
|
||||||
|
const compressedPaths = [];
|
||||||
|
for (const p of chooseRes.tempFilePaths) {
|
||||||
|
try {
|
||||||
|
const compressed = await new Promise((resolve, reject) => { uni.compressImage({ src: p, quality: 60, success: resolve, fail: reject }); });
|
||||||
|
compressedPaths.push(compressed.tempFilePath);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
if (!compressedPaths.length) return;
|
||||||
|
this.isUploading = true;
|
||||||
|
this.editForm.pendingCount += compressedPaths.length;
|
||||||
|
for (const path of compressedPaths) {
|
||||||
|
const url = await this.uploadFile(path);
|
||||||
|
if (url) this.editForm.images.push(url);
|
||||||
|
this.editForm.pendingCount--;
|
||||||
|
}
|
||||||
|
this.isUploading = false;
|
||||||
|
},
|
||||||
|
uploadFile(filePath) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
uni.uploadFile({
|
||||||
|
url: getBaseUrl() + "/upload/", filePath, name: "file",
|
||||||
|
success(res) { try { const data = JSON.parse(res.data); resolve(data.url || null); } catch { resolve(null); } },
|
||||||
|
fail: () => resolve(null),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async doSaveEdit() {
|
||||||
|
if (this.isUploading) return;
|
||||||
|
this.editSaving = true;
|
||||||
|
try {
|
||||||
|
const payload = { remark: this.editForm.remark.trim(), images: this.editForm.images };
|
||||||
|
await put(`/records/${this.editForm.recordId}`, payload);
|
||||||
|
uni.showToast({ title: "已保存", icon: "success" });
|
||||||
|
const idx = this.records.findIndex((r) => r.id === this.editForm.recordId);
|
||||||
|
if (idx >= 0) this.records[idx] = { ...this.records[idx], remark: payload.remark, images: payload.images };
|
||||||
|
this.closeEditPopup();
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: (e && e.data && e.data.detail) || "保存失败", icon: "none" });
|
||||||
|
} finally {
|
||||||
|
this.editSaving = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
confirmDelete(rec) {
|
confirmDelete(rec) {
|
||||||
this.openConfirmDlg({ title: "删除记录", content: "确定删除这条记录吗?删除后不可恢复。", target: rec });
|
this.openConfirmDlg({ title: "删除记录", content: "确定删除这条记录吗?删除后不可恢复。", action: () => this.deleteRecord(rec) });
|
||||||
},
|
},
|
||||||
// ═══ 双重确认倒计时(防误触) ═══
|
// ═══ 双重确认倒计时(防误触) ═══
|
||||||
openConfirmDlg({ title, content, target }) {
|
openConfirmDlg({ title, content, action }) {
|
||||||
this.clearConfirm();
|
this.clearConfirm();
|
||||||
this.confirmDlg = { visible: true, title, content, target };
|
this.confirmDlg = { visible: true, title, content, action };
|
||||||
this.confirming = "dlg";
|
this.confirming = "dlg";
|
||||||
this.confirmCount = 5;
|
this.confirmCount = 5;
|
||||||
this.confirmTimer = setInterval(() => {
|
this.confirmTimer = setInterval(() => {
|
||||||
@ -190,15 +275,15 @@ export default {
|
|||||||
confirmDlgConfirm() {
|
confirmDlgConfirm() {
|
||||||
if (this.confirming === "dlg" && this.confirmCount > 0) return; // 倒计时中:忽略
|
if (this.confirming === "dlg" && this.confirmCount > 0) return; // 倒计时中:忽略
|
||||||
if (this.confirming === "dlg") this.clearConfirm();
|
if (this.confirming === "dlg") this.clearConfirm();
|
||||||
const rec = this.confirmDlg.target;
|
const action = this.confirmDlg.action;
|
||||||
this.confirmDlg.visible = false;
|
this.confirmDlg.visible = false;
|
||||||
this.confirmDlg.target = null;
|
this.confirmDlg.action = null;
|
||||||
if (rec) this.deleteRecord(rec);
|
if (action) action();
|
||||||
},
|
},
|
||||||
confirmDlgCancel() {
|
confirmDlgCancel() {
|
||||||
this.clearConfirm();
|
this.clearConfirm();
|
||||||
this.confirmDlg.visible = false;
|
this.confirmDlg.visible = false;
|
||||||
this.confirmDlg.target = null;
|
this.confirmDlg.action = null;
|
||||||
},
|
},
|
||||||
async deleteRecord(rec) {
|
async deleteRecord(rec) {
|
||||||
try {
|
try {
|
||||||
@ -255,4 +340,24 @@ export default {
|
|||||||
.dlg-cancel { background: #f3f4f6; color: #6b7280; }
|
.dlg-cancel { background: #f3f4f6; color: #6b7280; }
|
||||||
.dlg-danger { background: #dc2626; color: #fff; }
|
.dlg-danger { background: #dc2626; color: #fff; }
|
||||||
.dlg-danger[disabled] { opacity: 0.5; }
|
.dlg-danger[disabled] { opacity: 0.5; }
|
||||||
|
|
||||||
|
/* 编辑记录弹窗 */
|
||||||
|
.edit-overlay { position: fixed; inset: 0; z-index: 999; background: rgba(0,0,0,0.45); display: flex; align-items: flex-end; justify-content: center; }
|
||||||
|
.edit-popup { width: 100%; max-width: 480px; background: #fff; border-radius: 16px 16px 0 0; padding: 20px 16px 32px; box-sizing: border-box; max-height: 80vh; overflow-y: auto; }
|
||||||
|
.edit-title { display: block; text-align: center; font-size: 16px; font-weight: 700; color: #1f2937; margin-bottom: 12px; }
|
||||||
|
.edit-textarea { width: 100%; height: 80px; border: 1px solid #e5e7eb; border-radius: 10px; padding: 10px; font-size: 14px; box-sizing: border-box; }
|
||||||
|
.edit-imgs { display: flex; flex-wrap: wrap; margin-top: 10px; }
|
||||||
|
.edit-img-cell { position: relative; width: 160rpx; height: 160rpx; margin: 10rpx; }
|
||||||
|
.edit-img { width: 160rpx; height: 160rpx; border-radius: 12rpx; border: 1px solid #e5e7eb; }
|
||||||
|
.edit-img-loading { display: flex; align-items: center; justify-content: center; background: #f3f4f6; border-radius: 12rpx; border: 1px dashed #d1d5db; }
|
||||||
|
.edit-img-loading-text { font-size: 36rpx; }
|
||||||
|
.edit-img-del { position: absolute; top: -12rpx; right: -12rpx; width: 40rpx; height: 40rpx; background: #ef4444; color: #fff; border-radius: 20rpx; font-size: 24rpx; text-align: center; line-height: 40rpx; z-index: 2; }
|
||||||
|
.edit-upload { width: 100%; height: 42px; border: 1px dashed #d1d5db; border-radius: 10px; background: #f9fafb; color: #6b7280; font-size: 14px; line-height: 42px; margin-top: 10px; }
|
||||||
|
.edit-upload[disabled] { opacity: 0.5; }
|
||||||
|
.edit-btns { display: flex; gap: 10px; margin-top: 16px; }
|
||||||
|
.edit-btn { flex: 1; height: 42px; line-height: 42px; border-radius: 10px; font-size: 14px; font-weight: 600; text-align: center; box-sizing: border-box; padding: 0; margin: 0; }
|
||||||
|
.edit-btn::after { border: none; }
|
||||||
|
.edit-cancel { background: #f3f4f6; color: #6b7280; }
|
||||||
|
.edit-save { background: #2563eb; color: #fff; }
|
||||||
|
.edit-save[disabled] { opacity: 0.5; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user