Преглед на файлове

feat(homesys): 房屋管家对接接口

wangpx преди 7 месеца
родител
ревизия
10aae782d2
променени са 7 файла, в които са добавени 1353 реда и са изтрити 120 реда
  1. 27 1
      pages.json
  2. 3 4
      pages/work/homesys/index.vue
  3. 466 0
      pages/work/homesysSetting/index.vue
  4. 690 0
      pages/work/homesysSetting/user.vue
  5. 107 0
      pages/work/homesysSetting/userList.vue
  6. 59 114
      pages/work/index.vue
  7. 1 1
      utils/request.js

+ 27 - 1
pages.json

@@ -104,6 +104,33 @@
 				"enablePullDownRefresh" : false
 			}
 		},
+		{
+			"path" : "pages/work/homesysSetting/index",
+			"style" : 
+			{
+				"navigationBarTitleText" : "房屋管理",
+				// "navigationStyle": "custom", // 隐藏默认导航栏(需自行实现自定义导航栏)
+				"enablePullDownRefresh" : false
+			}
+		},
+		{
+			"path" : "pages/work/homesysSetting/user",
+			"style" : 
+			{
+				"navigationBarTitleText" : "用户管理",
+				// "navigationStyle": "custom", // 隐藏默认导航栏(需自行实现自定义导航栏)
+				"enablePullDownRefresh" : false
+			}
+		},
+		{
+			"path" : "pages/work/homesysSetting/userList",
+			"style" : 
+			{
+				"navigationBarTitleText" : "用户列表",
+				// "navigationStyle": "custom", // 隐藏默认导航栏(需自行实现自定义导航栏)
+				"enablePullDownRefresh" : false
+			}
+		},
 		{
 			"path": "pages/mine/edit/edit",
 			"style": {
@@ -160,7 +187,6 @@
 				"navigationBarTitleText" : "头像修改"
 			}
 		}
