From 07040fed2686224f34ef8c865788891c4789e19b Mon Sep 17 00:00:00 2001
From: WindowBird <13870814+windows-bird@user.noreply.gitee.com>
Date: Thu, 16 Oct 2025 17:47:18 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=89=8C=E4=BD=8D-1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/adminMemorial-确认操作说明.md | 79 +++++++++++++
docs/memorialHall-扫码导航修复说明.md | 71 ++++++++++++
pages/memorial/addMemorial.vue | 159 +++++++++++++++++++++++---
3 files changed, 291 insertions(+), 18 deletions(-)
create mode 100644 docs/adminMemorial-确认操作说明.md
create mode 100644 docs/memorialHall-扫码导航修复说明.md
diff --git a/docs/adminMemorial-确认操作说明.md b/docs/adminMemorial-确认操作说明.md
new file mode 100644
index 0000000..6e45077
--- /dev/null
+++ b/docs/adminMemorial-确认操作说明.md
@@ -0,0 +1,79 @@
+## adminMemorial 确认操作说明
+
+本文档说明在 `pages/memorial/adminMemorial.vue` 中新增的确认封装与其使用方式。
+
+### 目的
+- 在高风险操作(全部开启、全部关闭、强制开启、强制关闭、时长归零)前弹出确认,避免误触。
+- 通过 Promise 封装让调用处可以使用 async/await,编写更清晰的顺序代码。
+
+### 核心封装:confirmAction
+```javascript
+// 确认提示封装,返回是否确认
+async function confirmAction(message) {
+ const res = await new Promise((resolve) => {
+ uni.showModal({
+ title: "确认操作",
+ content: message,
+ confirmText: "确认",
+ cancelText: "取消",
+ success: (r) => resolve(r),
+ });
+ });
+ return !!(res && res.confirm);
+}
+```
+
+### 为何需要 Promise 包装
+- `uni.showModal` 使用回调(success/fail),不能直接 `await`。
+- 用 `new Promise` 将回调转换为 Promise,调用方即可:
+```javascript
+const ok = await confirmAction("确定要全部关闭吗?");
+if (!ok) return; // 用户取消则中断后续流程
+```
+
+### 关于 `!!(res && res.confirm)`
+- `res && res.confirm`:若存在回调结果再读取 `confirm`,否则为 `false`。
+- `!!value`:将任意值强制转为布尔,等价 `Boolean(value)`。
+- 语义:仅当用户点击“确认”时返回 `true`,其他情况均为 `false`。
+
+### 已接入的高风险操作
+- `handleAllOpen`:全部开启
+- `handleAllClose`:全部关闭
+- `handleForceOpen`:强制开启(需选中单元)
+- `handleForceClose`:强制关闭(需选中单元)
+- `handleResetDuration`:时长归零(需选中单元)
+
+接入方式统一如下(示例):
+```javascript
+const ok = await this.confirmAction("确定要全部开启吗?");
+if (!ok) return;
+// 继续调用接口
+```
+
+### 可定制项(如需)
+- 通过 `uni.showModal` 的参数可定制:
+ - `title`:标题
+ - `content`:正文
+ - `showCancel`:是否显示取消按钮
+ - `confirmText` / `cancelText`:按钮文案
+ - `confirmColor` / `cancelColor`:按钮颜色
+
+若需要让“取消”成为更显眼的默认选择,可调整按钮文案与颜色,或在交互上将危险操作改为二次输入确认。
+
+### 错误处理(可选增强)
+如需捕获弹窗失败(极少见),可在封装中补充 `fail` 并 `reject(err)`,调用方用 `try/catch` 处理:
+```javascript
+try {
+ const ok = await confirmAction("确定继续吗?");
+ if (!ok) return;
+ // ...
+} catch (e) {
+ // 统一错误上报或提示
+}
+```
+
+
+
+
+
+
diff --git a/docs/memorialHall-扫码导航修复说明.md b/docs/memorialHall-扫码导航修复说明.md
new file mode 100644
index 0000000..89cfb68
--- /dev/null
+++ b/docs/memorialHall-扫码导航修复说明.md
@@ -0,0 +1,71 @@
+# memorialHall 扫码导航修复说明
+
+## 问题描述
+用户通过扫码直接进入 `memorialHall.vue` 页面时,如果没有上一页(即页面栈中只有当前页面),点击返回按钮会卡在当前页面,无法正常返回。
+
+## 解决方案
+
+### 1. 导入路由工具函数
+在 `memorialHall.vue` 中导入 `navigateBack` 函数:
+```javascript
+import { navigateBack } from "../../utils/router.js";
+```
+
+### 2. 监听导航栏返回事件
+为自定义导航栏添加 `@back` 事件监听:
+```vue
+
+```
+
+### 3. 实现返回处理方法
+添加 `handleNavbarBack` 方法,使用 `navigateBack` 函数处理返回逻辑:
+```javascript
+// 处理导航栏返回事件
+handleNavbarBack() {
+ // 使用路由工具函数,如果没有上一页会自动跳转到首页
+ navigateBack();
+},
+```
+
+### 4. 优化自定义导航栏组件
+修改 `custom-navbar.vue` 的 `handleBack` 方法,支持父组件完全控制返回逻辑:
+```javascript
+handleBack() {
+ if (this.showBack) {
+ // 触发自定义事件,让父组件处理返回逻辑
+ this.$emit("back");
+ // 如果父组件没有监听 back 事件,则执行默认返回逻辑
+ if (!this.$listeners.back) {
+ uni.navigateBack({
+ delta: 1,
+ fail: () => {
+ // 如果没有上一页,跳转到首页
+ uni.switchTab({
+ url: "/pages/index/index",
+ });
+ },
+ });
+ }
+ }
+},
+```
+
+## 工作原理
+
+1. **路由工具函数**:`navigateBack` 函数会先尝试 `uni.navigateBack`,如果失败(没有上一页),会自动调用 `reLaunchToPage("index")` 跳转到首页。
+
+2. **事件驱动**:自定义导航栏通过 `@back` 事件将返回控制权交给父组件,父组件可以使用自己的返回逻辑。
+
+3. **向下兼容**:如果父组件没有监听 `@back` 事件,导航栏组件会执行默认的返回逻辑,确保不会破坏现有功能。
+
+## 测试场景
+
+- ✅ 正常页面跳转后返回(有上一页)
+- ✅ 扫码直接进入后返回(无上一页,自动跳转首页)
+- ✅ 其他页面使用自定义导航栏(向下兼容)
+
+## 相关文件
+
+- `pages/memorial/memorialHall.vue` - 主要修改页面
+- `components/custom-navbar/custom-navbar.vue` - 导航栏组件优化
+- `utils/router.js` - 路由工具函数(已存在)
diff --git a/pages/memorial/addMemorial.vue b/pages/memorial/addMemorial.vue
index adf399d..c83a44d 100644
--- a/pages/memorial/addMemorial.vue
+++ b/pages/memorial/addMemorial.vue
@@ -53,16 +53,12 @@
>所属区域
*
-
+
- {{ regionOptions[regionIndex] || "请选择区域" }}
+ {{ selectedRegionText || "请选择区域" }}
-
+ >
+
@@ -78,6 +74,15 @@
+
+
+
@@ -100,9 +105,11 @@ export default {
orderNum: "",
remark: "",
},
- // 区域选项
- regionOptions: ["A区", "B区", "C区", "D区"],
- regionIndex: -1,
+ // 区域相关数据
+ regionList: [], // 区域树形数据
+ showRegionPicker: false, // 控制区域选择器显示
+ selectedRegion: null, // 选中的区域信息
+ selectedRegionText: "", // 选中的区域显示文本
// 加载状态
loading: false,
};
@@ -114,10 +121,16 @@ export default {
this.deviceInfo.sn &&
this.deviceInfo.mac &&
this.formData.code.trim() &&
- this.formData.regionId
+ this.selectedRegion
);
},
},
+ async onLoad() {
+ // 页面加载时获取区域数据
+ await this.loadRegionData();
+ // 尝试从缓存中恢复区域选择
+ this.loadCachedRegion();
+ },
methods: {
// 扫码获取SN
async handleScanCode() {
@@ -231,10 +244,114 @@ export default {
}
},
- // 区域选择
- onRegionChange(e) {
- this.regionIndex = e.detail.value;
- this.formData.regionId = this.regionOptions[this.regionIndex];
+ // 加载区域数据
+ async loadRegionData() {
+ try {
+ // 从缓存中获取templeId,如果没有则使用默认值
+ const templeId = uni.getStorageSync("templeId") || "12";
+
+ const res = await this.$request.get(`/bst/region/listTree/${templeId}`);
+
+ if (res && res.data) {
+ // 转换数据格式为u-select需要的格式
+ this.regionList = this.transformRegionData(res.data);
+ console.log("区域数据加载成功:", this.regionList);
+ } else {
+ throw new Error("获取区域数据失败");
+ }
+ } catch (error) {
+ console.error("加载区域数据失败:", error);
+ uni.showToast({
+ title: "加载区域数据失败",
+ icon: "none",
+ });
+ }
+ },
+
+ // 转换区域数据格式
+ transformRegionData(data) {
+ if (!data || !data.children) return [];
+
+ return data.children.map((floor) => ({
+ label: floor.label,
+ value: floor.id,
+ children: floor.children
+ ? floor.children.map((area) => ({
+ label: area.label,
+ value: area.id,
+ }))
+ : [],
+ }));
+ },
+
+ // 显示区域选择器
+ showRegionSelect() {
+ if (this.regionList.length === 0) {
+ uni.showToast({
+ title: "区域数据加载中,请稍后",
+ icon: "none",
+ });
+ return;
+ }
+ this.showRegionPicker = true;
+ },
+
+ // 区域选择确认
+ onRegionConfirm(e) {
+ console.log("区域选择结果:", e);
+ if (e && e.length >= 2) {
+ const [floorIndex, areaIndex] = e;
+ const floor = this.regionList[floorIndex];
+ const area = floor.children[areaIndex];
+
+ this.selectedRegion = {
+ floorId: floor.value,
+ floorName: floor.label,
+ areaId: area.value,
+ areaName: area.label,
+ };
+
+ this.selectedRegionText = `${floor.label} - ${area.label}`;
+ this.formData.regionId = area.value;
+
+ // 缓存区域选择
+ this.cacheRegionSelection();
+
+ uni.showToast({
+ title: "区域选择成功",
+ icon: "success",
+ });
+ }
+ },
+
+ // 区域选择取消
+ onRegionCancel() {
+ this.showRegionPicker = false;
+ },
+
+ // 缓存区域选择
+ cacheRegionSelection() {
+ if (this.selectedRegion) {
+ uni.setStorageSync("lastSelectedRegion", this.selectedRegion);
+ uni.setStorageSync("lastSelectedRegionText", this.selectedRegionText);
+ }
+ },
+
+ // 从缓存加载区域选择
+ loadCachedRegion() {
+ try {
+ const cachedRegion = uni.getStorageSync("lastSelectedRegion");
+ const cachedRegionText = uni.getStorageSync("lastSelectedRegionText");
+
+ if (cachedRegion && cachedRegionText) {
+ this.selectedRegion = cachedRegion;
+ this.selectedRegionText = cachedRegionText;
+ this.formData.regionId = cachedRegion.areaId;
+ console.log("已恢复缓存的区域选择:", cachedRegion);
+ }
+ } catch (error) {
+ console.error("加载缓存区域失败:", error);
+ }
},
// 取消操作
@@ -265,7 +382,7 @@ export default {
id: this.deviceInfo.id, // 使用设备ID
code: this.formData.code,
name: this.formData.name,
- regionId: this.getRegionId(this.formData.regionId),
+ regionId: this.selectedRegion ? this.selectedRegion.areaId : "",
orderNum: this.formData.orderNum || "1",
sn: this.deviceInfo.sn,
mac: this.deviceInfo.mac,
@@ -312,7 +429,7 @@ export default {
return false;
}
- if (!this.formData.regionId) {
+ if (!this.selectedRegion) {
uni.showToast({
title: "请选择所属区域",
icon: "none",
@@ -502,6 +619,12 @@ export default {
flex: 1;
}
+.picker-arrow {
+ color: #999;
+ font-size: 24rpx;
+ transform: rotate(90deg);
+}
+
.textarea {
width: 100%;
min-height: 120rpx;