提交 a978aa7d 作者: 方治民

Merge branch 'dev' of https://gitlab.yiring.com/digital-agri/agri-app into dev

import { otherHttp } from '/@/utils/http/axios' import { otherHttp } from '/@/utils/http/axios'
import { postFormData } from '/@/utils/http/formDataRequest'
enum Api { enum Api {
zoneList = '/online/cgform/api/getData/01fd687ecb164aea914e92047e144d66', // 功能菜单数据 zoneList = '/online/cgform/api/getData/01fd687ecb164aea914e92047e144d66', // 功能菜单数据
...@@ -9,6 +10,9 @@ enum Api { ...@@ -9,6 +10,9 @@ enum Api {
commonToolsList = '/online/cgform/api/getData/3a7fbb877f304b7d83935caa454859c4', // 常用工具数据 commonToolsList = '/online/cgform/api/getData/3a7fbb877f304b7d83935caa454859c4', // 常用工具数据
getFarmBaseList = '/farmbase/getFarmBaseList', getFarmBaseList = '/farmbase/getFarmBaseList',
} }
export function queryByType(data = {}) {
return postFormData('/device/queryByType', data).then((res) => res.data)
}
/** /**
* 删除设备 * 删除设备
* @param id * @param id
......
...@@ -546,24 +546,11 @@ ...@@ -546,24 +546,11 @@
{ {
"path": "pages/device/device", "path": "pages/device/device",
"style": { "style": {
"navigationBarTitleText": "物联设备", "navigationBarTitleText": "基地设备",
"enablePullDownRefresh": false, "enablePullDownRefresh": false,
"navigationBarBackgroundColor": "#5DB66F", "navigationBarBackgroundColor": "#5DB66F",
"navigationBarTextStyle": "white", "navigationBarTextStyle": "white",
"backgroundColorBottom": "#F2F2F2", "backgroundColorBottom": "#F2F2F2"
"app-plus": {
"titleNView": {
"buttons": [
{
"text": "+ 添加设备",
"fontSrc": "/static/uni.ttf",
"color": "#fff",
"fontSize": "26rpx",
"width": "auto"
}
]
}
}
} }
}, },
{ {
......
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue' import { computed, reactive, ref, watch } from 'vue'
import { useDictStore } from '@/store/modules/dict' import { useDictStore } from '@/store/modules/dict'
import * as NongchangAPI from '@/api/model/nongchang' import * as NongchangAPI from '@/api/model/nongchang'
// 定义Props // 定义Props
interface Props { interface Props {
show: boolean show: boolean
editData?: any editData?: any
farmId:any, farmId: any
farmBaseId:any, farmBaseId: any
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
show: false, show: false,
editData: null, editData: null,
farmId:0, farmId: 0,
farmBaseId:0, farmBaseId: 0,
})
})
// 定义Emits
// 定义Emits const emit = defineEmits<{
const emit = defineEmits<{ 'update:show': [value: boolean]
'update:show': [value: boolean] submit: [data: any]
submit: [data: any] close: []
close: [] }>()
}>()
// 表单引用
// 表单引用 const formRef = ref()
const formRef = ref() const loading = ref(false)
const loading = ref(false) const showDeviceTypePicker = ref(false)
const showDeviceTypePicker = ref(false)
// 字典存储
// 字典存储 const dictStore = useDictStore()
const dictStore = useDictStore()
// 表单数据
// 表单数据 const formData = reactive({
const formData = reactive({ deviceName: '',
deviceName: '', deviceType: '',
deviceType: '', deviceTypeText: '',
deviceTypeText: '', deviceIdentifier: '',
deviceIdentifier: '', })
})
// 设备类型选项 - 使用字典数据
// 设备类型选项 - 使用字典数据 const deviceTypeOptions = computed(() => {
const deviceTypeOptions = computed(() => { return (
return ( dictStore.getDictList.deviceType?.map((item: any) => ({
dictStore.getDictList.deviceType?.map((item: any) => ({ value: item.value,
value: item.value, text: item.text,
text: item.text, })) || []
})) || [] )
) })
})
// 设备类型选择器数据 (不再需要,因为fui-picker使用options属性)
// 设备类型选择器数据 (不再需要,因为fui-picker使用options属性)
// 计算属性
// 计算属性 const dialogTitle = computed(() => {
const dialogTitle = computed(() => { return props.editData ? '编辑设备' : '添加设备'
return props.editData ? '编辑设备' : '添加设备' })
})
const submitButtonText = computed(() => {
const submitButtonText = computed(() => { return props.editData ? '保存' : '确认'
return props.editData ? '保存' : '确认' })
})
// 表单验证规则
// 表单验证规则 const rules = {
const rules = { deviceName: [
deviceName: [ { required: true, message: '请输入设备名称', trigger: 'blur' },
{ required: true, message: '请输入设备名称', trigger: 'blur' }, { min: 2, max: 50, message: '设备名称长度在2-50个字符之间', trigger: 'blur' },
{ min: 2, max: 50, message: '设备名称长度在2-50个字符之间', trigger: 'blur' }, ],
], deviceType: [{ required: true, message: '请选择设备类型', type: 'number', trigger: 'change' }],
deviceType: [{ required: true, message: '请选择设备类型', type: 'number', trigger: 'change' }], deviceIdentifier: [
deviceIdentifier: [ { required: true, message: '请输入设备唯一标识', trigger: 'blur' },
{ required: true, message: '请输入设备唯一标识', trigger: 'blur' }, { min: 6, max: 100, message: '设备标识长度在6-100个字符之间', trigger: 'blur' },
{ min: 6, max: 100, message: '设备标识长度在6-100个字符之间', trigger: 'blur' }, {
{ validator: (rule: any, value: any, callback: any) => {
validator: (rule: any, value: any, callback: any) => { if (!/^[a-zA-Z0-9\-_:]+$/.test(value)) {
if (!/^[a-zA-Z0-9\-_:]+$/.test(value)) { callback(new Error('设备标识只能包含字母、数字、下划线、中划线和冒号'))
callback(new Error('设备标识只能包含字母、数字、下划线、中划线和冒号')) } else {
} else { callback()
callback() }
}
},
trigger: 'blur',
}, },
], trigger: 'blur',
}
// 监听显示状态
watch(
() => props.show,
(newVal) => {
if (newVal && props.editData) {
// 编辑模式,填充数据
resetFormData()
loadEditData()
} else if (newVal) {
// 添加模式,重置表单
resetFormData()
}
}, },
) ],
}
// 重置表单数据
function resetFormData() { // 监听显示状态
formData.deviceName = '' watch(
formData.deviceType = '' () => props.show,
formData.deviceTypeText = '' (newVal) => {
formData.deviceIdentifier = '' if (newVal && props.editData) {
// 编辑模式,填充数据
// 清除验证状态 resetFormData()
if (formRef.value) { loadEditData()
formRef.value.resetFields() } else if (newVal) {
// 添加模式,重置表单
resetFormData()
} }
},
)
// 重置表单数据
function resetFormData() {
formData.deviceName = ''
formData.deviceType = ''
formData.deviceTypeText = ''
formData.deviceIdentifier = ''
// 清除验证状态
if (formRef.value) {
formRef.value.resetFields()
} }
}
// 加载编辑数据 // 加载编辑数据
function loadEditData() { function loadEditData() {
if (!props.editData) if (!props.editData) return
return
formData.deviceName = props.editData.deviceName || ''
formData.deviceName = props.editData.deviceName || '' formData.deviceType = props.editData.deviceType || ''
formData.deviceType = props.editData.deviceType || '' formData.deviceIdentifier = props.editData.deviceIdentifier || ''
formData.deviceIdentifier = props.editData.deviceIdentifier || ''
// 设置设备类型文本
// 设置设备类型文本 if (props.editData.deviceType) {
if (props.editData.deviceType) { const typeOption = deviceTypeOptions.value.find((item) => item.value === props.editData.deviceType)
const typeOption = deviceTypeOptions.value.find((item) => item.value === props.editData.deviceType) if (typeOption) {
if (typeOption) { formData.deviceTypeText = typeOption.text
formData.deviceTypeText = typeOption.text
} else {
// 如果字典中没有找到对应的文本,使用原始数据中的文本
formData.deviceTypeText = props.editData.deviceType_dictText || ''
}
} else { } else {
formData.deviceTypeText = '' // 如果字典中没有找到对应的文本,使用原始数据中的文本
formData.deviceTypeText = props.editData.deviceType_dictText || ''
} }
} else {
formData.deviceTypeText = ''
} }
}
// 设备类型选择确认
function handleDeviceTypeConfirm(e: any) {
formData.deviceType = e.value
formData.deviceTypeText = e.text
showDeviceTypePicker.value = false
}
// 提交表单
async function handleSubmit() {
try {
console.log(formData)
// 先进行表单验证
const valid = await formRef.value.validate()
if (!valid) {
return
}
// 设备类型选择确认 loading.value = true
function handleDeviceTypeConfirm(e: any) {
formData.deviceType = e.value
formData.deviceTypeText = e.text
showDeviceTypePicker.value = false
}
// 提交表单
async function handleSubmit() {
try {
console.log(formData)
// 先进行表单验证
const valid = await formRef.value.validate()
if (!valid) {
return
}
loading.value = true
// 准备提交数据
const submitData = {
...formData,
farmId: props.farmId,
farmBaseId: props.farmBaseId,
}
// 根据 editData 判断是新增还是修改 // 准备提交数据
let result const submitData = {
if (props.editData && props.editData.id) { ...formData,
// 编辑设备,需要传递设备ID farmId: props.farmId,
result = await NongchangAPI.editDevice({ farmBaseId: props.farmBaseId,
...submitData, }
id: props.editData.id,
})
console.log('修改设备数据:', submitData)
} else {
// 新增设备
result = await NongchangAPI.addDevice(submitData)
console.log('新增设备数据:', submitData)
}
uni.showToast({ title: '操作成功', icon: 'success' }) // 根据 editData 判断是新增还是修改
emit('update:show', false) let result
emit('submitSuccess') if (props.editData && props.editData.id) {
emit('close') // 编辑设备,需要传递设备ID
} catch (error) { result = await NongchangAPI.editDevice({
console.log('提交失败:', error) ...submitData,
uni.showToast({ id: props.editData.id,
title: '操作失败',
icon: 'none',
duration: 2000,
}) })
} finally { console.log('修改设备数据:', submitData)
loading.value = false } else {
// 新增设备
result = await NongchangAPI.addDevice(submitData)
console.log('新增设备数据:', submitData)
} }
}
// 关闭弹窗 uni.showToast({ title: '操作成功', icon: 'success' })
function handleClose() {
emit('update:show', false) emit('update:show', false)
emit('submitSuccess')
emit('close') emit('close')
} catch (error) {
console.log('提交失败:', error)
uni.showToast({
title: '操作失败',
icon: 'none',
duration: 2000,
})
} finally {
loading.value = false
} }
}
// 暴露方法给父组件
defineExpose({ // 关闭弹窗
resetFormData, function handleClose() {
setLoading: (value: boolean) => { emit('update:show', false)
loading.value = value emit('close')
}, }
})
// 暴露方法给父组件
defineExpose({
resetFormData,
setLoading: (value: boolean) => {
loading.value = value
},
})
</script> </script>
<template> <template>
...@@ -271,143 +269,143 @@ return ...@@ -271,143 +269,143 @@ return
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.dialog-content { .dialog-content {
padding: 10rpx 30rpx; padding: 10rpx 30rpx;
width: 90%; width: 90%;
max-height: 80vh; max-height: 80vh;
overflow-y: auto; overflow-y: auto;
} }
.section-title { .section-title {
font-size: 32rpx; font-size: 32rpx;
font-weight: 600; font-weight: 600;
color: #333; color: #333;
margin: 20rpx 0 30rpx 0; margin: 20rpx 0 30rpx 0;
padding-left: 20rpx; padding-left: 20rpx;
border-left: 6rpx solid #5db66f; border-left: 6rpx solid #5db66f;
} }
.dialog-buttons { .dialog-buttons {
display: flex; display: flex;
gap: 20rpx; gap: 20rpx;
margin-top: 40rpx; margin-top: 40rpx;
.submit-btn { .submit-btn {
flex: 1; flex: 1;
::v-deep .u-button { ::v-deep .u-button {
background-color: #5db66f; background-color: #5db66f;
border-color: #5db66f; border-color: #5db66f;
}
} }
}
.cancel-btn { .cancel-btn {
flex: 1; flex: 1;
::v-deep .u-button { ::v-deep .u-button {
background-color: #fff; background-color: #fff;
color: #666; color: #666;
border: 2rpx solid #dcdfe6; border: 2rpx solid #dcdfe6;
}
} }
} }
}
// uview-plus 表单样式调整 // uview-plus 表单样式调整
::v-deep .u-form-item { ::v-deep .u-form-item {
margin-bottom: 40rpx; margin-bottom: 40rpx;
.u-form-item__body {
padding: 0;
align-items: flex-start;
}
.u-form-item__body__left__content__label { .u-form-item__body {
font-size: 28rpx; padding: 0;
color: #333; align-items: flex-start;
font-weight: 500; }
width: 180rpx;
flex-shrink: 0;
text-align: right;
padding-right: 20rpx;
line-height: 80rpx;
}
.u-form-item__body__right { .u-form-item__body__left__content__label {
flex: 1; font-size: 28rpx;
min-width: 0; color: #333;
} font-weight: 500;
width: 180rpx;
flex-shrink: 0;
text-align: right;
padding-right: 20rpx;
line-height: 80rpx;
} }
.address-display {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12rpx 18rpx;
background-color: transparent;
border-bottom: 2rpx solid #e4e7ed;
border-radius: 0;
cursor: pointer;
width: 100%;
text {
flex: 1;
font-size: 28rpx;
color: #333;
}
.placeholder-text { .u-form-item__body__right {
color: #c0c4cc; flex: 1;
} min-width: 0;
}
}
.address-display {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12rpx 18rpx;
background-color: transparent;
border-bottom: 2rpx solid #e4e7ed;
border-radius: 0;
cursor: pointer;
width: 100%;
text {
flex: 1;
font-size: 28rpx;
color: #333;
} }
::v-deep .u-form-item__body__left__content__required { .placeholder-text {
position: static; color: #c0c4cc;
padding-right: 10rpx;
} }
}
// 输入框样式优化 ::v-deep .u-form-item__body__left__content__required {
::v-deep .u-input { position: static;
.u-input__content { padding-right: 10rpx;
padding: 0; }
.u-input__content__field-wrapper { // 输入框样式优化
border: none; ::v-deep .u-input {
border-radius: 0; .u-input__content {
padding: 0;
.u-input__content__field-wrapper__field { .u-input__content__field-wrapper {
font-size: 28rpx; border: none;
height: auto; border-radius: 0;
}
.u-input__content__field-wrapper__placeholder { .u-input__content__field-wrapper__field {
color: #c0c4cc; font-size: 28rpx;
font-size: 28rpx; height: auto;
}
} }
&.u-input--focus { .u-input__content__field-wrapper__placeholder {
.u-input__content__field-wrapper { color: #c0c4cc;
border-bottom-color: #5db66f; font-size: 28rpx;
}
} }
} }
}
// 模态框样式调整 &.u-input--focus {
::v-deep .u-modal { .u-input__content__field-wrapper {
.u-modal__content { border-bottom-color: #5db66f;
border-radius: 20rpx; }
padding: 20rpx 0rpx;
} }
}
}
.u-modal__header { // 模态框样式调整
border-bottom: 2rpx solid #f0f0f0; ::v-deep .u-modal {
padding: 30rpx; .u-modal__content {
border-radius: 20rpx;
padding: 20rpx 0rpx;
}
.u-modal__header__title { .u-modal__header {
font-size: 32rpx; border-bottom: 2rpx solid #f0f0f0;
font-weight: 600; padding: 30rpx;
color: #333;
} .u-modal__header__title {
font-size: 32rpx;
font-weight: 600;
color: #333;
} }
} }
}
</style> </style>
<script setup> <script setup>
import { reactive, ref } from 'vue' import { reactive, ref, computed } from 'vue'
import { onNavigationBarButtonTap } from '@dcloudio/uni-app' import { onLoad, onNavigationBarButtonTap } from '@dcloudio/uni-app'
import SaveDialog from './components/save-dialog.vue' import SaveDialog from './components/save-dialog.vue'
import * as NongchangAPI from '@/api/model/nongchang' import * as NongchangAPI from '@/api/model/nongchang'
import { useDictStore } from '@/store/modules/dict'
const isOnePage = ref(true) import { merge } from 'lodash-es'
const paging = ref(null)
const pageData = reactive({ const dictStore = useDictStore()
param: { const deviceType = ref('') // 设备类型参数
pageNo: 1, const pageData = reactive({
pageSize: 10, param: {
deviceName: '', deviceName: '',
}, deviceType: '', // 添加设备类型筛选
list: [], farmBaseId: '', //基地id
}) },
list: [],
// 滑动操作选项 selectedCategory: 'all',
const swipeOptions = [ selectedDevice: {},
{ })
text: '编辑',
style: { // 摄像头设备的分类标签
backgroundColor: 'var(--fui-color-primary)', const cameraCategories = computed(() => {
}, const dictList =
}, dictStore.getDictList.deviceType?.map((item) => ({
{ id: item.value,
text: '删除', name: item.text,
style: { })) || []
backgroundColor: 'var(--fui-color-danger)', return [{ id: 'all', name: '全部' }, ...dictList]
}, })
},
] onLoad((options) => {
function getList() { // 获取设备类型参数
if (!paging.value) return if (options.deviceType) {
NongchangAPI.getDeviceList(pageData.param) deviceType.value = options.deviceType
.then((res) => { pageData.selectedCategory = options.deviceType
pageData.total = res.total pageData.param.deviceType = options.deviceType
paging.value.complete(res.records) pageData.param.farmBaseId = options.farmBaseId
})
.catch(() => {
paging.value.complete(false)
})
} }
function queryList(pageNo, pageSize) { getList()
pageData.param.pageNo = pageNo })
pageData.param.pageSize = pageSize // 判断是否为摄像头设备
getList() const isCameraDevice = computed(() => {
return deviceType.value == '1'
})
// 获取设备列表
function getList() {
const formData = {
deviceName: pageData.param.deviceName,
deviceType: pageData.param.deviceType,
farmBaseId: pageData.param.farmBaseId,
} }
function handleSearch() { NongchangAPI.queryByType(formData)
// 重置页码为1,重新搜索 .then((res) => {
pageData.param.pageNo = 1 pageData.list = res.result || []
if (paging.value) { if (res.result.length > 0) handleDeviceClick(res.result[0])
paging.value.reload()
}
}
const showDialog = ref(false)
const currentEditData = ref(null)
onNavigationBarButtonTap((_) => {
showAddDialog()
})
function showAddDialog() {
currentEditData.value = null
showDialog.value = true
}
function showEditDialog(device) {
currentEditData.value = device
showDialog.value = true
}
function handleSubmitSuccess() {
// 提交成功后刷新列表
handleSearch()
}
function handleDialogClose() {
// 弹窗关闭后的处理逻辑
}
// 删除设备
async function handleDelete(id) {
try {
await uni.showModal({
title: '确认删除',
content: '确定要删除这个设备吗?',
success: async (res) => {
if (res.confirm) {
await NongchangAPI.delDevice(id)
uni.showToast({ title: '删除成功', icon: 'success' })
handleSearch()
}
},
})
} catch (error) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
// 获取状态样式类
function getStatusClass(status) {
if (status === '已连接')
return 'status-connected'
if (status === '未连接')
return 'status-disconnected'
return 'status-unknown'
}
// 处理滑动打开事件
function handleSwipeOpen(index) {
// 关闭其他已打开的滑动单元格,确保同时只有一个处于打开状态
pageData.list.forEach((item, i) => {
item.isShow = i === index
}) })
.catch(() => {
pageData.list = []
})
}
function handleSearch() {
// 搜索时重新获取列表
getList()
}
const showDialog = ref(false)
const currentEditData = ref(null)
onNavigationBarButtonTap((_) => {
showAddDialog()
})
function showAddDialog() {
currentEditData.value = null
showDialog.value = true
}
function handleSubmitSuccess() {
// 提交成功后刷新列表
getList()
}
function handleDialogClose() {
// 弹窗关闭后的处理逻辑
}
// 获取状态样式类
function getStatusClass(status) {
if (status === '已连接') return 'status-connected'
if (status === '未连接') return 'status-disconnected'
return 'status-unknown'
}
// 分类标签点击
function handleCategoryClick(category) {
pageData.selectedCategory = category.id
if (category.id === 'all') {
pageData.param.deviceType = ''
} else {
pageData.param.deviceType = category.id
} }
getList()
// 处理滑动操作 }
function handleSwipeAction(e, device) {
const index = e.index // 设备卡片点击
const position = e.position function handleDeviceClick(device) {
if (position === 'right') { pageData.selectedDevice = device
if (index === 0) { deviceType.value = device.deviceType
// 编辑操作 }
showEditDialog(device)
} else if (index === 1) {
// 删除操作
handleDelete(device.id)
}
}
}
</script> </script>
<template> <template>
<view class="codefun-flex-col page"> <view class="codefun-flex-col page">
<z-paging ref="paging" v-model="pageData.list" @query="queryList"> <!-- 顶部占位区域 - 根据设备类型显示 -->
<view class="codefun-flex-col group_3"> <!-- 摄像头设备 - 视频占位区域 -->
<view class="codefun-flex-row codefun-items-center section_2"> <view v-if="isCameraDevice" class="video-placeholder-top">
<image class="image_6" src="/static/images/codefun/6c5c5a3c082b8c60a307d3a7caee623c.png" /> <view class="video-overlay">
<u-input <view class="video-info">
v-model="pageData.param.deviceName" <text class="video-location">{{ pageData.selectedDevice.deviceName }}</text>
placeholder="请输入设备名称搜索" <!-- <text class="video-time">2022年11月01日 星期二 14:53:24</text> -->
border="none"
class="codefun-ml-8"
@confirm="handleSearch"
/>
</view> </view>
<image class="fullscreen-icon" src="/static/images/device/fullscreen.png" mode="aspectFit" />
</view>
</view>
<!-- 设备列表 --> <!-- 其他设备 - 设备信息卡片 -->
<view class="device-list"> <view v-else class="device-info-card">
<uni-swipe-action> <view class="device-header">
<uni-swipe-action-item <view class="device-icon">
v-for="(item, index) in pageData.list" <image class="icon-img" src="/static/images/device/location-icon.png" mode="aspectFit" />
:key="index" </view>
:right-options="swipeOptions" <view class="device-title-group">
@click="(e) => handleSwipeAction(e, item)" <view class="device-title">{{ pageData.selectedDevice.deviceName }}</view>
> <view class="device-badges">
<view class="device-item"> <text class="badge badge-green" v-if="pageData.selectedDevice.isOnline == 1">在线</text>
<view class="device-info"> <text class="badge badge-gray" v-else>离线</text>
<view class="device-name">{{ item.deviceName }}</view> </view>
<view class="device-details">
<text class="device-type">类型: {{ item.deviceType_dictText || '未知' }}</text>
<text class="device-identifier">标识: {{ item.deviceIdentifier }}</text>
</view>
</view>
<view class="device-status-time">
<text class="device-status" :class="getStatusClass(item.connectStatus)">
{{ item.connectStatus_dictText || '未知' }}
</text>
<view style="opacity: 0">1</view>
<view class="device-time">添加日期: {{ item.createTime }}</view>
</view>
</view>
</uni-swipe-action-item>
</uni-swipe-action>
</view> </view>
</view>
<view class="device-stats">
<view class="stat-item">
<text class="stat-label">设备ID:</text>
<text class="stat-value">{{ pageData.selectedDevice.deviceIdentifier }}</text>
</view>
<!-- <view class="stat-item">
<text class="stat-label">位置坐标:</text>
<text class="stat-value">30.2680, 120.0450</text>
</view> -->
</view>
<view class="device-stats">
<view class="stat-item">
<text class="stat-label">设备来源:</text>
<text class="stat-value">灌溉设备</text>
</view>
<view class="stat-item">
<text class="stat-label">归属地:</text>
<text class="stat-value">西区灌溉基地</text>
</view>
</view>
</view>
<!-- 通用设备列表区域 -->
<view class="codefun-flex-col group_3">
<!-- 搜索框 -->
<view class="codefun-flex-row codefun-items-center section_2">
<image class="image_6" src="/static/images/codefun/6c5c5a3c082b8c60a307d3a7caee623c.png" />
<u-input
v-model="pageData.param.deviceName"
:placeholder="isCameraDevice ? '请输入关键词搜索' : '请输入设备名称搜索'"
border="none"
class="codefun-ml-8"
@confirm="handleSearch"
/>
</view>
<!-- 空状态 --> <!-- 分类标签 -->
<view class="empty-state" v-if="pageData.list.length === 0"> <view class="category-tabs">
<text>暂无设备数据</text> <view
v-for="category in cameraCategories"
:key="category.id"
class="category-tab"
:class="{ active: pageData.selectedCategory === category.id }"
@click="handleCategoryClick(category)"
>
{{ category.name }}
</view> </view>
</view> </view>
</z-paging>
<!-- 设备卡片网格 -->
<view class="device-grid">
<view
v-for="(item, index) in pageData.list"
:key="index"
class="device-card"
:class="{ selected: pageData.selectedDevice.id == item.id }"
@click="handleDeviceClick(item)"
>
<view class="">
<view class="device-name">{{ item.deviceName }}</view>
<!-- <view class="device-status" :class="getStatusClass(item.connectStatus_dictText)">
{{ item.connectStatus_dictText || '未知' }}
</view> -->
</view>
<!-- <view class="device-card-body">
<view class="device-detail">
<text class="detail-label">设备类型:</text>
<text class="detail-value">{{ item.deviceType_dictText || '未知' }}</text>
</view>
<view class="device-detail">
<text class="detail-label">设备编号:</text>
<text class="detail-value">{{ item.deviceIdentifier }}</text>
</view>
</view> -->
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="pageData.list.length === 0">
<text>暂无设备数据</text>
</view>
</view>
<!-- 弹窗组件 --> <!-- 弹窗组件 -->
<SaveDialog <SaveDialog
:show="showDialog" :show="showDialog"
...@@ -192,584 +236,820 @@ return 'status-disconnected' ...@@ -192,584 +236,820 @@ return 'status-disconnected'
</template> </template>
<style lang="scss"> <style lang="scss">
body { body {
background-color: #e6f5e8; background-color: #e6f5e8;
} }
.mt-5 { .mt-5 {
margin-top: 10rpx; margin-top: 10rpx;
} }
.mt-11 { .mt-11 {
margin-top: 22rpx; margin-top: 22rpx;
} }
.ml-5 { .ml-5 {
margin-left: 10rpx; margin-left: 10rpx;
} }
.ml-13 {
margin-left: 26rpx;
}
.ml-9 {
margin-left: 18rpx;
}
.page {
background-color: #e6f5e8;
mix-blend-mode: NOTTHROUGH;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
height: 100%;
.section {
padding: 32rpx 24rpx 32rpx 36rpx;
background-color: #5db66f;
mix-blend-mode: NOTTHROUGH;
.ml-13 { .image {
margin-left: 26rpx; border-radius: 64rpx;
} width: 108rpx;
height: 42rpx;
}
.ml-9 { .group {
margin-left: 18rpx; margin-right: 4rpx;
}
.page { .image_2 {
background-color: #e6f5e8; mix-blend-mode: NOTTHROUGH;
mix-blend-mode: NOTTHROUGH; width: 34rpx;
width: 100%; height: 22rpx;
overflow-y: auto; }
overflow-x: hidden;
height: 100%;
.section {
padding: 32rpx 24rpx 32rpx 36rpx;
background-color: #5db66f;
mix-blend-mode: NOTTHROUGH;
.image { .image_3 {
border-radius: 64rpx; mix-blend-mode: NOTTHROUGH;
width: 108rpx; width: 30rpx;
height: 42rpx; height: 22rpx;
} }
.group { .image_4 {
margin-right: 4rpx; width: 48rpx;
height: 22rpx;
}
}
.image_2 { .group_2 {
mix-blend-mode: NOTTHROUGH; padding-left: 6rpx;
width: 34rpx;
height: 22rpx;
}
.image_3 { .image_5 {
mix-blend-mode: NOTTHROUGH; mix-blend-mode: NOTTHROUGH;
width: 30rpx; width: 14rpx;
height: 22rpx; height: 26rpx;
} }
.image_4 { .pos {
width: 48rpx; position: absolute;
height: 22rpx; left: 6rpx;
} top: 50%;
transform: translateY(-50%);
} }
.group_2 { .text {
padding-left: 6rpx; color: #ffffffe6;
line-height: 29.6rpx;
}
.image_5 { .pos_2 {
mix-blend-mode: NOTTHROUGH; position: absolute;
width: 14rpx; right: 0;
height: 26rpx; top: 50%;
} transform: translateY(-50%);
.pos { .text_2 {
position: absolute; line-height: 25.78rpx;
left: 6rpx;
top: 50%;
transform: translateY(-50%);
} }
}
}
}
.text { .group_3 {
color: #ffffffe6; padding: 28rpx 24rpx 58rpx;
line-height: 29.6rpx;
}
.pos_2 { .section_2 {
position: absolute; padding: 16rpx 20rpx;
right: 0; background-color: #ffffff;
top: 50%; border-radius: 1998rpx;
transform: translateY(-50%); mix-blend-mode: NOTTHROUGH;
.text_2 { .text_3 {
line-height: 25.78rpx; color: #cccccc;
} line-height: 26.02rpx;
}
} }
} }
.group_3 { .section_3 {
padding: 28rpx 24rpx 58rpx; padding: 0 24rpx;
background-color: #ffffff;
border-radius: 18.46rpx;
mix-blend-mode: NOTTHROUGH;
.section_2 { .group_4 {
padding: 16rpx 20rpx; padding: 42rpx 4rpx 30rpx 22rpx;
background-color: #ffffff; border-bottom: solid 2rpx #eeeeee;
border-radius: 1998rpx;
mix-blend-mode: NOTTHROUGH;
.text_3 { .image_7 {
color: #cccccc; width: 100rpx;
line-height: 26.02rpx; height: 86rpx;
} }
}
.section_3 {
padding: 0 24rpx;
background-color: #ffffff;
border-radius: 18.46rpx;
mix-blend-mode: NOTTHROUGH;
.group_4 { .group_7 {
padding: 42rpx 4rpx 30rpx 22rpx; margin-left: 32rpx;
border-bottom: solid 2rpx #eeeeee;
.image_7 { .text_5 {
width: 100rpx; color: #5db66f;
height: 86rpx; font-size: 40rpx;
font-family: SourceHanSansCN;
line-height: 40rpx;
} }
.group_7 { .text_7 {
margin-left: 32rpx; line-height: 22.18rpx;
.text_5 {
color: #5db66f;
font-size: 40rpx;
font-family: SourceHanSansCN;
line-height: 40rpx;
}
.text_7 {
line-height: 22.18rpx;
}
} }
}
.text_4 { .text_4 {
color: #333333; color: #333333;
line-height: 40rpx; line-height: 40rpx;
} }
.group_5 { .group_5 {
width: 219.94rpx; width: 219.94rpx;
.group_6 { .group_6 {
overflow: hidden; overflow: hidden;
.text-wrapper_2 { .text-wrapper_2 {
padding: 8rpx 0 4rpx; padding: 8rpx 0 4rpx;
overflow: hidden; overflow: hidden;
width: 60rpx; width: 60rpx;
height: 40rpx; height: 40rpx;
}
} }
}
.group_8 { .group_8 {
padding-top: 16rpx; padding-top: 16rpx;
.text-wrapper_3 {
padding: 8rpx 0 4rpx;
overflow: hidden;
width: 60rpx;
height: 40rpx;
.text_8 { .text-wrapper_3 {
color: #ff9800;
}
}
}
.text-wrapper {
padding: 8rpx 0 4rpx; padding: 8rpx 0 4rpx;
overflow: hidden; overflow: hidden;
width: 112rpx; width: 60rpx;
height: 40rpx; height: 40rpx;
.text_6 { .text_8 {
line-height: 25.74rpx; color: #ff9800;
} }
} }
} }
}
.group_9 {
padding: 12rpx 4rpx;
.image_8 { .text-wrapper {
margin: 4rpx 0; padding: 8rpx 0 4rpx;
width: 40rpx; overflow: hidden;
width: 112rpx;
height: 40rpx; height: 40rpx;
}
.text-wrapper_4 { .text_6 {
padding: 12rpx 0; line-height: 25.74rpx;
overflow: hidden; }
width: 168rpx;
height: 48rpx;
} }
}
}
.image_9 { .group_9 {
margin-right: 12rpx; padding: 12rpx 4rpx;
width: 16rpx;
height: 26rpx; .image_8 {
} margin: 4rpx 0;
width: 40rpx;
height: 40rpx;
}
.text-wrapper_4 {
padding: 12rpx 0;
overflow: hidden;
width: 168rpx;
height: 48rpx;
} }
.font_5 { .image_9 {
font-size: 28rpx; margin-right: 12rpx;
font-family: SourceHanSansCN; width: 16rpx;
line-height: 25.76rpx; height: 26rpx;
color: #5db66f;
} }
} }
.font_3 { .font_5 {
font-size: 28rpx; font-size: 28rpx;
font-family: SourceHanSansCN; font-family: SourceHanSansCN;
line-height: 25.76rpx; line-height: 25.76rpx;
color: #1f2937; color: #5db66f;
} }
}
.section_4 { .font_3 {
padding-left: 6rpx; font-size: 28rpx;
padding-bottom: 26rpx; font-family: SourceHanSansCN;
background-color: #ffffff; line-height: 25.76rpx;
border-radius: 26.28rpx; color: #1f2937;
mix-blend-mode: NOTTHROUGH; }
.list { .section_4 {
margin-left: 18rpx; padding-left: 6rpx;
margin-right: 24rpx; padding-bottom: 26rpx;
background-color: #ffffff;
.list-item { border-radius: 26.28rpx;
padding: 24rpx 0; mix-blend-mode: NOTTHROUGH;
border-bottom: solid 2rpx #eeeeee;
.list {
.section_5 { margin-left: 18rpx;
padding: 12rpx 0 116rpx; margin-right: 24rpx;
background-image: url('/static/images/codefun/4be80e2618f3c4b4aa1ce64fd9063abf.png');
background-size: 100% 100%; .list-item {
background-repeat: no-repeat; padding: 24rpx 0;
width: 160rpx; border-bottom: solid 2rpx #eeeeee;
height: 160rpx;
.section_5 {
.text-wrapper_5 { padding: 12rpx 0 116rpx;
padding: 8rpx 0; background-image: url('/static/images/codefun/4be80e2618f3c4b4aa1ce64fd9063abf.png');
background-image: linear-gradient(90deg, #43cf7c 0%, #5db66f 100%); background-size: 100% 100%;
border-radius: 0rpx 8rpx 8rpx 0rpx; background-repeat: no-repeat;
mix-blend-mode: NOTTHROUGH; width: 160rpx;
width: 84rpx; height: 160rpx;
.font_6 { .text-wrapper_5 {
font-size: 20rpx; padding: 8rpx 0;
font-family: SourceHanSansCN; background-image: linear-gradient(90deg, #43cf7c 0%, #5db66f 100%);
line-height: 18.38rpx; border-radius: 0rpx 8rpx 8rpx 0rpx;
color: #ffffff; mix-blend-mode: NOTTHROUGH;
} width: 84rpx;
.font_6 {
font-size: 20rpx;
font-family: SourceHanSansCN;
line-height: 18.38rpx;
color: #ffffff;
} }
} }
}
.group_10 { .group_10 {
margin-right: 40rpx; margin-right: 40rpx;
.text_9 { .text_9 {
line-height: 22.34rpx; line-height: 22.34rpx;
} }
.text_10 { .text_10 {
line-height: 22.22rpx; line-height: 22.22rpx;
} }
.group_11 { .group_11 {
padding-left: 64rpx; padding-left: 64rpx;
.image_10 { .image_10 {
width: 28rpx; width: 28rpx;
height: 28rpx; height: 28rpx;
} }
.text-wrapper_6 { .text-wrapper_6 {
padding-top: 8rpx; padding-top: 8rpx;
overflow: hidden; overflow: hidden;
width: 48rpx; width: 48rpx;
height: 32rpx; height: 32rpx;
.text_11 { .text_11 {
line-height: 22.12rpx; line-height: 22.12rpx;
}
} }
}
.font_8 { .font_8 {
font-size: 24rpx; font-size: 24rpx;
font-family: SourceHanSansCN; font-family: SourceHanSansCN;
line-height: 22.28rpx; line-height: 22.28rpx;
color: #f44336; color: #f44336;
}
} }
} }
} }
} }
}
.group_14 { .group_14 {
margin: 18rpx 24rpx 0; margin: 18rpx 24rpx 0;
.text_14 { .text_14 {
line-height: 26.12rpx; line-height: 26.12rpx;
} }
.text-wrapper_8 { .text-wrapper_8 {
padding: 4rpx 0; padding: 4rpx 0;
overflow: hidden; overflow: hidden;
width: 48rpx; width: 48rpx;
height: 32rpx; height: 32rpx;
.text_15 { .text_15 {
line-height: 22.2rpx; line-height: 22.2rpx;
}
} }
} }
}
.font_7 { .font_7 {
font-size: 24rpx; font-size: 24rpx;
font-family: SourceHanSansCN; font-family: SourceHanSansCN;
line-height: 22.28rpx; line-height: 22.28rpx;
color: #5db66f; color: #5db66f;
} }
.group_15 {
margin-left: 24rpx;
margin-top: 28rpx;
width: 528.18rpx;
.text_16 { .group_15 {
line-height: 25.12rpx; margin-left: 24rpx;
} margin-top: 28rpx;
width: 528.18rpx;
.text_17 { .text_16 {
line-height: 26.26rpx; line-height: 25.12rpx;
}
} }
.group_16 { .text_17 {
margin-left: 24rpx; line-height: 26.26rpx;
margin-top: 28rpx; }
width: 216.94rpx; }
.text_18 { .group_16 {
line-height: 25.82rpx; margin-left: 24rpx;
} margin-top: 28rpx;
width: 216.94rpx;
.text_19 { .text_18 {
line-height: 25.34rpx; line-height: 25.82rpx;
}
} }
.font_9 { .text_19 {
font-size: 28rpx; line-height: 25.34rpx;
font-family: SourceHanSansCN;
line-height: 25.76rpx;
color: #6b7280;
} }
}
.group_17 { .font_9 {
margin-left: 24rpx; font-size: 28rpx;
margin-top: 28rpx; font-family: SourceHanSansCN;
line-height: 25.76rpx;
color: #6b7280;
}
.text_20 { .group_17 {
line-height: 26.16rpx; margin-left: 24rpx;
} margin-top: 28rpx;
.text_21 { .text_20 {
line-height: 22.66rpx; line-height: 26.16rpx;
}
} }
.divider { .text_21 {
margin: 28rpx 18rpx 0 24rpx; line-height: 22.66rpx;
background-color: #f3f4f6;
height: 2rpx;
} }
}
.text_22 { .divider {
margin-left: 24rpx; margin: 28rpx 18rpx 0 24rpx;
margin-top: 32rpx; background-color: #f3f4f6;
line-height: 26.12rpx; height: 2rpx;
} }
.group_18 { .text_22 {
margin-left: 24rpx; margin-left: 24rpx;
margin-top: 32rpx; margin-top: 32rpx;
line-height: 26.12rpx;
}
.text_23 { .group_18 {
line-height: 26.06rpx; margin-left: 24rpx;
} margin-top: 32rpx;
.text_24 { .text_23 {
line-height: 22.66rpx; line-height: 26.06rpx;
}
} }
.font_10 { .text_24 {
font-size: 28rpx; line-height: 22.66rpx;
font-family: SourceHanSansCN;
line-height: 22.28rpx;
color: #1f2937;
} }
}
.group_19 { .font_10 {
margin-left: 24rpx; font-size: 28rpx;
margin-top: 28rpx; font-family: SourceHanSansCN;
line-height: 22.28rpx;
color: #1f2937;
}
.text_25 { .group_19 {
line-height: 25.9rpx; margin-left: 24rpx;
} margin-top: 28rpx;
.text_26 { .text_25 {
line-height: 25.78rpx; line-height: 25.9rpx;
}
} }
.group_20 { .text_26 {
margin-left: 24rpx; line-height: 25.78rpx;
margin-top: 32rpx;
.text_27 {
line-height: 25.96rpx;
}
.text_28 {
line-height: 25.88rpx;
}
} }
}
.divider_2 { .group_20 {
margin-top: 28rpx; margin-left: 24rpx;
background-color: #f3f4f6; margin-top: 32rpx;
width: 716rpx;
height: 2rpx;
}
.text-wrapper_9 { .text_27 {
margin-left: 24rpx; line-height: 25.96rpx;
margin-top: 32rpx; }
padding: 24rpx 0;
background-color: #5db66f;
border-radius: 400rpx;
mix-blend-mode: NOTTHROUGH;
.text_29 { .text_28 {
line-height: 25.9rpx; line-height: 25.88rpx;
}
} }
} }
.font_4 { .divider_2 {
font-size: 24rpx; margin-top: 28rpx;
font-family: SourceHanSansCN; background-color: #f3f4f6;
line-height: 22.28rpx; width: 716rpx;
color: #555555; height: 2rpx;
} }
}
.image_6 { .text-wrapper_9 {
width: 32rpx; margin-left: 24rpx;
height: 32rpx; margin-top: 32rpx;
} padding: 24rpx 0;
background-color: #5db66f;
border-radius: 400rpx;
mix-blend-mode: NOTTHROUGH;
.font { .text_29 {
font-size: 32rpx; line-height: 25.9rpx;
font-family: SourceHanSansCN; }
line-height: 29.88rpx; }
color: #333333;
} }
.font_2 { .font_4 {
font-size: 28rpx; font-size: 24rpx;
font-family: SourceHanSansCN; font-family: SourceHanSansCN;
line-height: 25.76rpx; line-height: 22.28rpx;
color: #ffffff; color: #555555;
} }
} }
/* 设备列表样式 */ .image_6 {
.device-list { width: 32rpx;
margin-top: 20rpx; height: 32rpx;
}
.device-item {
background-color: #ffffff;
border-radius: 16rpx;
padding: 24rpx;
display: flex;
// justify-content: space-between;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
} }
::v-deep .uni-swipe { .font {
border-radius: 16rpx; font-size: 32rpx;
margin-bottom: 20rpx; font-family: SourceHanSansCN;
line-height: 29.88rpx;
color: #333333;
} }
.device-info { .font_2 {
flex: 1; font-size: 28rpx;
font-family: SourceHanSansCN;
line-height: 25.76rpx;
color: #ffffff;
} }
}
.device-name {
font-size: 32rpx; /* 设备列表样式 */
.device-list {
margin-top: 20rpx;
}
.device-item {
background-color: #ffffff;
border-radius: 16rpx;
padding: 24rpx;
display: flex;
// justify-content: space-between;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
::v-deep .uni-swipe {
border-radius: 16rpx;
margin-bottom: 20rpx;
}
.device-info {
flex: 1;
}
.device-name {
font-size: 28rpx;
font-weight: 400;
letter-spacing: 0px;
text-align: center;
}
.device-details {
display: flex;
flex-direction: column;
gap: 6rpx;
margin-bottom: 8rpx;
}
.device-type,
.device-identifier {
font-size: 24rpx;
color: #666666;
}
.device-status-time {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6rpx;
min-width: 180rpx;
}
.device-status {
font-size: 24rpx;
font-weight: bold;
}
.device-time {
font-size: 22rpx;
color: #999999;
}
.device-actions {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.status-connected {
color: #5db66f !important;
font-weight: bold;
}
.status-disconnected {
color: #f44336 !important;
font-weight: bold;
}
.status-unknown {
color: #ff9800 !important;
font-weight: bold;
}
.empty-state {
text-align: center;
padding: 100rpx 0;
color: #999999;
font-size: 28rpx;
}
/* 分类标签样式 */
.category-tabs {
display: flex;
gap: 16rpx;
padding: 24rpx 0;
overflow-x: auto;
white-space: nowrap;
}
.category-tab {
padding: 12rpx 32rpx;
background-color: #ffffff;
border-radius: 40rpx;
font-size: 28rpx;
color: #333333;
cursor: pointer;
flex-shrink: 0;
border: 2rpx solid transparent;
transition: all 0.3s;
&.active {
background-color: #5db66f;
color: #ffffff;
font-weight: bold; font-weight: bold;
color: #333333;
margin-bottom: 8rpx;
border-left: 6rpx solid #5db66f;
padding-left: 6rpx;
} }
}
.device-details {
display: flex; /* 摄像头设备样式 */
flex-direction: column; /* 顶部视频占位区域 */
gap: 6rpx; .video-placeholder-top {
margin-bottom: 8rpx; position: relative;
width: 100%;
height: 400rpx;
background-image: url('/static/images/device/video-bg.jpg');
background-size: cover;
background-position: center;
flex-shrink: 0;
}
.video-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 20rpx 24rpx;
background: linear-gradient(to top, rgba(0, 0, 0, 0.6), transparent);
display: flex;
justify-content: space-between;
align-items: flex-end;
}
.video-info {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.video-location {
color: #ffffff;
font-size: 32rpx;
font-weight: bold;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
.video-time {
color: #ffffff;
font-size: 24rpx;
opacity: 0.9;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
.fullscreen-icon {
width: 48rpx;
height: 48rpx;
opacity: 0.9;
}
/* 设备信息卡片样式 */
.device-info-card {
margin: 24rpx 24rpx 0rpx 24rpx;
padding: 32rpx 24rpx;
background-color: #ffffff;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
}
.device-header {
display: flex;
align-items: flex-start;
gap: 20rpx;
margin-bottom: 24rpx;
}
.device-icon {
width: 80rpx;
height: 80rpx;
background-color: #e8f5e9;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.icon-img {
width: 48rpx;
height: 48rpx;
}
.device-title-group {
flex: 1;
}
.device-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
margin-bottom: 12rpx;
}
.device-badges {
display: flex;
gap: 12rpx;
}
.badge {
padding: 6rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
}
.badge-green {
background-color: #5db66f;
color: #ffffff;
}
.badge-gray {
background-color: #e0e0e0;
color: #666666;
}
.device-stats {
display: flex;
justify-content: space-between;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
} }
}
.device-type,
.device-identifier { .stat-item {
font-size: 24rpx; flex: 1;
color: #666666; display: flex;
align-items: center;
}
.stat-label {
font-size: 26rpx;
color: #999999;
}
.stat-value {
font-size: 26rpx;
color: #333333;
}
.device-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
margin-top: 20rpx;
}
.device-card {
background-color: #ffffff;
border-radius: 16rpx;
padding: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
cursor: pointer;
transition: all 0.3s;
border: 2rpx solid transparent;
&.selected {
border-color: #5db66f;
box-shadow: 0 4rpx 12rpx rgba(93, 182, 111, 0.3);
background: #5DB66F;
color: #FFFFFF !important;
} }
}
.device-status-time { .device-card-header {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-end; gap: 8rpx;
gap: 6rpx; margin-bottom: 16rpx;
min-width: 180rpx; border-bottom: 2rpx solid #f0f0f0;
} padding-bottom: 16rpx;
.device-status { .device-name {
font-size: 24rpx; font-size: 28rpx;
font-weight: bold; font-weight: bold;
color: #333333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.device-time { .device-status {
font-size: 22rpx; font-size: 22rpx;
color: #999999; font-weight: bold;
} }
}
.device-actions { .device-card-body {
display: flex;
flex-direction: column;
gap: 12rpx;
.device-detail {
font-size: 22rpx;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12rpx; gap: 4rpx;
}
.status-connected {
color: #5db66f !important;
font-weight: bold;
}
.status-disconnected {
color: #f44336 !important;
font-weight: bold;
}
.status-unknown { .detail-label {
color: #ff9800 !important; color: #999999;
font-weight: bold; }
}
.empty-state { .detail-value {
text-align: center; color: #666666;
padding: 100rpx 0; overflow: hidden;
color: #999999; text-overflow: ellipsis;
font-size: 28rpx; white-space: nowrap;
}
} }
}
</style> </style>
...@@ -286,6 +286,7 @@ ...@@ -286,6 +286,7 @@
:required="false" :required="false"
clearable clearable
trim trim
type="number"
placeholder="请输入手机号" placeholder="请输入手机号"
v-model="model.form.data.username" v-model="model.form.data.username"
name="mobile" name="mobile"
......
...@@ -15,6 +15,7 @@ import type { ToolBoxButtonHandleEvent } from '@/components/Map/Widgets/ToolBox' ...@@ -15,6 +15,7 @@ import type { ToolBoxButtonHandleEvent } from '@/components/Map/Widgets/ToolBox'
import { ToolBoxWidget, useToolBoxWidget } from '@/components/Map/Widgets/ToolBox' import { ToolBoxWidget, useToolBoxWidget } from '@/components/Map/Widgets/ToolBox'
import * as NongchangAPI from '@/api/model/nongchang' import * as NongchangAPI from '@/api/model/nongchang'
import * as farmbaseApi from '@/api/model/farmbase' import * as farmbaseApi from '@/api/model/farmbase'
import navigate from '@/utils/page/navigate'
// 页面参数 // 页面参数
const page = reactive<Page>({ const page = reactive<Page>({
...@@ -343,6 +344,9 @@ onNavigationBarButtonTap((e) => { ...@@ -343,6 +344,9 @@ onNavigationBarButtonTap((e) => {
Navigate.to(`/pages/jidiguanli/add?farmId=${model.id}`) Navigate.to(`/pages/jidiguanli/add?farmId=${model.id}`)
} }
}) })
const toDevice = (device)=>{
Navigate.to(`/pages/device/device?deviceType=${device.deviceType}&farmBaseId=`+model.farmbaseInfo?.id)
}
</script> </script>
<template> <template>
...@@ -485,16 +489,20 @@ onNavigationBarButtonTap((e) => { ...@@ -485,16 +489,20 @@ onNavigationBarButtonTap((e) => {
<view class="box-4"> <view class="box-4">
<view class="box-4-title"> <view class="box-4-title">
<view class="box-4-title-text1"><text>基地设备</text></view> <view class="box-4-title-text1"><text>基地设备</text></view>
<view class="box-4-title-text2" @click="showDialog=true"><text>+ 添加设备</text></view> <view class="box-4-title-text2" @click="showDialog = true"><text>+ 添加设备</text></view>
</view> </view>
<view class="box-4-device"> <view class="box-4-device">
<view <view
class="box-4-device-item" class="box-4-device-item"
v-for="(device, index) in model.deviceTypeCount" v-for="(device, index) in model.deviceTypeCount"
:key="index" :key="index"
@click="toDevice(device)"
> >
<view class="box-4-item-icon"> <view class="box-4-item-icon">
<image class="box-4-item-icon-image" :src="`/static/images/nongchang/device${device.deviceType}.png`" /> <image
class="box-4-item-icon-image"
:src="`/static/images/nongchang/device${device.deviceType}.png`"
/>
</view> </view>
<view class="box-4-item-content"> <view class="box-4-item-content">
<text class="box-4-item-text1">{{ device.deviceName }}</text> <text class="box-4-item-text1">{{ device.deviceName }}</text>
...@@ -506,7 +514,13 @@ onNavigationBarButtonTap((e) => { ...@@ -506,7 +514,13 @@ onNavigationBarButtonTap((e) => {
</view> </view>
</view> </view>
</view> </view>
<SaveDialog :show="showDialog" :farmId="model.id" :farmBaseId="model.farmbaseInfo?.id" @submitSuccess="getDeviceTypeCount" @close="showDialog=false"/> <SaveDialog
:show="showDialog"
:farmId="model.id"
:farmBaseId="model.farmbaseInfo?.id"
@submitSuccess="getDeviceTypeCount"
@close="showDialog = false"
/>
</view> </view>
</template> </template>
...@@ -887,8 +901,8 @@ page { ...@@ -887,8 +901,8 @@ page {
.box-4-device-item { .box-4-device-item {
width: 49%; width: 49%;
// height: 48%; // height: 48%;
height: 136rpx; height: 136rpx;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
border-radius: 24rpx; border-radius: 24rpx;
......
/**
* uni-app formdata 请求封装
* 使用 uni.request 模拟 formdata 提交
*/
import { useGlobSetting } from '/@/hooks/setting'
import { useUserStoreWithOut } from '@/store/modules/user'
interface FormDataRequestOptions {
url: string
data?: Record<string, any>
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
header?: Record<string, any>
timeout?: number
}
interface FormDataResponse<T = any> {
data: T
statusCode: number
header: Record<string, any>
errMsg: string
}
/**
* formdata 格式请求
* @param options 请求配置
* @returns Promise
*/
export function formDataRequest<T = any>(options: FormDataRequestOptions): Promise<FormDataResponse<T>> {
return new Promise((resolve, reject) => {
const { url, data = {}, method = 'POST', header = {}, timeout = 60000 } = options
// 获取全局配置
const globSetting = useGlobSetting()
const urlPrefix = globSetting.urlPrefix
const apiUrl = globSetting.apiUrl
// 获取 token
const userStore = useUserStoreWithOut()
const token = userStore.getToken
// 拼接完整 URL
const fullUrl = `${apiUrl}${urlPrefix}${url}`
// 将数据转换为 formdata 格式字符串
const formDataStr = Object.keys(data)
.map((key) => {
const value = data[key]
if (value === null || value === undefined) {
return ''
}
if (Array.isArray(value)) {
// 处理数组
return value.map((item) => `${encodeURIComponent(key)}=${encodeURIComponent(item)}`).join('&')
}
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
})
.filter((item) => item !== '')
.join('&')
uni.request({
url: fullUrl,
method,
data: formDataStr,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Access-Token': token,
...header,
},
timeout,
success: (res: any) => {
resolve({
data: res.data,
statusCode: res.statusCode,
header: res.header,
errMsg: res.errMsg,
})
},
fail: (err: any) => {
reject({
data: null,
statusCode: err.statusCode || 0,
header: err.header || {},
errMsg: err.errMsg || '请求失败',
})
},
})
})
}
/**
* POST formdata 请求
* @param url 请求地址
* @param data 请求数据
* @param header 请求头
* @returns Promise
*/
export function postFormData<T = any>(
url: string,
data?: Record<string, any>,
header?: Record<string, any>,
): Promise<FormDataResponse<T>> {
return formDataRequest<T>({
url,
data,
method: 'POST',
header,
})
}
/**
* PUT formdata 请求
* @param url 请求地址
* @param data 请求数据
* @param header 请求头
* @returns Promise
*/
export function putFormData<T = any>(
url: string,
data?: Record<string, any>,
header?: Record<string, any>,
): Promise<FormDataResponse<T>> {
return formDataRequest<T>({
url,
data,
method: 'PUT',
header,
})
}
...@@ -11,8 +11,8 @@ const PROXY_LIST: [[string, string]?] = [ ...@@ -11,8 +11,8 @@ const PROXY_LIST: [[string, string]?] = [
// [`http://192.168.0.100:18100`, `https://oss.beta.app.yiring.com`], // [`http://192.168.0.100:18100`, `https://oss.beta.app.yiring.com`],
// 开发环境(预览) // 开发环境(预览)
// [`http://192.168.0.156:18100`, `http://111.22.182.169:49091`], [`http://192.168.0.156:18100`, `http://111.22.182.169:49091`],
[`http://192.168.0.156:18100`, `http://36.133.16.81:42111`], // [`http://192.168.0.156:18100`, `http://36.133.16.81:42111`],
] ]
/** /**
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论