-		
 	],
 	"tabBar": {
 		"color": "#7A7E83",

+ 3 - 4
pages/work/homesys/index.vue

@@ -27,12 +27,11 @@ onMounted(() => {
 	const username = 'yunzhuoa'
 	const password = 'HNYZ0821'
 	getHomeAssistantLoginFlowId(homeCode.value).then((data) => {
-		console.log('getHomeAssistantLoginFlowId', data)
+		// console.log('getHomeAssistantLoginFlowId', data)
 		getHomeAssistantLoginCode(data.flow_id, username, password).then(({result}) => {
-			console.log('getHomeAssistantLoginCode', result);
+			// console.log('getHomeAssistantLoginCode', result);
 			authCode.value = result
-			homeSrc.value = 
-	'https://api.ygtxfj.com:8125/lovelace/'+homeCode.value+'?kiosk=&%3Fauth_callback=1&auth_callback=1&code='+result+'&state=eyJoYXNzVXJsIjoiaHR0cHM6Ly9hcGkueWd0eGZqLmNvbTo4MTI1IiwiY2xpZW50SWQiOiJodHRwczovL2FwaS55Z3R4ZmouY29tOjgxMjUvIn0%3D&storeToken=true'
+			homeSrc.value = homeCode.value+'?kiosk=&%3Fauth_callback=1&auth_callback=1&code='+result+'&state=eyJoYXNzVXJsIjoiaHR0cHM6Ly9hcGkueWd0eGZqLmNvbTo4MTI1IiwiY2xpZW50SWQiOiJodHRwczovL2FwaS55Z3R4ZmouY29tOjgxMjUvIn0%3D&storeToken=true'
 		})
 	})
 	// switch (userStore.user.userName) {

+ 466 - 0
pages/work/homesysSetting/index.vue

@@ -0,0 +1,466 @@
+<template>
+	<page-meta root-font-size="system" />
+	<view>
+		<view class="container">
+		<!-- 页面标题 -->
+		<view class="header">
+			<text class="title">智能家居房间管理</text>
+		</view>
+		
+		<!-- 添加房间按钮 -->
+		<view class="add-btn-container">
+			<button class="add-btn" @click="addRoom">
+				<text class="add-icon">+</text>
+				<text>添加房间</text>
+			</button>
+		</view>
+		
+		<!-- 房间列表 -->
+		<view class="room-list">
+			<view v-if="loading" class="loading">
+				<text>加载中...</text>
+			</view>
+			<view v-else-if="roomList.length === 0" class="empty">
+				<text>暂无房间数据</text>
+			</view>
+			<view v-else>
+				<view v-for="room in roomList" :key="room.id" class="room-item">
+					<view class="room-info">
+						<text class="room-name">{{ room.room_name }}</text>
+						<text class="room-url">{{ room.url }}</text>
+					</view>
+					<view class="room-actions">
+						<button class="action-btn edit-btn" @click="editRoom(room)">编辑</button>
+						<button class="action-btn delete-btn" @click="deleteRoomConfirm(room)">删除</button>
+					</view>
+				</view>
+			</view>
+		</view>
+		
+		<!-- 添加/编辑房间弹窗 -->
+		<uni-popup ref="popup" type="center" :mask-click="false">
+			<view class="popup-content">
+				<view class="popup-header">
+					<text class="popup-title">{{ isEdit ? '编辑房间' : '添加房间' }}</text>
+					<text class="close-btn" @click="closeDialog">×</text>
+				</view>
+				<view class="popup-body">
+					<view class="form-item">
+						<text class="label">房间名称</text>
+						<input 
+							class="input" 
+							v-model="formData.roomName" 
+							placeholder="请输入房间名称"
+							maxlength="20"
+						/>
+					</view>
+					<view class="form-item">
+						<text class="label">房间URL</text>
+						<input 
+							class="input" 
+							v-model="formData.url" 
+							placeholder="请输入房间URL"
+							maxlength="200"
+						/>
+					</view>
+				</view>
+				<view class="popup-footer">
+					<button class="cancel-btn" @click="closeDialog">取消</button>
+					<button class="confirm-btn" @click="submitForm">确定</button>
+				</view>
+			</view>
+		</uni-popup>
+		
+		<!-- 删除确认弹窗 -->
+		<uni-popup ref="deletePopup" type="center">
+			<view class="delete-popup">
+				<view class="delete-content">
+					<text class="delete-title">确认删除</text>
+					<text class="delete-message">确定要删除房间"{{ currentRoom?.room_name }}"吗?</text>
+					<view class="delete-actions">
+						<button class="cancel-btn" @click="closeDeleteDialog">取消</button>
+						<button class="delete-confirm-btn" @click="confirmDelete">删除</button>
+					</view>
+				</view>
+			</view>
+		</uni-popup>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { getRoomList, createRoom, deleteRoom, updateRoom } from '@/api/work.js'
+
+// 响应式数据
+const roomList = ref([])
+const loading = ref(false)
+const isEdit = ref(false)
+const currentRoom = ref(null)
+const formData = ref({
+	roomName: '',
+	url: 'https://api.ygtxfj.com:8125/lovelace/'
+})
+
+// 弹窗引用
+const popup = ref(null)
+const deletePopup = ref(null)
+
+// 页面加载时获取房间列表
+onMounted(() => {
+	loadRoomList()
+})
+
+// 加载房间列表
+const loadRoomList = () => {
+	loading.value = true
+	getRoomList().then((res) => {
+		console.log('getRoomList', res)
+		if (res) {
+			roomList.value = res
+		}
+		loading.value = false
+	})
+	.catch((error) => {
+		console.error('获取房间列表失败:', error)
+		uni.showToast({
+			title: '获取房间列表失败',
+			icon: 'none'
+		})
+		loading.value = false
+	})
+}
+
+// 添加房间
+const addRoom = () => {
+	isEdit.value = false
+	resetForm()
+	popup.value.open()
+}
+
+// 编辑房间
+const editRoom = (room) => {
+	isEdit.value = true
+	currentRoom.value = room
+	formData.value = {
+		roomName: room.room_name,
+		url: room.url
+	}
+	popup.value.open()
+}
+
+// 删除房间确认
+const deleteRoomConfirm = (room) => {
+	currentRoom.value = room
+	deletePopup.value.open()
+}
+
+// 关闭添加/编辑弹窗
+const closeDialog = () => {
+	popup.value.close()
+	resetForm()
+}
+
+// 关闭删除确认弹窗
+const closeDeleteDialog = () => {
+	deletePopup.value.close()
+	currentRoom.value = null
+}
+
+// 重置表单
+const resetForm = () => {
+	formData.value = {
+		roomName: '',
+		url: 'https://api.ygtxfj.com:8125/lovelace/'
+	}
+	isEdit.value = false
+	currentRoom.value = null
+}
+
+// 提交表单
+const submitForm = () => {
+	if (!formData.value.roomName.trim()) {
+		uni.showToast({
+			title: '请输入房间名称',
+			icon: 'none'
+		})
+		return
+	}
+	
+	if (!formData.value.url.trim()) {
+		uni.showToast({
+			title: '请输入房间URL',
+			icon: 'none'
+		})
+		return
+	}
+	
+	const apiCall = isEdit.value 
+		? updateRoom(currentRoom.value.id, formData.value.roomName, formData.value.url)
+		: createRoom(formData.value.roomName, formData.value.url)
+	
+	apiCall.then((res) => {
+		console.log(isEdit.value ? 'updateRoom' : 'createRoom', res)
+		uni.showToast({
+			title: isEdit.value ? '编辑成功' : '添加成功',
+			icon: 'success'
+		})
+		closeDialog()
+		loadRoomList()
+	}).catch((error) => {
+		console.error('操作失败:', error)
+		uni.showToast({
+			title: '操作失败',
+			icon: 'none'
+		})
+	})
+}
+
+// 确认删除
+const confirmDelete = () => {
+	deleteRoom(currentRoom.value.id).then((res) => {
+		console.log('deleteRoom', res)
+		uni.showToast({
+			title: '删除成功',
+			icon: 'success'
+		})
+		closeDeleteDialog()
+		loadRoomList()
+	}).catch((error) => {
+		console.error('删除失败:', error)
+		uni.showToast({
+			title: '删除失败',
+			icon: 'none'
+		})
+	})
+}
+</script>
+
+<style lang="scss" scoped>
+.container {
+	padding: 20rpx;
+	background-color: #f5f5f5;
+	min-height: 100vh;
+}
+
+.header {
+	text-align: center;
+	margin-bottom: 30rpx;
+	
+	.title {
+		font-size: 36rpx;
+		font-weight: bold;
+		color: #333;
+	}
+}
+
+.add-btn-container {
+	margin-bottom: 30rpx;
+	
+	.add-btn {
+		width: 100%;
+		height: 80rpx;
+		background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+		color: white;
+		border: none;
+		border-radius: 40rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 28rpx;
+		
+		.add-icon {
+			margin-right: 10rpx;
+			font-size: 32rpx;
+		}
+	}
+}
+
+.room-list {
+	.loading, .empty {
+		text-align: center;
+		padding: 100rpx 0;
+		color: #999;
+		font-size: 28rpx;
+	}
+}
+
+.room-item {
+	background: white;
+	border-radius: 16rpx;
+	padding: 30rpx;
+	margin-bottom: 20rpx;
+	box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	
+	.room-info {
+		flex: 1;
+		
+		.room-name {
+			display: block;
+			font-size: 32rpx;
+			font-weight: bold;
+			color: #333;
+			margin-bottom: 10rpx;
+		}
+		
+		.room-url {
+			display: block;
+			font-size: 24rpx;
+			color: #666;
+			word-break: break-all;
+		}
+	}
+	
+	.room-actions {
+		display: flex;
+		gap: 20rpx;
+		
+		.action-btn {
+			padding: 12rpx 24rpx;
+			border-radius: 20rpx;
+			font-size: 24rpx;
+			border: none;
+			
+			&.edit-btn {
+				background-color: #007aff;
+				color: white;
+			}
+			
+			&.delete-btn {
+				background-color: #ff3b30;
+				color: white;
+			}
+		}
+	}
+}
+
+.popup-content {
+	width: 600rpx;
+	background: white;
+	border-radius: 20rpx;
+	overflow: hidden;
+	
+	.popup-header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 30rpx;
+		border-bottom: 1rpx solid #eee;
+		
+		.popup-title {
+			font-size: 32rpx;
+			font-weight: bold;
+			color: #333;
+		}
+		
+		.close-btn {
+			font-size: 40rpx;
+			color: #999;
+			width: 60rpx;
+			height: 60rpx;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+		}
+	}
+	
+	.popup-body {
+		padding: 30rpx;
+		
+		.form-item {
+			margin-bottom: 30rpx;
+			
+			.label {
+				display: block;
+				font-size: 28rpx;
+				color: #333;
+				margin-bottom: 15rpx;
+			}
+			
+			.input {
+				width: 100%;
+				height: 80rpx;
+				border: 1rpx solid #ddd;
+				border-radius: 8rpx;
+				padding: 0 20rpx;
+				font-size: 28rpx;
+				box-sizing: border-box;
+			}
+		}
+	}
+	
+	.popup-footer {
+		display: flex;
+		border-top: 1rpx solid #eee;
+		
+		button {
+			flex: 1;
+			height: 100rpx;
+			border: none;
+			font-size: 28rpx;
+			
+			&.cancel-btn {
+				background-color: #f5f5f5;
+				color: #666;
+			}
+			
+			&.confirm-btn {
+				background-color: #007aff;
+				color: white;
+			}
+		}
+	}
+}
+
+.delete-popup {
+	.delete-content {
+		width: 500rpx;
+		background: white;
+		border-radius: 20rpx;
+		padding: 40rpx;
+		text-align: center;
+		
+		.delete-title {
+			display: block;
+			font-size: 32rpx;
+			font-weight: bold;
+			color: #333;
+			margin-bottom: 20rpx;
+		}
+		
+		.delete-message {
+			display: block;
+			font-size: 28rpx;
+			color: #666;
+			margin-bottom: 40rpx;
+			line-height: 1.5;
+		}
+		
+		.delete-actions {
+			display: flex;
+			gap: 20rpx;
+			
+			button {
+				flex: 1;
+				height: 80rpx;
+				border-radius: 40rpx;
+				font-size: 28rpx;
+				border: none;
+				
+				&.cancel-btn {
+					background-color: #f5f5f5;
+					color: #666;
+				}
+				
+				&.delete-confirm-btn {
+					background-color: #ff3b30;
+					color: white;
+				}
+			}
+		}
+	}
+}
+</style>

+ 690 - 0
pages/work/homesysSetting/user.vue

@@ -0,0 +1,690 @@
+<template>
+	<page-meta root-font-size="system" />
+	<view>
+		<view class="container">
+			<view v-if="currentUser" class="user-info">
+				<view class="user-card">
+					<text class="user-label">当前用户:</text>
+					<text class="user-account">{{ currentUser }}</text>
+				</view>
+			</view>
+
+			<!-- 绑定房间按钮 -->
+			<view v-if="currentUser" class="bind-btn-container">
+				<button class="bind-btn" @click="showBindDialog">
+					<text class="bind-icon">+</text>
+					<text>绑定房间</text>
+				</button>
+			</view>
+			<!-- 用户绑定的房间列表 -->
+			<view v-if="currentUser" class="user-room-list">
+				<view v-if="loading" class="loading">
+					<text>加载中...</text>
+				</view>
+				<view v-else-if="userRoomList.length === 0" class="empty">
+					<text>该用户暂无绑定的房间</text>
+				</view>
+				<view v-else>
+					<view v-for="userRoom in userRoomList" :key="userRoom.id" class="user-room-item">
+						<view class="room-info">
+							<text class="room-name">{{ userRoom.room_name }}</text>
+							<text class="room-url">{{ userRoom.room_url }}</text>
+						</view>
+						<view class="room-actions">
+							<button class="action-btn delete-btn" @click="deleteUserRoomConfirm(userRoom)">解绑</button>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 绑定房间弹窗 -->
+			<uni-popup ref="bindPopup" type="center" :mask-click="false">
+				<view class="popup-content">
+					<view class="popup-header">
+						<text class="popup-title">绑定房间</text>
+						<text class="close-btn" @click="closeBindDialog">×</text>
+					</view>
+					<view class="popup-body">
+						<view class="form-item">
+							<text class="label">选择房间</text>
+							<picker :value="selectedRoomIndex" :range="selectableRooms" range-key="room_name" @change="onRoomChange">
+								<view class="picker-view">
+									<text v-if="selectedRoomIndex >= 0">{{ selectableRooms[selectedRoomIndex]?.room_name }}</text>
+									<text v-else class="placeholder">请选择房间</text>
+									<text class="picker-arrow">></text>
+								</view>
+							</picker>
+						</view>
+					</view>
+					<view class="popup-footer">
+						<button class="cancel-btn" @click="closeBindDialog">取消</button>
+						<button class="confirm-btn" @click="submitBind">确定</button>
+					</view>
+				</view>
+			</uni-popup>
+
+			<!-- 解绑确认弹窗 -->
+			<uni-popup ref="deletePopup" type="center">
+				<view class="delete-popup">
+					<view class="delete-content">
+						<text class="delete-title">确认解绑</text>
+						<text
+							class="delete-message">确定要解绑用户"{{ currentUserRoom?.user_account }}"与房间"{{ currentUserRoom?.room_name }}"的绑定吗?</text>
+						<view class="delete-actions">
+							<button class="cancel-btn" @click="closeDeleteDialog">取消</button>
+							<button class="delete-confirm-btn" @click="confirmUnbind">解绑</button>
+						</view>
+					</view>
+				</view>
+			</uni-popup>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import { ref, onMounted } from 'vue'
+	import { onLoad } from '@dcloudio/uni-app'
+	import { bindUserRoom, deleteUserRoom, getUserRoomByUser, getRoomList, getUserRoomList } from '@/api/work.js'
+
+	onLoad((options) => {
+		if (options && options.userName) {
+			currentUser.value = String(options.userName).trim()
+			searchUserAccount.value = currentUser.value
+			loading.value = true
+			Promise.resolve()
+				.then(() => loadUserRooms())
+				.then(() => loadAvailableRooms())
+				.finally(() => { loading.value = false })
+		}
+	})
+	// 响应式数据
+	const searchUserAccount = ref('')
+	const currentUser = ref('')
+	const userRoomList = ref([])
+	const allBindList = ref([])
+	const availableRooms = ref([])
+	const selectableRooms = ref([])
+	const loading = ref(false)
+	const selectedRoomIndex = ref(-1)
+	const currentUserRoom = ref(null)
+
+	// 弹窗引用
+	const bindPopup = ref(null)
+	const deletePopup = ref(null)
+
+	const showBindDialog = () => {
+		selectedRoomIndex.value = -1
+		// 打开前仅基于本地数据计算,避免重复请求
+		computeSelectableRooms()
+		bindPopup.value.open()
+	}
+	
+	// 页面加载时获取所有绑定数据
+	onMounted(() => {
+		// 若无路由参数,可在此回退到输入账号搜索
+	})
+
+	// 加载所有用户绑定数据
+	const loadAllBindList = () => {
+		loading.value = true
+		getUserRoomList().then((res) => {
+			console.log('getUserRoomList', res)
+			if (res) {
+				allBindList.value = res
+			} else {
+				allBindList.value = []
+			}
+			loading.value = false
+		}).catch((error) => {
+			console.error('获取所有绑定数据失败:', error)
+			allBindList.value = []
+			loading.value = false
+		})
+	}
+
+	// 搜索用户
+	const searchUser = () => {
+		if (!searchUserAccount.value.trim()) {
+			uni.showToast({
+				title: '请输入用户账号',
+				icon: 'none'
+			})
+			return
+		}
+
+		loading.value = true
+		currentUser.value = searchUserAccount.value.trim()
+
+		loadUserRooms().then(() => {
+			return loadAvailableRooms()
+		}).then(() => {
+			loading.value = false
+		}).catch((error) => {
+			console.error('搜索用户失败:', error)
+			uni.showToast({
+				title: '搜索用户失败',
+				icon: 'none'
+			})
+			loading.value = false
+		})
+	}
+
+	// 加载用户绑定的房间
+	const loadUserRooms = () => {
+		return getUserRoomByUser(currentUser.value).then((res) => {
+			console.log('getUserRoomByUser', res)
+			if (res) {
+				userRoomList.value = res
+			} else {
+				userRoomList.value = []
+			}
+		}).catch((error) => {
+			console.error('获取用户房间失败:', error)
+			userRoomList.value = []
+		})
+	}
+
+	// 加载可用房间列表
+	const loadAvailableRooms = () => {
+		return getRoomList().then((res) => {
+			console.log('getRoomList', res)
+			if (res) {
+				availableRooms.value = res
+				computeSelectableRooms()
+			}
+		}).catch((error) => {
+			console.error('获取房间列表失败:', error)
+			availableRooms.value = []
+			selectableRooms.value = []
+		})
+	}
+
+	// 基于本地数据计算可选房间,避免额外网络请求
+	const computeSelectableRooms = () => {
+		const norm = (v) => (v == null ? '' : String(v).trim())
+		const getRoomUrl = (room) => norm(room?.url ?? room?.room_url)
+		const boundUrlSet = new Set((userRoomList.value || []).map(item => norm(item.room_url ?? item.roomUrl ?? item.url)))
+		const filtered = (availableRooms.value || []).filter((room) => {
+			const urlKey = getRoomUrl(room)
+			return !boundUrlSet.has(urlKey)
+		})
+		const uniqueMap = new Map()
+		for (const room of filtered) {
+			const key = getRoomUrl(room)
+			if (!uniqueMap.has(key)) uniqueMap.set(key, room)
+		}
+		selectableRooms.value = Array.from(uniqueMap.values())
+	}
+
+	// 房间选择变化
+	const onRoomChange = (e) => {
+		selectedRoomIndex.value = e.detail.value
+	}
+
+	// 解绑房间确认
+	const deleteUserRoomConfirm = (userRoom) => {
+		currentUserRoom.value = userRoom
+		deletePopup.value.open()
+	}
+
+	// 关闭绑定弹窗
+	const closeBindDialog = () => {
+		bindPopup.value.close()
+		selectedRoomIndex.value = -1
+	}
+
+	// 关闭删除确认弹窗
+	const closeDeleteDialog = () => {
+		deletePopup.value.close()
+		currentUserRoom.value = null
+	}
+
+	// 提交绑定
+	const submitBind = () => {
+		if (selectedRoomIndex.value < 0) {
+			uni.showToast({
+				title: '请选择房间',
+				icon: 'none'
+			})
+			return
+		}
+
+		const selectedRoom = selectableRooms.value[selectedRoomIndex.value]
+
+		// 再次校验是否已绑定(防止数据滞后/并发重复)仅根据 URL 判断
+		const norm = (v) => (v == null ? '' : String(v).trim())
+		const getRoomUrl = (room) => norm(room?.url ?? room?.room_url)
+		const alreadyBoundUrls = new Set(userRoomList.value.map(item => norm(item.room_url ?? item.roomUrl ?? item.url)))
+		if (alreadyBoundUrls.has(getRoomUrl(selectedRoom))) {
+			uni.showToast({
+				title: '该房间已绑定',
+				icon: 'none'
+			})
+			return
+		}
+
+		bindUserRoom(currentUser.value, selectedRoom.id + "").then((res) => {
+			console.log('bindUserRoom', res)
+			uni.showToast({
+				title: '绑定成功',
+				icon: 'success'
+			})
+			closeBindDialog()
+			return loadUserRooms()
+		}).then(() => {
+			return loadAvailableRooms()
+		}).catch((error) => {
+			console.error('绑定失败:', error)
+			uni.showToast({
+				title: '绑定失败',
+				icon: 'none'
+			})
+		})
+	}
+
+	// 确认解绑
+	const confirmUnbind = () => {
+		deleteUserRoom(currentUserRoom.value.id).then((res) => {
+			console.log('deleteUserRoom', res)
+			uni.showToast({
+				title: '解绑成功',
+				icon: 'success'
+			})
+			closeDeleteDialog()
+			// 如果当前有搜索用户,也重新加载用户房间数据
+			loadUserRooms().then(() => {
+				return loadAvailableRooms()
+			})
+		}).catch((error) => {
+			console.error('解绑失败:', error)
+			uni.showToast({
+				title: '解绑失败',
+				icon: 'none'
+			})
+		})
+	}
+
+	// 格式化时间
+	const formatTime = (timeStr) => {
+		if (!timeStr) return ''
+		const date = new Date(timeStr)
+		return date.toLocaleString('zh-CN')
+	}
+</script>
+
+<style lang="scss" scoped>
+	.container {
+		padding: 20rpx;
+		background-color: #f5f5f5;
+		min-height: 100vh;
+	}
+
+	.header {
+		text-align: center;
+		margin-bottom: 30rpx;
+
+		.title {
+			font-size: 36rpx;
+			font-weight: bold;
+			color: #333;
+		}
+	}
+
+	.add-bind-container {
+		margin-bottom: 30rpx;
+
+		.add-bind-btn {
+			width: 100%;
+			height: 80rpx;
+			background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+			color: white;
+			border: none;
+			border-radius: 40rpx;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			font-size: 28rpx;
+
+			.add-icon {
+				margin-right: 10rpx;
+				font-size: 32rpx;
+			}
+		}
+	}
+
+	.search-container {
+		margin-bottom: 30rpx;
+
+		.search-box {
+			display: flex;
+			background: white;
+			border-radius: 40rpx;
+			padding: 10rpx;
+			box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+
+			.search-input {
+				flex: 1;
+				height: 60rpx;
+				padding: 0 20rpx;
+				font-size: 28rpx;
+				border: none;
+				outline: none;
+			}
+
+			.search-btn {
+				width: 120rpx;
+				height: 60rpx;
+				background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+				color: white;
+				border: none;
+				border-radius: 30rpx;
+				font-size: 24rpx;
+			}
+		}
+	}
+
+	.user-info {
+		margin-bottom: 30rpx;
+
+		.user-card {
+			background: white;
+			border-radius: 16rpx;
+			padding: 30rpx;
+			box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+
+			.user-label {
+				font-size: 28rpx;
+				color: #666;
+			}
+
+			.user-account {
+				font-size: 32rpx;
+				font-weight: bold;
+				color: #333;
+			}
+		}
+	}
+
+	.bind-btn-container {
+		margin-bottom: 30rpx;
+
+		.bind-btn {
+			width: 100%;
+			height: 80rpx;
+			background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+			color: white;
+			border: none;
+			border-radius: 40rpx;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			font-size: 28rpx;
+
+			.bind-icon {
+				margin-right: 10rpx;
+				font-size: 32rpx;
+			}
+		}
+	}
+
+	.all-bind-list {
+		margin-bottom: 30rpx;
+
+		.loading,
+		.empty {
+			text-align: center;
+			padding: 100rpx 0;
+			color: #999;
+			font-size: 28rpx;
+		}
+	}
+
+	.bind-item {
+		background: white;
+		border-radius: 16rpx;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+		box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+
+		.bind-info {
+			flex: 1;
+
+			.user-account {
+				display: block;
+				font-size: 32rpx;
+				font-weight: bold;
+				color: #333;
+				margin-bottom: 10rpx;
+			}
+
+			.room-id {
+				display: block;
+				font-size: 24rpx;
+				color: #666;
+			}
+		}
+
+		.bind-actions {
+			display: flex;
+			gap: 20rpx;
+
+			.action-btn {
+				padding: 12rpx 24rpx;
+				border-radius: 20rpx;
+				font-size: 24rpx;
+				border: none;
+
+				&.delete-btn {
+					background-color: #ff3b30;
+					color: white;
+				}
+			}
+		}
+	}
+
+	.user-room-list {
+
+		.loading,
+		.empty {
+			text-align: center;
+			padding: 100rpx 0;
+			color: #999;
+			font-size: 28rpx;
+		}
+	}
+
+	.user-room-item {
+		background: white;
+		border-radius: 16rpx;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+		box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+
+		.room-info {
+			flex: 1;
+
+			.room-name {
+				display: block;
+				font-size: 32rpx;
+				font-weight: bold;
+				color: #333;
+				margin-bottom: 10rpx;
+			}
+
+			.room-url {
+				display: block;
+				font-size: 24rpx;
+				color: #666;
+				word-break: break-all;
+				margin-bottom: 10rpx;
+			}
+
+			.bind-time {
+				display: block;
+				font-size: 22rpx;
+				color: #999;
+			}
+		}
+
+		.room-actions {
+			display: flex;
+			gap: 20rpx;
+
+			.action-btn {
+				padding: 12rpx 24rpx;
+				border-radius: 20rpx;
+				font-size: 24rpx;
+				border: none;
+
+				&.delete-btn {
+					background-color: #ff3b30;
+					color: white;
+				}
+			}
+		}
+	}
+
+	.popup-content {
+		width: 600rpx;
+		background: white;
+		border-radius: 20rpx;
+		overflow: hidden;
+
+		.popup-header {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			padding: 30rpx;
+			border-bottom: 1rpx solid #eee;
+
+			.popup-title {
+				font-size: 32rpx;
+				font-weight: bold;
+				color: #333;
+			}
+
+			.close-btn {
+				font-size: 40rpx;
+				color: #999;
+				width: 60rpx;
+				height: 60rpx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+			}
+		}
+
+		.popup-body {
+			padding: 30rpx;
+
+			.form-item {
+				margin-bottom: 30rpx;
+
+				.label {
+					display: block;
+					font-size: 28rpx;
+					color: #333;
+					margin-bottom: 15rpx;
+				}
+
+				.picker-view {
+					width: 100%;
+					height: 80rpx;
+					border: 1rpx solid #ddd;
+					border-radius: 8rpx;
+					padding: 0 20rpx;
+					display: flex;
+					align-items: center;
+					justify-content: space-between;
+					box-sizing: border-box;
+
+					.placeholder {
+						color: #999;
+					}
+
+					.picker-arrow {
+						color: #999;
+						font-size: 24rpx;
+					}
+				}
+			}
+		}
+
+		.popup-footer {
+			display: flex;
+			border-top: 1rpx solid #eee;
+
+			button {
+				flex: 1;
+				height: 100rpx;
+				border: none;
+				font-size: 28rpx;
+
+				&.cancel-btn {
+					background-color: #f5f5f5;
+					color: #666;
+				}
+
+				&.confirm-btn {
+					background-color: #007aff;
+					color: white;
+				}
+			}
+		}
+	}
+
+	.delete-popup {
+		.delete-content {
+			width: 500rpx;
+			background: white;
+			border-radius: 20rpx;
+			padding: 40rpx;
+			text-align: center;
+
+			.delete-title {
+				display: block;
+				font-size: 32rpx;
+				font-weight: bold;
+				color: #333;
+				margin-bottom: 20rpx;
+			}
+
+			.delete-message {
+				display: block;
+				font-size: 28rpx;
+				color: #666;
+				margin-bottom: 40rpx;
+				line-height: 1.5;
+			}
+
+			.delete-actions {
+				display: flex;
+				gap: 20rpx;
+
+				button {
+					flex: 1;
+					height: 80rpx;
+					border-radius: 40rpx;
+					font-size: 28rpx;
+					border: none;
+
+					&.cancel-btn {
+						background-color: #f5f5f5;
+						color: #666;
+					}
+
+					&.delete-confirm-btn {
+						background-color: #ff3b30;
+						color: white;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 107 - 0
pages/work/homesysSetting/userList.vue

@@ -0,0 +1,107 @@
+<template>
+	<page-meta root-font-size="system" />
+	<view class="contact_container">
+		<uv-index-list :index-list="indexList">
+			<template v-for="(items, index) in itemArr" :key="index">
+				<uv-index-item>
+					<uv-index-anchor :text="indexList[index]" size="16"></uv-index-anchor>
+					<view class="chat_list">
+						<uni-list :border="true">
+							<uni-list-chat v-for="(item, index) in items" @click="clickChat(item)" :title="item.name"
+								:avatar="item.avatar || config.baseUrlPre+config.defaultAvatarPath" :clickable="true"
+								:avatar-circle="true" :key="index">
+								<view class="chat-custom-right">
+									<text class="chat-custom-text">{{ item.dept }}</text>
+								</view>
+							</uni-list-chat>
+						</uni-list>
+					</view>
+				</uv-index-item>
+			</template>
+		</uv-index-list>
+	</view>
+</template>
+
+<script setup lang="ts">
+import { onMounted, ref } from 'vue'
+import $tab from '@/plugins/tab.js'
+import { getContactAllUser } from '@/api/contacts.js'
+import { useUserStore } from '@/store/user.js'
+import config from '@/config';
+const userStore = useUserStore()
+
+onMounted(() => {
+	// 获取 通讯录 索引列表
+	let unitId = userStore.user.unitId
+	if ( unitId === 0 ) unitId = userStore.user.groupid
+	getContactAllUser(unitId).then(res => {
+		getContactList(res.returnParams)
+	})
+})
+
+// 索引列表
+const indexList = ref([])
+const itemArr = ref([])
+function getContactList(data) {
+	const filtered = {}
+	Object.keys(data).forEach((key) => {
+		const users = (data[key] || []).filter((user) => user.userState !== '0' && user.name.slice(-3) !== '管理员')
+		if (users.length > 0) {
+			filtered[key] = users
+		}
+	})
+	indexList.value = Object.keys(filtered)
+	itemArr.value = Object.values(filtered)
+}
+// 点击通讯录列表
+function clickChat(e) {
+	$tab.navigateTo('/pages/work/homesysSetting/user?userName=' + e.userName)
+}
+</script>
+
+<style lang="scss">
+::v-deep .contact_container {
+	.uv-index-list {
+		.uv-index-list__letter {
+			.uv-index-list__letter__item {
+				width: calc(16px + .5*(1rem - 16px)) !important;
+				height: calc(16px + .5*(1rem - 16px)) !important;
+			}
+		}
+		.uv-index-item {
+			.uv-index-anchor {
+				height: calc(32px + .5*(1rem - 16px)) !important;
+				.uv-index-anchor__text {
+					font-size: calc(1.5rem + 0px) !important;
+				}
+			}
+			.chat_list {
+				.uni-list-chat {
+					.uni-list-chat__header-warp {
+						.uni-list-chat__header {
+							width: calc(45px + .5*(1rem - 16px)) !important;
+							height: calc(45px + .5*(1rem - 16px)) !important;
+						}
+					}
+					.uni-list-chat__content {
+						.uni-list-chat__content-title {
+							font-size: calc(1rem + 0px) !important;
+						}
+						.uni-list-chat__content-note {
+							font-size: calc(0.75rem + 0px) !important;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	.chat_list {
+		.chat-custom-right {
+			.chat-custom-text {
+				margin-right: 10px;
+			}
+		}
+	}
+}
+</style>

+ 59 - 114
pages/work/index.vue

@@ -72,11 +72,30 @@
 		<uni-section title="房屋管家" titleFontSize="1.3rem" v-if="homesysShow" type="line"></uni-section>
 		<view class="grid-body" v-if="homesysShow">
 			<uni-grid :column="uni_grid_column" :showBorder="false" @change="changeHomesys">
-				<uni-grid-item v-for="(code, index) in homesysInfo.homeCode" :index="index" v-if="homesysShow" :key="index">
+				<uni-grid-item v-for="(home, index) in homesysList" :index="index" v-if="homesysShow" :key="index">
 					<view class="grid-item-box">
-						<!-- <uni-icons type="home-filled" size="30" color="#66ccff" class="icon_size"></uni-icons> -->
 						<text class="ygoa_work_icon icon-zhinengjiaju"></text>
-						<text class="text">{{ roomAliases[code] || code }}</text>
+						<text class="text">
+							{{ home.room_name || home.room_url.split('/').pop() }}
+						</text>
+					</view>
+				</uni-grid-item>
+				<uni-grid-item :index="homesysList.length + 1" v-if="userStore.user.userName == 'yzadmin'">
+					<view class="grid-item-box">
+						<text class="ygoa_work_icon icon-zhinengjiaju"></text>
+						<text class="text">房屋管理</text>
+					</view>
+				</uni-grid-item>
+				<!-- <uni-grid-item :index="homesysList.length + 2" v-if="userStore.user.userName == 'yzadmin'">
+					<view class="grid-item-box">
+						<text class="ygoa_work_icon icon-zhinengjiaju"></text>
+						<text class="text">用户管理</text>
+					</view>
+				</uni-grid-item> -->
+				<uni-grid-item :index="homesysList.length + 3" v-if="userStore.user.userName == 'yzadmin'">
+					<view class="grid-item-box">
+						<text class="ygoa_work_icon icon-zhinengjiaju"></text>
+						<text class="text">用户管理</text>
 					</view>
 				</uni-grid-item>
 			</uni-grid>
@@ -99,7 +118,7 @@ import cuiCalculator from "@/components/cui-calculator/cui-calculator.vue"
 import { onMounted, ref } from "vue"
 import $tab from "@/plugins/tab.js"
 import { useUserStore } from '@/store/user.js'
-import { getProcessList } from '@/api/work.js'
+import { getProcessList, roomUserLogin, getUserRoomByUser } from '@/api/work.js'
 import { clearCache } from '@/utils/ygoa.js'
 import { onShow } from '@dcloudio/uni-app'
 import config from '@/config.js';
@@ -115,117 +134,47 @@ if(config.companyCode && config.companyCode == 'yg'){
 	diaryShow.value = false;
 }
 const homesysShow = ref(false)
-const homesysList = [
-	{
-		name: '测试',
-		username: 'yzadmin',
-		homeCode: [103]
-	},
-	{
-		name: '林明东',
-		username: 'linmd',
-		homeCode: [203, 204, 303, 306, 310]
-	},
-	{
-		name: '何总',
-		username: 'hez',
-		homeCode: [302, 303, 306, 310, 204]
-	},
-	{
-		name: '黄林葳',
-		username: 'huanglw',
-		homeCode: [303, 305, 306, 310, 204]
-	},
-	{
-		name: '余煌',
-		username: 'yuh',
-		homeCode: [202]
-	},
-	{
-		name: '周颖',
-		username: 'zhouy',
-		homeCode: [202]
-	},
-	{
-		name: '谭艳',
-		username: 'tany',
-		homeCode: [101, 204, 208, 303, 306, 310]
-	},
-	{
-		name: '林镇堃',
-		username: 'linzk',
-		homeCode: [106, 107]
-	},
-	{
-		name: '陈雪梅',
-		username: 'chenxm',
-		homeCode: [106, 107]
-	},
-	{
-		name: '陈小沛',
-		username: 'chenxp',
-		homeCode: [301]
-	},
-	{
-		name: '温永志',
-		username: '',
-		homeCode: [102]
-	},
-	{
-		name: '苏总',
-		username: '',
-		homeCode: [209]
-	},
-	{
-		name: '黄铭',
-		username: 'huangm',
-		homeCode: [303, 304, 306, 310]
-	},
-	{
-		name: '黄德胜',
-		username: 'huangds',
-		homeCode: [103]
-	},
-	{
-		name: '黄蓉',
-		username: 'huangr',
-		homeCode: [103]
-	},
-	{
-		name: '潘福雁',
-		username: 'panfy',
-		homeCode: []
-	},
-	{
-		name: '沈宇红',
-		username: 'shenyh',
-		homeCode: [106, 107, 204, 208, 303, 306, 310]
-	},
-	{
-		name: '余总',
-		username: '',
-		homeCode: [207, 204, 208, 303, 306, 310]
-	}
-]
-
-// 房屋别名映射
-const roomAliases = {
-	204: '二楼会议室',
-	208: '休息室',
-	303: '贵宾室',
-	306: '三楼茶室',
-	310: '三楼会议室'
+const homesysList = ref([])
+function getUserRoom() {
+	getUserRoomByUser(userStore.user.userName).then((res) => {
+		console.log('getUserRoomByUser', res)
+		const hasRoom = res.length != 0
+		if (hasRoom) {
+			homesysList.value = res
+			homesysShow.value = true
+			console.log('getUserRoomByUser', homesysList.value)
+		}
+	})
+}
+// 点击房屋管家
+function changeHomesys(e) { 
+	const roomIndex = e.detail.index
+	const roomListLength = homesysList.value.length
+	// if (roomIndex > roomListLength) {
+		switch (roomIndex) {
+			case roomListLength + 1: 
+				$tab.navigateTo('/pages/work/homesysSetting/index')
+				break
+			case roomListLength + 2: 
+				$tab.navigateTo('/pages/work/homesysSetting/user')
+				break
+			case roomListLength + 3: 
+				$tab.navigateTo('/pages/work/homesysSetting/userList')
+				break
+			default:
+				$tab.navigateTo('/pages/work/homesys/index?homeCode=' + homesysList.value[roomIndex].room_url)
+		}
+	// } else {
+	// 	$tab.navigateTo('/pages/work/homesys/index?homeCode=' + homesysList.value[roomIndex].room_url)
+	// }
 }
-const homesysInfo = ref({})
+onMounted(() => {
+	getUserRoom()
+})
 onMounted(() => {
 	sysFontSize = uni.getAppBaseInfo().fontSizeSetting
 	if (sysFontSize > 20) uni_grid_column.value = 3
 	initProcessList()
-	const homesysInfoIndex = homesysList.findIndex(({username}) => userStore.user.userName == username)
-	if (homesysInfoIndex != -1) {
-		homesysShow.value = true
-		homesysInfo.value = homesysList[homesysInfoIndex]
-	}
 })
 
 onShow(()=>{
@@ -295,10 +244,6 @@ function changeToolsGrid(e) {
 		default:
 	}
 }
-// 点击房屋管家
-function changeHomesys(e) { 
-	$tab.navigateTo('/pages/work/homesys/index?homeCode=' + homesysInfo.value.homeCode[e.detail.index])
-}
 // 计算器
 const calculatorPopup = ref(null)
 function openCalculatorPopup() { // 打开计算器弹出层

+ 1 - 1
utils/request.js

@@ -85,7 +85,7 @@ const request = config => {
       } else if (code === 500) {
         toast(msg)
         reject('500')
-      } else if (code !== 200) {
+      } else if (code !== 200 && code !== 201 &&  code !== 204) {
         toast(msg)
         reject(code)
       }