添加牌位-1

This commit is contained in:
WindowBird 2025-10-16 17:47:18 +08:00
parent e658ea335b
commit 07040fed26
3 changed files with 291 additions and 18 deletions

View File

@ -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) {
// 统一错误上报或提示
}
```

View File

@ -0,0 +1,71 @@
# memorialHall 扫码导航修复说明
## 问题描述
用户通过扫码直接进入 `memorialHall.vue` 页面时,如果没有上一页(即页面栈中只有当前页面),点击返回按钮会卡在当前页面,无法正常返回。
## 解决方案
### 1. 导入路由工具函数
`memorialHall.vue` 中导入 `navigateBack` 函数:
```javascript
import { navigateBack } from "../../utils/router.js";
```
### 2. 监听导航栏返回事件
为自定义导航栏添加 `@back` 事件监听:
```vue
<custom-navbar ref="customNavbar" title="往生大殿" @back="handleNavbarBack" />
```
### 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` - 路由工具函数(已存在)

View File

@ -53,16 +53,12 @@
>所属区域
<text class="required">*</text>
</view>
<picker
:range="regionOptions"
:value="regionIndex"
class="picker"
@change="onRegionChange"
>
<view class="picker" @click="showRegionSelect">
<view class="picker-text">
{{ regionOptions[regionIndex] || "请选择区域" }}
{{ selectedRegionText || "请选择区域" }}
</view>
</picker>
<view class="picker-arrow">></view>
</view>
</view>
</view>
</view>
@ -78,6 +74,15 @@
</view>
</view>
</view>
<!-- 区域选择器 -->
<u-select
v-model="showRegionPicker"
:list="regionList"
mode="mutil-column-auto"
@cancel="onRegionCancel"
@confirm="onRegionConfirm"
></u-select>
</view>
</template>
@ -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;