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;