707 lines
16 KiB
Vue
707 lines
16 KiB
Vue
<template>
|
||
<view class="page">
|
||
<base-background />
|
||
<!-- 使用自定义导航栏组件 -->
|
||
<custom-navbar title="添加牌位" />
|
||
|
||
<view class="content">
|
||
<!-- 扫码获取SN和MAC -->
|
||
<view class="scan-section">
|
||
<view class="section-title">设备信息</view>
|
||
<view class="scan-container">
|
||
<view class="scan-btn" @click="handleScanCode">
|
||
<view class="qrcode-icon">
|
||
<image
|
||
mode="aspectFit"
|
||
src="https://api.ccttiot.com/smartmeter/img/static/uy7BNwAMIKwvstqFnRhs"
|
||
/>
|
||
</view>
|
||
<view class="scan-text">扫码获取设备信息</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 显示SN和MAC信息 -->
|
||
<view v-if="deviceInfo.sn || deviceInfo.mac" class="device-info">
|
||
<view v-if="deviceInfo.code" class="info-item">
|
||
<view class="info-label">牌位编号:</view>
|
||
<view class="info-value"
|
||
>{{ deviceInfo.code }}(牌位已添加编号)
|
||
</view>
|
||
</view>
|
||
<view v-if="deviceInfo.mac" class="info-item">
|
||
<view class="info-label">mac:</view>
|
||
<view class="info-value">{{ deviceInfo.mac }}</view>
|
||
</view>
|
||
<view v-if="deviceInfo.sn" class="info-item">
|
||
<view class="info-label">sn:</view>
|
||
<view class="info-value">{{ deviceInfo.sn }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 牌位信息表单 -->
|
||
<view class="form-section">
|
||
<view class="section-title">牌位信息</view>
|
||
<view class="form-container">
|
||
<!-- 牌位编号 -->
|
||
<view class="form-item">
|
||
<view class="label"
|
||
>牌位编号
|
||
<text class="required">*</text>
|
||
</view>
|
||
<input
|
||
v-model="formData.code"
|
||
class="input"
|
||
maxlength="20"
|
||
placeholder="请输入牌位编号"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 区域选择 -->
|
||
<view class="form-item">
|
||
<view class="label"
|
||
>所属区域
|
||
<text class="required">*</text>
|
||
</view>
|
||
<view class="picker" @click="showRegionSelect">
|
||
<view class="picker-text">
|
||
{{ selectedRegionText || "请选择区域" }}
|
||
</view>
|
||
<view class="picker-arrow">></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 操作按钮 -->
|
||
<view class="button-container">
|
||
<view class="btn cancel-btn" @click="handleCancel">取消</view>
|
||
<view
|
||
:class="{ disabled: !canSubmit }"
|
||
class="btn confirm-btn"
|
||
@click="handleConfirm"
|
||
>确认添加
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 列表渲染 -->
|
||
<view v-for="(item, index) in winB_List" :key="index">
|
||
<view>{{ item.mac }}</view>
|
||
<view>{{ item.sn }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 区域选择器 -->
|
||
<u-select
|
||
v-model="showRegionPicker"
|
||
:list="regionList"
|
||
mode="mutil-column-auto"
|
||
@cancel="onRegionCancel"
|
||
@confirm="onRegionConfirm"
|
||
></u-select>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { createPagination } from "../../composables/winB_Pagination";
|
||
import { getMemorialList } from "../../api/memorial/memorial";
|
||
|
||
export default {
|
||
mixins: [
|
||
createPagination({
|
||
fetchData: getMemorialList,
|
||
mode: "loadMore", // 或 "pager"
|
||
pageSize: 5,
|
||
autoLoad: false, // 手动触发时设为 false
|
||
}),
|
||
],
|
||
created() {
|
||
// 条件就绪后触发首次加载
|
||
this.winB_UpdateParams();
|
||
},
|
||
data() {
|
||
return {
|
||
// 设备信息
|
||
deviceInfo: {
|
||
sn: "",
|
||
mac: "",
|
||
code: "",
|
||
id: "",
|
||
},
|
||
// 表单数据
|
||
formData: {
|
||
code: "",
|
||
id: "",
|
||
regionId: "",
|
||
},
|
||
// 区域相关数据
|
||
regionList: [], // 区域树形数据
|
||
showRegionPicker: false, // 控制区域选择器显示
|
||
selectedRegion: null, // 选中的区域信息
|
||
selectedRegionText: "", // 选中的区域显示文本
|
||
// 加载状态
|
||
loading: false,
|
||
};
|
||
},
|
||
computed: {
|
||
// 是否可以提交
|
||
canSubmit() {
|
||
return (
|
||
this.deviceInfo.sn &&
|
||
this.deviceInfo.mac &&
|
||
this.formData.code.trim() &&
|
||
this.selectedRegion
|
||
);
|
||
},
|
||
},
|
||
onReachBottom() {
|
||
this.winB_LoadMore();
|
||
},
|
||
async onLoad() {
|
||
// 页面加载时获取区域数据
|
||
await this.loadRegionData();
|
||
// 尝试从缓存中恢复区域选择
|
||
this.loadCachedRegion();
|
||
},
|
||
methods: {
|
||
// 扫码获取SN
|
||
async handleScanCode() {
|
||
try {
|
||
uni.showLoading({ title: "扫码中...", mask: true });
|
||
|
||
// 调用扫码功能
|
||
const res = await new Promise((resolve, reject) => {
|
||
uni.scanCode({
|
||
onlyFromCamera: true,
|
||
scanType: ["qrCode"],
|
||
success: (result) => {
|
||
resolve(result);
|
||
},
|
||
fail: (error) => {
|
||
reject(error);
|
||
},
|
||
});
|
||
});
|
||
|
||
uni.hideLoading();
|
||
|
||
if (res.result) {
|
||
// 解析二维码内容,提取SN参数
|
||
let sn = null;
|
||
let queryParams = res.result.split("?")[1];
|
||
if (queryParams) {
|
||
let params = queryParams.split("&");
|
||
params.forEach((param) => {
|
||
let [key, value] = param.split("=");
|
||
if (key === "s") {
|
||
sn = value;
|
||
}
|
||
});
|
||
}
|
||
|
||
// 如果没有从URL参数中获取到SN,尝试直接使用结果
|
||
if (!sn) {
|
||
sn = res.result.trim();
|
||
}
|
||
|
||
console.log("扫码获取到SN:", sn);
|
||
console.log("原始扫码结果:", res.result);
|
||
|
||
if (sn) {
|
||
// 通过SN获取设备信息
|
||
await this.getDeviceInfoBySn(sn);
|
||
} else {
|
||
uni.showToast({
|
||
title: "未找到有效的SN码",
|
||
icon: "none",
|
||
});
|
||
}
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading();
|
||
console.error("扫码失败:", error);
|
||
|
||
if (error.errMsg && error.errMsg.includes("cancel")) {
|
||
// 用户取消扫码,不显示错误提示
|
||
return;
|
||
}
|
||
|
||
uni.showToast({
|
||
title: "扫码失败,请重试",
|
||
icon: "none",
|
||
});
|
||
}
|
||
},
|
||
|
||
// 通过SN获取设备信息
|
||
async getDeviceInfoBySn(sn) {
|
||
try {
|
||
uni.showLoading({ title: "获取设备信息...", mask: true });
|
||
|
||
// 调用API获取设备信息
|
||
const res = await this.$request.get("/bst/memorial/getBySn", {
|
||
sn: sn,
|
||
});
|
||
|
||
uni.hideLoading();
|
||
|
||
console.log("@@@@@@@@@@@@@@@@@@@@", res);
|
||
|
||
if (res) {
|
||
const deviceData = res;
|
||
this.deviceInfo = {
|
||
sn: deviceData.sn || sn,
|
||
mac: deviceData.mac || "",
|
||
code: deviceData.code || "",
|
||
id: deviceData.id || "",
|
||
};
|
||
|
||
uni.showToast({
|
||
title: "设备信息获取成功",
|
||
icon: "success",
|
||
});
|
||
|
||
console.log("设备信息:", this.deviceInfo);
|
||
} else {
|
||
throw new Error(res?.msg || "获取设备信息失败");
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading();
|
||
console.error("获取设备信息失败:", error);
|
||
|
||
uni.showToast({
|
||
title: error.message || "获取设备信息失败",
|
||
icon: "none",
|
||
});
|
||
}
|
||
},
|
||
|
||
// 加载区域数据
|
||
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 [floor, area] = e;
|
||
|
||
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);
|
||
}
|
||
},
|
||
|
||
// 取消操作
|
||
handleCancel() {
|
||
uni.navigateBack();
|
||
},
|
||
|
||
// 确认添加
|
||
async handleConfirm() {
|
||
if (!this.canSubmit) {
|
||
uni.showToast({
|
||
title: "请完善必填信息",
|
||
icon: "none",
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 表单验证
|
||
if (!this.validateForm()) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
uni.showLoading({ title: "添加中...", mask: true });
|
||
|
||
// 构建请求数据
|
||
const requestData = {
|
||
id: this.deviceInfo.id, // 使用设备ID
|
||
code: this.formData.code,
|
||
regionId: this.selectedRegion ? this.selectedRegion.areaId : "",
|
||
};
|
||
|
||
console.log("提交数据:", requestData);
|
||
|
||
// 调用API添加牌位
|
||
const res = await this.$request.put("/bst/memorial", requestData);
|
||
|
||
uni.hideLoading();
|
||
|
||
if (res && (res.code === 200 || res.status === 200)) {
|
||
uni.showToast({
|
||
title: "添加成功",
|
||
icon: "success",
|
||
});
|
||
|
||
// 延迟返回上一页
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 1500);
|
||
} else {
|
||
throw new Error(res?.msg || "添加失败");
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading();
|
||
console.error("添加牌位失败:", error);
|
||
uni.showToast({
|
||
title: error.message || "添加失败,请重试",
|
||
icon: "none",
|
||
});
|
||
}
|
||
},
|
||
|
||
// 表单验证
|
||
validateForm() {
|
||
if (!this.formData.code.trim()) {
|
||
uni.showToast({
|
||
title: "请输入牌位编号",
|
||
icon: "none",
|
||
});
|
||
return false;
|
||
}
|
||
|
||
if (!this.selectedRegion) {
|
||
uni.showToast({
|
||
title: "请选择所属区域",
|
||
icon: "none",
|
||
});
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.page {
|
||
width: 100%;
|
||
min-height: 100vh;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.content {
|
||
padding: 20rpx;
|
||
padding-bottom: 120rpx;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 20rpx;
|
||
padding-left: 10rpx;
|
||
border-left: 6rpx solid #4a90e2;
|
||
}
|
||
|
||
.scan-section {
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 30rpx;
|
||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.scan-container {
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.scan-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 200rpx;
|
||
border: 3rpx dashed #4a90e2;
|
||
border-radius: 16rpx;
|
||
background: #f8fbff;
|
||
transition: all 0.3s ease;
|
||
|
||
&:active {
|
||
background: #e8f4ff;
|
||
transform: scale(0.98);
|
||
}
|
||
}
|
||
|
||
.qrcode-icon {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
margin-bottom: 10rpx;
|
||
|
||
image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
}
|
||
|
||
.scan-text {
|
||
font-size: 28rpx;
|
||
color: #4a90e2;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.device-info {
|
||
background: #f8f9fa;
|
||
border-radius: 12rpx;
|
||
padding: 20rpx;
|
||
border: 2rpx solid #e9ecef;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 15rpx;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
width: 140rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
flex: 1;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.form-section {
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 30rpx;
|
||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.form-container {
|
||
// 表单容器样式
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 30rpx;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
.label {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
margin-bottom: 12rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.required {
|
||
color: #ff4757;
|
||
margin-left: 4rpx;
|
||
}
|
||
|
||
.input {
|
||
width: 100%;
|
||
height: 80rpx;
|
||
border: 2rpx solid #e5e5e5;
|
||
border-radius: 8rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
box-sizing: border-box;
|
||
background: #fff;
|
||
|
||
&:focus {
|
||
border-color: #4a90e2;
|
||
}
|
||
}
|
||
|
||
.picker {
|
||
width: 100%;
|
||
height: 80rpx;
|
||
border: 2rpx solid #e5e5e5;
|
||
border-radius: 8rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 20rpx;
|
||
background: #fff;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.picker-text {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
flex: 1;
|
||
}
|
||
|
||
.picker-arrow {
|
||
color: #999;
|
||
font-size: 24rpx;
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
.textarea {
|
||
width: 100%;
|
||
min-height: 120rpx;
|
||
border: 2rpx solid #e5e5e5;
|
||
border-radius: 8rpx;
|
||
padding: 20rpx;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
box-sizing: border-box;
|
||
background: #fff;
|
||
resize: none;
|
||
|
||
&:focus {
|
||
border-color: #4a90e2;
|
||
}
|
||
}
|
||
|
||
.button-container {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
padding: 0 20rpx;
|
||
}
|
||
|
||
.btn {
|
||
flex: 1;
|
||
height: 88rpx;
|
||
border-radius: 12rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.cancel-btn {
|
||
background: #f5f5f5;
|
||
color: #666;
|
||
border: 2rpx solid #e5e5e5;
|
||
|
||
&:active {
|
||
background: #e8e8e8;
|
||
}
|
||
}
|
||
|
||
.confirm-btn {
|
||
background: #4a90e2;
|
||
color: #fff;
|
||
box-shadow: 0 4rpx 12rpx rgba(74, 144, 226, 0.3);
|
||
|
||
&:active {
|
||
background: #357abd;
|
||
}
|
||
|
||
&.disabled {
|
||
background: #ccc;
|
||
color: #999;
|
||
box-shadow: none;
|
||
|
||
&:active {
|
||
background: #ccc;
|
||
}
|
||
}
|
||
}
|
||
</style>
|