提交 032ca494 作者: 宇宙超人

1

上级 45e70da5
......@@ -546,24 +546,11 @@
{
"path": "pages/device/device",
"style": {
"navigationBarTitleText": "物联设备",
"navigationBarTitleText": "基地设备",
"enablePullDownRefresh": false,
"navigationBarBackgroundColor": "#5DB66F",
"navigationBarTextStyle": "white",
"backgroundColorBottom": "#F2F2F2",
"app-plus": {
"titleNView": {
"buttons": [
{
"text": "+ 添加设备",
"fontSrc": "/static/uni.ttf",
"color": "#fff",
"fontSize": "26rpx",
"width": "auto"
}
]
}
}
"backgroundColorBottom": "#F2F2F2"
}
},
{
......
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue'
import { useDictStore } from '@/store/modules/dict'
import * as NongchangAPI from '@/api/model/nongchang'
import { computed, reactive, ref, watch } from 'vue'
import { useDictStore } from '@/store/modules/dict'
import * as NongchangAPI from '@/api/model/nongchang'
// 定义Props
interface Props {
// 定义Props
interface Props {
show: boolean
editData?: any
farmId:any,
farmBaseId:any,
}
farmId: any
farmBaseId: any
}
const props = withDefaults(defineProps<Props>(), {
const props = withDefaults(defineProps<Props>(), {
show: false,
editData: null,
farmId:0,
farmBaseId:0,
})
farmId: 0,
farmBaseId: 0,
})
// 定义Emits
const emit = defineEmits<{
// 定义Emits
const emit = defineEmits<{
'update:show': [value: boolean]
submit: [data: any]
close: []
}>()
}>()
// 表单引用
const formRef = ref()
const loading = ref(false)
const showDeviceTypePicker = ref(false)
// 表单引用
const formRef = ref()
const loading = ref(false)
const showDeviceTypePicker = ref(false)
// 字典存储
const dictStore = useDictStore()
// 字典存储
const dictStore = useDictStore()
// 表单数据
const formData = reactive({
// 表单数据
const formData = reactive({
deviceName: '',
deviceType: '',
deviceTypeText: '',
deviceIdentifier: '',
})
})
// 设备类型选项 - 使用字典数据
const deviceTypeOptions = computed(() => {
// 设备类型选项 - 使用字典数据
const deviceTypeOptions = computed(() => {
return (
dictStore.getDictList.deviceType?.map((item: any) => ({
value: item.value,
text: item.text,
})) || []
)
})
})
// 设备类型选择器数据 (不再需要,因为fui-picker使用options属性)
// 设备类型选择器数据 (不再需要,因为fui-picker使用options属性)
// 计算属性
const dialogTitle = computed(() => {
// 计算属性
const dialogTitle = computed(() => {
return props.editData ? '编辑设备' : '添加设备'
})
})
const submitButtonText = computed(() => {
const submitButtonText = computed(() => {
return props.editData ? '保存' : '确认'
})
})
// 表单验证规则
const rules = {
// 表单验证规则
const rules = {
deviceName: [
{ required: true, message: '请输入设备名称', trigger: 'blur' },
{ min: 2, max: 50, message: '设备名称长度在2-50个字符之间', trigger: 'blur' },
......@@ -84,10 +83,10 @@
trigger: 'blur',
},
],
}
}
// 监听显示状态
watch(
// 监听显示状态
watch(
() => props.show,
(newVal) => {
if (newVal && props.editData) {
......@@ -99,10 +98,10 @@
resetFormData()
}
},
)
)
// 重置表单数据
function resetFormData() {
// 重置表单数据
function resetFormData() {
formData.deviceName = ''
formData.deviceType = ''
formData.deviceTypeText = ''
......@@ -112,12 +111,11 @@
if (formRef.value) {
formRef.value.resetFields()
}
}
}
// 加载编辑数据
function loadEditData() {
if (!props.editData)
return
// 加载编辑数据
function loadEditData() {
if (!props.editData) return
formData.deviceName = props.editData.deviceName || ''
formData.deviceType = props.editData.deviceType || ''
......@@ -135,17 +133,17 @@ return
} else {
formData.deviceTypeText = ''
}
}
}
// 设备类型选择确认
function handleDeviceTypeConfirm(e: any) {
// 设备类型选择确认
function handleDeviceTypeConfirm(e: any) {
formData.deviceType = e.value
formData.deviceTypeText = e.text
showDeviceTypePicker.value = false
}
}
// 提交表单
async function handleSubmit() {
// 提交表单
async function handleSubmit() {
try {
console.log(formData)
// 先进行表单验证
......@@ -192,21 +190,21 @@ return
} finally {
loading.value = false
}
}
}
// 关闭弹窗
function handleClose() {
// 关闭弹窗
function handleClose() {
emit('update:show', false)
emit('close')
}
}
// 暴露方法给父组件
defineExpose({
// 暴露方法给父组件
defineExpose({
resetFormData,
setLoading: (value: boolean) => {
loading.value = value
},
})
})
</script>
<template>
......@@ -271,23 +269,23 @@ return
</template>
<style lang="scss" scoped>
.dialog-content {
.dialog-content {
padding: 10rpx 30rpx;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
}
.section-title {
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin: 20rpx 0 30rpx 0;
padding-left: 20rpx;
border-left: 6rpx solid #5db66f;
}
}
.dialog-buttons {
.dialog-buttons {
display: flex;
gap: 20rpx;
margin-top: 40rpx;
......@@ -310,10 +308,10 @@ return
border: 2rpx solid #dcdfe6;
}
}
}
}
// uview-plus 表单样式调整
::v-deep .u-form-item {
// uview-plus 表单样式调整
::v-deep .u-form-item {
margin-bottom: 40rpx;
.u-form-item__body {
......@@ -336,8 +334,8 @@ return
flex: 1;
min-width: 0;
}
}
.address-display {
}
.address-display {
display: flex;
justify-content: space-between;
align-items: center;
......@@ -357,15 +355,15 @@ return
.placeholder-text {
color: #c0c4cc;
}
}
}
::v-deep .u-form-item__body__left__content__required {
::v-deep .u-form-item__body__left__content__required {
position: static;
padding-right: 10rpx;
}
}
// 输入框样式优化
::v-deep .u-input {
// 输入框样式优化
::v-deep .u-input {
.u-input__content {
padding: 0;
......@@ -390,10 +388,10 @@ return
}
}
}
}
}
// 模态框样式调整
::v-deep .u-modal {
// 模态框样式调整
::v-deep .u-modal {
.u-modal__content {
border-radius: 20rpx;
padding: 20rpx 0rpx;
......@@ -409,5 +407,5 @@ return
color: #333;
}
}
}
}
</style>
<script setup>
import { reactive, ref } from 'vue'
import { onNavigationBarButtonTap } from '@dcloudio/uni-app'
import SaveDialog from './components/save-dialog.vue'
import * as NongchangAPI from '@/api/model/nongchang'
const isOnePage = ref(true)
const paging = ref(null)
const pageData = reactive({
import { reactive, ref, computed } from 'vue'
import { onLoad, onNavigationBarButtonTap } from '@dcloudio/uni-app'
import SaveDialog from './components/save-dialog.vue'
import * as NongchangAPI from '@/api/model/nongchang'
import { useDictStore } from '@/store/modules/dict'
import { merge } from 'lodash-es'
const dictStore = useDictStore()
const deviceType = ref('') // 设备类型参数
const pageData = reactive({
param: {
pageNo: 1,
pageSize: 10,
deviceName: '',
deviceType: '', // 添加设备类型筛选
},
list: [],
})
// 滑动操作选项
const swipeOptions = [
{
text: '编辑',
style: {
backgroundColor: 'var(--fui-color-primary)',
},
},
{
text: '删除',
style: {
backgroundColor: 'var(--fui-color-danger)',
},
},
]
function getList() {
if (!paging.value) return
NongchangAPI.getDeviceList(pageData.param)
.then((res) => {
pageData.total = res.total
paging.value.complete(res.records)
})
.catch(() => {
paging.value.complete(false)
})
}
function queryList(pageNo, pageSize) {
pageData.param.pageNo = pageNo
pageData.param.pageSize = pageSize
selectedCategory: 'all',
})
// 摄像头设备的分类标签
const cameraCategories = computed(() => {
const dictList = dictStore.getDictList.deviceType?.map((item) => ({
id: item.value,
name: item.text,
})) || []
return [{ id: 'all', name: '全部' }, ...dictList]
})
onLoad((options) => {
// 获取设备类型参数
if (options.deviceType) {
deviceType.value = options.deviceType
pageData.param.deviceType = options.deviceType
}
})
// 判断是否为摄像头设备
const isCameraDevice = computed(() => {
return deviceType.value === '1'
})
// 获取设备列表
function getList() {
// TODO: API请求留空,后续实现
// NongchangAPI.getDeviceList(pageData.param)
// .then((res) => {
// pageData.list = res.records || []
// })
// .catch(() => {
// pageData.list = []
// })
}
function handleSearch() {
// 搜索时重新获取列表
getList()
}
}
function handleSearch() {
// 重置页码为1,重新搜索
pageData.param.pageNo = 1
if (paging.value) {
paging.value.reload()
}
}
const showDialog = ref(false)
const currentEditData = ref(null)
onNavigationBarButtonTap((_) => {
const showDialog = ref(false)
const currentEditData = ref(null)
onNavigationBarButtonTap((_) => {
showAddDialog()
})
function showAddDialog() {
})
function showAddDialog() {
currentEditData.value = null
showDialog.value = true
}
}
function showEditDialog(device) {
currentEditData.value = device
showDialog.value = true
}
function handleSubmitSuccess() {
function handleSubmitSuccess() {
// 提交成功后刷新列表
handleSearch()
}
getList()
}
function handleDialogClose() {
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'
// 获取状态样式类
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
})
}
}
// 处理滑动操作
function handleSwipeAction(e, device) {
const index = e.index
const position = e.position
if (position === 'right') {
if (index === 0) {
// 编辑操作
showEditDialog(device)
} else if (index === 1) {
// 删除操作
handleDelete(device.id)
}
}
}
// 分类标签点击
function handleCategoryClick(category) {
pageData.selectedCategory = category.id
// TODO: 根据分类筛选设备
getList()
}
</script>
<template>
<view class="codefun-flex-col page">
<z-paging ref="paging" v-model="pageData.list" @query="queryList">
<!-- 摄像头设备布局 -->
<view v-if="isCameraDevice" class="camera-layout">
<!-- 搜索框 -->
<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" />
......@@ -145,33 +109,103 @@ return 'status-disconnected'
/>
</view>
<!-- 设备列表 -->
<view class="device-list">
<uni-swipe-action>
<uni-swipe-action-item
<!-- 分类标签 -->
<view class="category-tabs">
<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 class="camera-grid">
<view v-for="(item, index) in pageData.list" :key="index" class="camera-card" @click="() => {}">
<!-- 视频占位区域 -->
<view class="video-placeholder">
<view class="video-title">{{ item.deviceName }}</view>
<image class="play-icon" src="/static/images/device/play-icon.png" mode="aspectFit" />
</view>
<!-- 设备信息 -->
<view class="camera-info">
<view class="info-row">
<text class="info-label">设备类型:</text>
<text class="info-value">{{ item.deviceType_dictText || '未知' }}</text>
</view>
<view class="info-row">
<text class="info-label">设备编号:</text>
<text class="info-value">{{ item.deviceIdentifier }}</text>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="pageData.list.length === 0">
<text>暂无设备数据</text>
</view>
</view>
</view>
<!-- 其他设备布局 -->
<view v-else>
<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="请输入设备名称搜索"
border="none"
class="codefun-ml-8"
@confirm="handleSearch"
/>
</view>
<!-- 分类标签 -->
<view class="category-tabs">
<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 class="device-grid">
<view
v-for="(item, index) in pageData.list"
:key="index"
:right-options="swipeOptions"
@click="(e) => handleSwipeAction(e, item)"
class="device-card"
:class="{ selected: item.selected }"
@click="() => {}"
>
<view class="device-item">
<view class="device-info">
<view class="device-card-header">
<view class="device-name">{{ item.deviceName }}</view>
<view class="device-details">
<text class="device-type">类型: {{ item.deviceType_dictText || '未知' }}</text>
<text class="device-identifier">标识: {{ item.deviceIdentifier }}</text>
<view class="device-status" :class="getStatusClass(item.connectStatus_dictText)">
{{ item.connectStatus_dictText || '未知' }}
</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 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>
</uni-swipe-action-item>
</uni-swipe-action>
</view>
<!-- 空状态 -->
......@@ -179,7 +213,8 @@ return 'status-disconnected'
<text>暂无设备数据</text>
</view>
</view>
</z-paging>
</view>
<!-- 弹窗组件 -->
<SaveDialog
:show="showDialog"
......@@ -192,31 +227,31 @@ return 'status-disconnected'
</template>
<style lang="scss">
body {
body {
background-color: #e6f5e8;
}
}
.mt-5 {
.mt-5 {
margin-top: 10rpx;
}
}
.mt-11 {
.mt-11 {
margin-top: 22rpx;
}
}
.ml-5 {
.ml-5 {
margin-left: 10rpx;
}
}
.ml-13 {
.ml-13 {
margin-left: 26rpx;
}
}
.ml-9 {
.ml-9 {
margin-left: 18rpx;
}
}
.page {
.page {
background-color: #e6f5e8;
mix-blend-mode: NOTTHROUGH;
width: 100%;
......@@ -679,14 +714,14 @@ return 'status-disconnected'
line-height: 25.76rpx;
color: #ffffff;
}
}
}
/* 设备列表样式 */
.device-list {
/* 设备列表样式 */
.device-list {
margin-top: 20rpx;
}
}
.device-item {
.device-item {
background-color: #ffffff;
border-radius: 16rpx;
padding: 24rpx;
......@@ -694,82 +729,247 @@ return 'status-disconnected'
// justify-content: space-between;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
}
::v-deep .uni-swipe {
::v-deep .uni-swipe {
border-radius: 16rpx;
margin-bottom: 20rpx;
}
}
.device-info {
.device-info {
flex: 1;
}
}
.device-name {
.device-name {
font-size: 32rpx;
font-weight: bold;
color: #333333;
margin-bottom: 8rpx;
border-left: 6rpx solid #5db66f;
padding-left: 6rpx;
}
}
.device-details {
.device-details {
display: flex;
flex-direction: column;
gap: 6rpx;
margin-bottom: 8rpx;
}
}
.device-type,
.device-identifier {
.device-type,
.device-identifier {
font-size: 24rpx;
color: #666666;
}
}
.device-status-time {
.device-status-time {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6rpx;
min-width: 180rpx;
}
}
.device-status {
.device-status {
font-size: 24rpx;
font-weight: bold;
}
}
.device-time {
.device-time {
font-size: 22rpx;
color: #999999;
}
}
.device-actions {
.device-actions {
display: flex;
flex-direction: column;
gap: 12rpx;
}
}
.status-connected {
.status-connected {
color: #5db66f !important;
font-weight: bold;
}
}
.status-disconnected {
.status-disconnected {
color: #f44336 !important;
font-weight: bold;
}
}
.status-unknown {
.status-unknown {
color: #ff9800 !important;
font-weight: bold;
}
}
.empty-state {
.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;
}
}
/* 摄像头设备样式 */
.camera-layout {
width: 100%;
height: 100%;
}
.camera-grid {
display: flex;
flex-direction: column;
gap: 24rpx;
margin-top: 20rpx;
}
.camera-card {
background-color: #ffffff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.video-placeholder {
position: relative;
width: 100%;
height: 400rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
.video-title {
position: absolute;
top: 20rpx;
left: 20rpx;
color: #ffffff;
font-size: 32rpx;
font-weight: bold;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
.play-icon {
width: 100rpx;
height: 100rpx;
opacity: 0.8;
}
}
.camera-info {
padding: 24rpx;
display: flex;
flex-direction: column;
gap: 12rpx;
.info-row {
display: flex;
font-size: 26rpx;
.info-label {
color: #999999;
min-width: 140rpx;
}
.info-value {
color: #333333;
flex: 1;
}
}
}
/* 其他设备网格样式 */
.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);
}
}
.device-card-header {
display: flex;
flex-direction: column;
gap: 8rpx;
margin-bottom: 16rpx;
border-bottom: 2rpx solid #f0f0f0;
padding-bottom: 16rpx;
.device-name {
font-size: 28rpx;
font-weight: bold;
color: #333333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.device-status {
font-size: 22rpx;
font-weight: bold;
}
}
.device-card-body {
display: flex;
flex-direction: column;
gap: 12rpx;
.device-detail {
font-size: 22rpx;
display: flex;
flex-direction: column;
gap: 4rpx;
.detail-label {
color: #999999;
}
.detail-value {
color: #666666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
</style>
......@@ -286,6 +286,7 @@
:required="false"
clearable
trim
type="number"
placeholder="请输入手机号"
v-model="model.form.data.username"
name="mobile"
......
......@@ -15,6 +15,7 @@ import type { ToolBoxButtonHandleEvent } from '@/components/Map/Widgets/ToolBox'
import { ToolBoxWidget, useToolBoxWidget } from '@/components/Map/Widgets/ToolBox'
import * as NongchangAPI from '@/api/model/nongchang'
import * as farmbaseApi from '@/api/model/farmbase'
import navigate from '@/utils/page/navigate'
// 页面参数
const page = reactive<Page>({
......@@ -343,6 +344,9 @@ onNavigationBarButtonTap((e) => {
Navigate.to(`/pages/jidiguanli/add?farmId=${model.id}`)
}
})
const toDevice = (device)=>{
Navigate.to(`/pages/device/device?deviceType=${device.deviceType}`)
}
</script>
<template>
......@@ -485,16 +489,20 @@ onNavigationBarButtonTap((e) => {
<view class="box-4">
<view class="box-4-title">
<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 class="box-4-device">
<view
class="box-4-device-item"
v-for="(device, index) in model.deviceTypeCount"
:key="index"
@click="toDevice(device)"
>
<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 class="box-4-item-content">
<text class="box-4-item-text1">{{ device.deviceName }}</text>
......@@ -506,7 +514,13 @@ onNavigationBarButtonTap((e) => {
</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>
</template>
......
......@@ -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.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://111.22.182.169:49091`],
// [`http://192.168.0.156:18100`, `http://36.133.16.81:42111`],
]
/**
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论