完善保险端项目
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 开发环境配置
|
||||
NODE_ENV=development
|
||||
VUE_APP_BASE_URL=http://localhost:3000/api
|
||||
VUE_APP_WEIXIN_APPID=wx-your-dev-appid
|
||||
VUE_APP_DEBUG=true
|
||||
# 开发环境配置
|
||||
NODE_ENV=development
|
||||
VUE_APP_BASE_URL=http://localhost:3000/api
|
||||
VUE_APP_WEIXIN_APPID=wx-your-dev-appid
|
||||
VUE_APP_DEBUG=true
|
||||
VUE_APP_VERSION=1.0.0-dev
|
||||
@@ -1,6 +1,6 @@
|
||||
# 生产环境配置
|
||||
NODE_ENV=production
|
||||
VUE_APP_BASE_URL=https://your-production-domain.com/api
|
||||
VUE_APP_WEIXIN_APPID=wx-your-prod-appid
|
||||
VUE_APP_DEBUG=false
|
||||
# 生产环境配置
|
||||
NODE_ENV=production
|
||||
VUE_APP_BASE_URL=https://your-production-domain.com/api
|
||||
VUE_APP_WEIXIN_APPID=wx-your-prod-appid
|
||||
VUE_APP_DEBUG=false
|
||||
VUE_APP_VERSION=1.0.0
|
||||
55
mini_program/farm-monitor-dashboard/app.js
Normal file
55
mini_program/farm-monitor-dashboard/app.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// app.js - 养殖管理系统小程序入口文件
|
||||
App({
|
||||
onLaunch() {
|
||||
// 小程序启动
|
||||
console.log('养殖管理系统小程序启动')
|
||||
|
||||
// 获取系统信息
|
||||
const systemInfo = wx.getSystemInfoSync()
|
||||
this.globalData.systemInfo = systemInfo
|
||||
|
||||
// 检查登录状态
|
||||
this.checkLoginStatus()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 小程序显示
|
||||
console.log('小程序显示')
|
||||
},
|
||||
|
||||
onHide() {
|
||||
// 小程序隐藏
|
||||
console.log('小程序隐藏')
|
||||
},
|
||||
|
||||
onError(err) {
|
||||
// 小程序错误
|
||||
console.error('小程序错误:', err)
|
||||
},
|
||||
|
||||
// 检查登录状态
|
||||
checkLoginStatus() {
|
||||
const token = wx.getStorageSync('token')
|
||||
const userInfo = wx.getStorageSync('userInfo')
|
||||
|
||||
if (token && userInfo) {
|
||||
this.globalData.isLogin = true
|
||||
this.globalData.userInfo = userInfo
|
||||
this.globalData.token = token
|
||||
} else {
|
||||
this.globalData.isLogin = false
|
||||
this.globalData.userInfo = null
|
||||
this.globalData.token = ''
|
||||
}
|
||||
},
|
||||
|
||||
// 全局数据
|
||||
globalData: {
|
||||
isLogin: false,
|
||||
userInfo: null,
|
||||
token: '',
|
||||
systemInfo: null,
|
||||
apiBaseUrl: 'https://ad.ningmuyun.com/farm/api'
|
||||
}
|
||||
})
|
||||
|
||||
64
mini_program/farm-monitor-dashboard/app.json
Normal file
64
mini_program/farm-monitor-dashboard/app.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/home/home",
|
||||
"pages/login/login",
|
||||
"pages/cattle/cattle",
|
||||
"pages/cattle/archive/archive",
|
||||
"pages/cattle/detail/detail",
|
||||
"pages/cattle/transfer/transfer",
|
||||
"pages/cattle/transfer-detail/transfer-detail",
|
||||
"pages/cattle/exit/exit",
|
||||
"pages/cattle/exit-detail/exit-detail",
|
||||
"pages/cattle/pen/pen",
|
||||
"pages/cattle/pen-detail/pen-detail",
|
||||
"pages/cattle/batch/batch",
|
||||
"pages/cattle/batch-detail/batch-detail",
|
||||
"pages/device/device",
|
||||
"pages/device/collar/collar",
|
||||
"pages/device/eartag/eartag",
|
||||
"pages/device/host/host",
|
||||
"pages/device/fence/fence",
|
||||
"pages/alert/alert",
|
||||
"pages/alert/collar-alert/collar-alert",
|
||||
"pages/alert/collar-alert-detail/collar-alert-detail",
|
||||
"pages/alert/eartag-alert/eartag-alert",
|
||||
"pages/alert/eartag-alert-detail/eartag-alert-detail",
|
||||
"pages/production/production",
|
||||
"pages/profile/profile"
|
||||
],
|
||||
"window": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "养殖管理系统",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"backgroundColor": "#f6f6f6"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#999999",
|
||||
"selectedColor": "#52c41a",
|
||||
"borderStyle": "white",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/home/home",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/cattle/cattle",
|
||||
"text": "生产管理"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/profile",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
"permission": {
|
||||
"scope.userLocation": {
|
||||
"desc": "你的位置信息将用于养殖场定位和地图展示"
|
||||
}
|
||||
},
|
||||
"sitemapLocation": "sitemap.json",
|
||||
"style": "v2",
|
||||
"lazyCodeLoading": "requiredComponents"
|
||||
}
|
||||
|
||||
@@ -1,281 +1,281 @@
|
||||
/* app.wxss - 全局样式 */
|
||||
|
||||
/* 全局重置 */
|
||||
page {
|
||||
background-color: #f6f6f6;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 容器样式 */
|
||||
.container {
|
||||
padding: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
margin: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 通用工具类 */
|
||||
.text-center { text-align: center; }
|
||||
.text-left { text-align: left; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
.mt-8 { margin-top: 8rpx; }
|
||||
.mt-16 { margin-top: 16rpx; }
|
||||
.mt-24 { margin-top: 24rpx; }
|
||||
.mt-32 { margin-top: 32rpx; }
|
||||
.mb-8 { margin-bottom: 8rpx; }
|
||||
.mb-16 { margin-bottom: 16rpx; }
|
||||
.mb-24 { margin-bottom: 24rpx; }
|
||||
.mb-32 { margin-bottom: 32rpx; }
|
||||
|
||||
.pt-8 { padding-top: 8rpx; }
|
||||
.pt-16 { padding-top: 16rpx; }
|
||||
.pt-24 { padding-top: 24rpx; }
|
||||
.pt-32 { padding-top: 32rpx; }
|
||||
.pb-8 { padding-bottom: 8rpx; }
|
||||
.pb-16 { padding-bottom: 16rpx; }
|
||||
.pb-24 { padding-bottom: 24rpx; }
|
||||
.pb-32 { padding-bottom: 32rpx; }
|
||||
|
||||
.p-8 { padding: 8rpx; }
|
||||
.p-16 { padding: 16rpx; }
|
||||
.p-24 { padding: 24rpx; }
|
||||
.p-32 { padding: 32rpx; }
|
||||
|
||||
.m-8 { margin: 8rpx; }
|
||||
.m-16 { margin: 16rpx; }
|
||||
.m-24 { margin: 24rpx; }
|
||||
.m-32 { margin: 32rpx; }
|
||||
|
||||
/* Flex布局 */
|
||||
.flex { display: flex; }
|
||||
.flex-column { flex-direction: column; }
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
.justify-center { justify-content: center; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.justify-start { justify-content: flex-start; }
|
||||
.align-center { align-items: center; }
|
||||
.align-start { align-items: flex-start; }
|
||||
.align-end { align-items: flex-end; }
|
||||
|
||||
/* 状态颜色 */
|
||||
.status-normal { color: #52c41a; }
|
||||
.status-pregnant { color: #faad14; }
|
||||
.status-sick { color: #f5222d; }
|
||||
.status-quarantine { color: #909399; }
|
||||
|
||||
/* 主题颜色 */
|
||||
.color-primary { color: #3cc51f; }
|
||||
.color-success { color: #52c41a; }
|
||||
.color-warning { color: #faad14; }
|
||||
.color-danger { color: #f5222d; }
|
||||
.color-info { color: #1890ff; }
|
||||
|
||||
.bg-primary { background-color: #3cc51f; }
|
||||
.bg-success { background-color: #52c41a; }
|
||||
.bg-warning { background-color: #faad14; }
|
||||
.bg-danger { background-color: #f5222d; }
|
||||
.bg-info { background-color: #1890ff; }
|
||||
|
||||
/* 文字颜色 */
|
||||
.text-primary { color: #303133; }
|
||||
.text-regular { color: #606266; }
|
||||
.text-secondary { color: #909399; }
|
||||
.text-placeholder { color: #c0c4cc; }
|
||||
|
||||
/* 背景颜色 */
|
||||
.bg-white { background-color: #ffffff; }
|
||||
.bg-gray { background-color: #f5f5f5; }
|
||||
.bg-light { background-color: #fafafa; }
|
||||
|
||||
/* 边框 */
|
||||
.border { border: 1rpx solid #dcdfe6; }
|
||||
.border-top { border-top: 1rpx solid #dcdfe6; }
|
||||
.border-bottom { border-bottom: 1rpx solid #dcdfe6; }
|
||||
.border-left { border-left: 1rpx solid #dcdfe6; }
|
||||
.border-right { border-right: 1rpx solid #dcdfe6; }
|
||||
|
||||
/* 圆角 */
|
||||
.rounded { border-radius: 4rpx; }
|
||||
.rounded-sm { border-radius: 2rpx; }
|
||||
.rounded-lg { border-radius: 8rpx; }
|
||||
.rounded-xl { border-radius: 12rpx; }
|
||||
.rounded-full { border-radius: 50%; }
|
||||
|
||||
/* 阴影 */
|
||||
.shadow { box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1); }
|
||||
.shadow-sm { box-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.05); }
|
||||
.shadow-lg { box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.15); }
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-primary:active {
|
||||
background-color: #2ea617;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #52c41a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-success:active {
|
||||
background-color: #389e0d;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background-color: #faad14;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-warning:active {
|
||||
background-color: #d48806;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #f5222d;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-danger:active {
|
||||
background-color: #cf1322;
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
background-color: #ffffff;
|
||||
color: #303133;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
}
|
||||
|
||||
.btn-default:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 8rpx 16rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.btn-large {
|
||||
padding: 24rpx 48rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 24rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
/* 列表样式 */
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.list-item:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 64rpx 32rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64rpx;
|
||||
margin-bottom: 16rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.container {
|
||||
margin: 8rpx;
|
||||
padding: 12rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12rpx 24rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.card-header,
|
||||
.card-body,
|
||||
.card-footer {
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
padding: 16rpx;
|
||||
}
|
||||
}
|
||||
/* app.wxss - 全局样式 */
|
||||
|
||||
/* 全局重置 */
|
||||
page {
|
||||
background-color: #f6f6f6;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 容器样式 */
|
||||
.container {
|
||||
padding: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
margin: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 通用工具类 */
|
||||
.text-center { text-align: center; }
|
||||
.text-left { text-align: left; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
.mt-8 { margin-top: 8rpx; }
|
||||
.mt-16 { margin-top: 16rpx; }
|
||||
.mt-24 { margin-top: 24rpx; }
|
||||
.mt-32 { margin-top: 32rpx; }
|
||||
.mb-8 { margin-bottom: 8rpx; }
|
||||
.mb-16 { margin-bottom: 16rpx; }
|
||||
.mb-24 { margin-bottom: 24rpx; }
|
||||
.mb-32 { margin-bottom: 32rpx; }
|
||||
|
||||
.pt-8 { padding-top: 8rpx; }
|
||||
.pt-16 { padding-top: 16rpx; }
|
||||
.pt-24 { padding-top: 24rpx; }
|
||||
.pt-32 { padding-top: 32rpx; }
|
||||
.pb-8 { padding-bottom: 8rpx; }
|
||||
.pb-16 { padding-bottom: 16rpx; }
|
||||
.pb-24 { padding-bottom: 24rpx; }
|
||||
.pb-32 { padding-bottom: 32rpx; }
|
||||
|
||||
.p-8 { padding: 8rpx; }
|
||||
.p-16 { padding: 16rpx; }
|
||||
.p-24 { padding: 24rpx; }
|
||||
.p-32 { padding: 32rpx; }
|
||||
|
||||
.m-8 { margin: 8rpx; }
|
||||
.m-16 { margin: 16rpx; }
|
||||
.m-24 { margin: 24rpx; }
|
||||
.m-32 { margin: 32rpx; }
|
||||
|
||||
/* Flex布局 */
|
||||
.flex { display: flex; }
|
||||
.flex-column { flex-direction: column; }
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
.justify-center { justify-content: center; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.justify-start { justify-content: flex-start; }
|
||||
.align-center { align-items: center; }
|
||||
.align-start { align-items: flex-start; }
|
||||
.align-end { align-items: flex-end; }
|
||||
|
||||
/* 状态颜色 */
|
||||
.status-normal { color: #52c41a; }
|
||||
.status-pregnant { color: #faad14; }
|
||||
.status-sick { color: #f5222d; }
|
||||
.status-quarantine { color: #909399; }
|
||||
|
||||
/* 主题颜色 */
|
||||
.color-primary { color: #3cc51f; }
|
||||
.color-success { color: #52c41a; }
|
||||
.color-warning { color: #faad14; }
|
||||
.color-danger { color: #f5222d; }
|
||||
.color-info { color: #1890ff; }
|
||||
|
||||
.bg-primary { background-color: #3cc51f; }
|
||||
.bg-success { background-color: #52c41a; }
|
||||
.bg-warning { background-color: #faad14; }
|
||||
.bg-danger { background-color: #f5222d; }
|
||||
.bg-info { background-color: #1890ff; }
|
||||
|
||||
/* 文字颜色 */
|
||||
.text-primary { color: #303133; }
|
||||
.text-regular { color: #606266; }
|
||||
.text-secondary { color: #909399; }
|
||||
.text-placeholder { color: #c0c4cc; }
|
||||
|
||||
/* 背景颜色 */
|
||||
.bg-white { background-color: #ffffff; }
|
||||
.bg-gray { background-color: #f5f5f5; }
|
||||
.bg-light { background-color: #fafafa; }
|
||||
|
||||
/* 边框 */
|
||||
.border { border: 1rpx solid #dcdfe6; }
|
||||
.border-top { border-top: 1rpx solid #dcdfe6; }
|
||||
.border-bottom { border-bottom: 1rpx solid #dcdfe6; }
|
||||
.border-left { border-left: 1rpx solid #dcdfe6; }
|
||||
.border-right { border-right: 1rpx solid #dcdfe6; }
|
||||
|
||||
/* 圆角 */
|
||||
.rounded { border-radius: 4rpx; }
|
||||
.rounded-sm { border-radius: 2rpx; }
|
||||
.rounded-lg { border-radius: 8rpx; }
|
||||
.rounded-xl { border-radius: 12rpx; }
|
||||
.rounded-full { border-radius: 50%; }
|
||||
|
||||
/* 阴影 */
|
||||
.shadow { box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1); }
|
||||
.shadow-sm { box-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.05); }
|
||||
.shadow-lg { box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.15); }
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-primary:active {
|
||||
background-color: #2ea617;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #52c41a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-success:active {
|
||||
background-color: #389e0d;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background-color: #faad14;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-warning:active {
|
||||
background-color: #d48806;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #f5222d;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-danger:active {
|
||||
background-color: #cf1322;
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
background-color: #ffffff;
|
||||
color: #303133;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
}
|
||||
|
||||
.btn-default:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 8rpx 16rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.btn-large {
|
||||
padding: 24rpx 48rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 24rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
/* 列表样式 */
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.list-item:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 64rpx 32rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64rpx;
|
||||
margin-bottom: 16rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.container {
|
||||
margin: 8rpx;
|
||||
padding: 12rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12rpx 24rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.card-header,
|
||||
.card-body,
|
||||
.card-footer {
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
padding: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
48
mini_program/farm-monitor-dashboard/config/env.js
Normal file
48
mini_program/farm-monitor-dashboard/config/env.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// config/env.js - 环境配置文件
|
||||
|
||||
/**
|
||||
* 环境配置
|
||||
* 可以根据不同环境切换API地址
|
||||
*/
|
||||
|
||||
// 开发环境
|
||||
const development = {
|
||||
apiBaseUrl: 'https://ad.ningmuyun.com/farm/api',
|
||||
uploadUrl: 'https://ad.ningmuyun.com/farm/api/upload',
|
||||
imageBaseUrl: 'https://ad.ningmuyun.com/farm',
|
||||
timeout: 10000,
|
||||
debug: true
|
||||
}
|
||||
|
||||
// 测试环境
|
||||
const testing = {
|
||||
apiBaseUrl: 'https://test.ningmuyun.com/farm/api',
|
||||
uploadUrl: 'https://test.ningmuyun.com/farm/api/upload',
|
||||
imageBaseUrl: 'https://test.ningmuyun.com/farm',
|
||||
timeout: 10000,
|
||||
debug: true
|
||||
}
|
||||
|
||||
// 生产环境
|
||||
const production = {
|
||||
apiBaseUrl: 'https://ad.ningmuyun.com/farm/api',
|
||||
uploadUrl: 'https://ad.ningmuyun.com/farm/api/upload',
|
||||
imageBaseUrl: 'https://ad.ningmuyun.com/farm',
|
||||
timeout: 10000,
|
||||
debug: false
|
||||
}
|
||||
|
||||
// 当前环境配置
|
||||
// 可以通过修改这里来切换环境
|
||||
const currentEnv = 'development' // development | testing | production
|
||||
|
||||
// 环境映射
|
||||
const envMap = {
|
||||
development,
|
||||
testing,
|
||||
production
|
||||
}
|
||||
|
||||
// 导出当前环境配置
|
||||
module.exports = envMap[currentEnv]
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 背景圆形 -->
|
||||
<circle cx="15" cy="15" r="14" fill="#7CB342" stroke="#ffffff" stroke-width="2"/>
|
||||
|
||||
<!-- 围栏图标 -->
|
||||
<g transform="translate(7, 7)">
|
||||
<!-- 围栏柱子 -->
|
||||
<rect x="2" y="4" width="1.5" height="12" fill="#ffffff"/>
|
||||
<rect x="6" y="4" width="1.5" height="12" fill="#ffffff"/>
|
||||
<rect x="10" y="4" width="1.5" height="12" fill="#ffffff"/>
|
||||
<rect x="14" y="4" width="1.5" height="12" fill="#ffffff"/>
|
||||
|
||||
<!-- 围栏横条 -->
|
||||
<rect x="1" y="7" width="15" height="1" fill="#ffffff"/>
|
||||
<rect x="1" y="10" width="15" height="1" fill="#ffffff"/>
|
||||
<rect x="1" y="13" width="15" height="1" fill="#ffffff"/>
|
||||
</g>
|
||||
|
||||
<!-- 阴影效果 -->
|
||||
<circle cx="15" cy="16" r="12" fill="rgba(0,0,0,0.1)" opacity="0.3"/>
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 背景圆形 -->
|
||||
<circle cx="15" cy="15" r="14" fill="#7CB342" stroke="#ffffff" stroke-width="2"/>
|
||||
|
||||
<!-- 围栏图标 -->
|
||||
<g transform="translate(7, 7)">
|
||||
<!-- 围栏柱子 -->
|
||||
<rect x="2" y="4" width="1.5" height="12" fill="#ffffff"/>
|
||||
<rect x="6" y="4" width="1.5" height="12" fill="#ffffff"/>
|
||||
<rect x="10" y="4" width="1.5" height="12" fill="#ffffff"/>
|
||||
<rect x="14" y="4" width="1.5" height="12" fill="#ffffff"/>
|
||||
|
||||
<!-- 围栏横条 -->
|
||||
<rect x="1" y="7" width="15" height="1" fill="#ffffff"/>
|
||||
<rect x="1" y="10" width="15" height="1" fill="#ffffff"/>
|
||||
<rect x="1" y="13" width="15" height="1" fill="#ffffff"/>
|
||||
</g>
|
||||
|
||||
<!-- 阴影效果 -->
|
||||
<circle cx="15" cy="16" r="12" fill="rgba(0,0,0,0.1)" opacity="0.3"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 873 B After Width: | Height: | Size: 893 B |
234
mini_program/farm-monitor-dashboard/images/图标资源获取指南.md
Normal file
234
mini_program/farm-monitor-dashboard/images/图标资源获取指南.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# 首页图标资源获取指南
|
||||
|
||||
## 📌 需要的图标列表
|
||||
|
||||
根据首页UI设计,需要准备以下13个图标文件:
|
||||
|
||||
### 智能设备图标(5个)
|
||||
1. ✅ `icon-collar.png` - 智能项圈图标
|
||||
2. ✅ `icon-eartag.png` - 智能耳标图标
|
||||
3. ✅ `icon-anklet.png` - 智能脚环图标
|
||||
4. ✅ `icon-host.png` - 智能主机图标
|
||||
5. ✅ `icon-monitor.png` - 视频监控图标
|
||||
|
||||
### 智能工具图标(4个)
|
||||
6. ✅ `icon-fence.png` - 电子围栏图标
|
||||
7. ✅ `icon-scan.png` - 扫码溯源图标
|
||||
8. ✅ `icon-check.png` - 检测工具图标
|
||||
9. ✅ `icon-photo.png` - 档案拍照图标
|
||||
|
||||
### 业务办理图标(3个)
|
||||
10. ✅ `icon-quarantine.png` - 电子检疫图标
|
||||
11. ✅ `icon-rights.png` - 电子确权图标
|
||||
12. ✅ `icon-disposal.png` - 无害化处理申报图标
|
||||
|
||||
---
|
||||
|
||||
## 🎨 图标规格要求
|
||||
|
||||
### 尺寸
|
||||
- **推荐尺寸**:120x120 像素
|
||||
- **最小尺寸**:60x60 像素
|
||||
- **最大尺寸**:200x200 像素
|
||||
|
||||
### 格式
|
||||
- **格式**:PNG(推荐,支持透明)
|
||||
- **备选**:SVG(矢量图,可缩放)
|
||||
|
||||
### 风格
|
||||
- **图标风格**:扁平化、线性或面性图标
|
||||
- **颜色**:单色或双色(与背景渐变色搭配)
|
||||
- **背景**:透明背景
|
||||
|
||||
### 文件大小
|
||||
- 每个图标文件不超过50KB
|
||||
|
||||
---
|
||||
|
||||
## 🔍 图标获取途径
|
||||
|
||||
### 方法一:使用在线图标库(推荐)
|
||||
|
||||
#### 1. 阿里巴巴矢量图标库(iconfont)
|
||||
🔗 https://www.iconfont.cn/
|
||||
|
||||
**步骤:**
|
||||
1. 注册/登录账号
|
||||
2. 搜索对应的图标关键词
|
||||
3. 选择合适的图标
|
||||
4. 下载PNG格式(120x120)
|
||||
5. 重命名为对应的文件名
|
||||
|
||||
**推荐搜索关键词:**
|
||||
- 项圈:collar, necklace, tag
|
||||
- 耳标:tag, label, eartag
|
||||
- 脚环:anklet, tracker
|
||||
- 主机:server, host, gateway
|
||||
- 监控:monitor, camera, video
|
||||
- 围栏:fence, boundary
|
||||
- 扫码:scan, qrcode, barcode
|
||||
- 检测:check, test, detect
|
||||
- 拍照:camera, photo
|
||||
- 检疫:quarantine, inspection
|
||||
- 确权:certificate, rights
|
||||
- 处理:recycle, disposal
|
||||
|
||||
#### 2. IconPark(字节跳动)
|
||||
🔗 https://iconpark.oceanengine.com/
|
||||
|
||||
**特点:**
|
||||
- 高质量图标
|
||||
- 可自定义颜色
|
||||
- 支持多种格式下载
|
||||
|
||||
#### 3. Flaticon
|
||||
🔗 https://www.flaticon.com/
|
||||
|
||||
**特点:**
|
||||
- 海量免费图标
|
||||
- 支持PNG、SVG格式
|
||||
- 需要注明来源
|
||||
|
||||
#### 4. IconFinder
|
||||
🔗 https://www.iconfinder.com/
|
||||
|
||||
**特点:**
|
||||
- 免费+付费图标
|
||||
- 质量高
|
||||
- 可按风格筛选
|
||||
|
||||
---
|
||||
|
||||
### 方法二:设计师制作
|
||||
|
||||
如果您有设计师,可以根据以下参考制作图标:
|
||||
|
||||
**智能项圈**:圆形项圈 + 信号图标
|
||||
**智能耳标**:标签形状 + 数字/信号
|
||||
**智能脚环**:圆环 + 定位图标
|
||||
**智能主机**:服务器/主机图标
|
||||
**视频监控**:摄像头图标
|
||||
**电子围栏**:围栏/边界图标
|
||||
**扫码溯源**:二维码/扫描图标
|
||||
**检测工具**:检查/测试图标
|
||||
**档案拍照**:相机图标
|
||||
**电子检疫**:文档+检查图标
|
||||
**电子确权**:证书/文档图标
|
||||
**无害化处理**:回收/处理图标
|
||||
|
||||
---
|
||||
|
||||
### 方法三:使用Emoji(临时方案)
|
||||
|
||||
如果暂时没有图标资源,可以先用Emoji代替(已在代码中实现):
|
||||
|
||||
当前使用的Emoji:
|
||||
- 🎯 智能项圈
|
||||
- 🏷️ 智能耳标
|
||||
- 📍 智能脚环
|
||||
- 📡 智能主机
|
||||
- 📹 视频监控
|
||||
- ⭕ 电子围栏
|
||||
- ✓ 扫码溯源
|
||||
- 📊 检测工具
|
||||
- 📷 档案拍照
|
||||
- 📋 电子检疫
|
||||
- 📝 电子确权
|
||||
- ♻️ 无害化处理申报
|
||||
|
||||
**如需使用Emoji,请修改代码:**
|
||||
将 `<image>` 标签改为 `<text>` 标签即可。
|
||||
|
||||
---
|
||||
|
||||
## 📦 下载后的操作步骤
|
||||
|
||||
### 1. 重命名文件
|
||||
按照上述文件名列表,将下载的图标重命名。
|
||||
|
||||
### 2. 放置图标
|
||||
将所有图标文件复制到:
|
||||
```
|
||||
mini_program/farm-monitor-dashboard/images/
|
||||
```
|
||||
|
||||
### 3. 验证图标
|
||||
在微信开发者工具中:
|
||||
1. 打开首页
|
||||
2. 检查所有图标是否正常显示
|
||||
3. 如有缺失,检查文件名是否正确
|
||||
|
||||
---
|
||||
|
||||
## 🎨 配色建议
|
||||
|
||||
虽然图标可以是单色的(代码中已设置背景色),但如果想要更好的视觉效果,建议图标颜色与背景色协调:
|
||||
|
||||
| 功能 | 背景色 | 建议图标色 |
|
||||
|------|--------|-----------|
|
||||
| 智能项圈 | 黄色渐变 | 深橙色 #FF9800 |
|
||||
| 智能耳标 | 蓝色渐变 | 深蓝色 #2196F3 |
|
||||
| 智能脚环 | 紫色渐变 | 深紫色 #9C27B0 |
|
||||
| 智能主机 | 浅蓝渐变 | 蓝色 #03A9F4 |
|
||||
| 视频监控 | 橙色渐变 | 深橙色 #FF9800 |
|
||||
| 电子围栏 | 黄橙渐变 | 橙色 #FFA726 |
|
||||
| 扫码溯源 | 蓝色渐变 | 深蓝色 #5C6BC0 |
|
||||
| 检测工具 | 紫色渐变 | 紫色 #7E57C2 |
|
||||
| 档案拍照 | 红色渐变 | 红色 #EF5350 |
|
||||
| 电子检疫 | 黄色渐变 | 金色 #FDD835 |
|
||||
| 电子确权 | 青色渐变 | 青色 #26A69A |
|
||||
| 无害化处理 | 绿紫渐变 | 绿色 #66BB6A |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始建议
|
||||
|
||||
**如果急于上线,建议:**
|
||||
|
||||
1. **方案A**:先用Emoji(已实现)
|
||||
- 优点:立即可用
|
||||
- 缺点:视觉效果一般
|
||||
|
||||
2. **方案B**:快速下载iconfont图标
|
||||
- 时间:约30分钟
|
||||
- 质量:中等
|
||||
- 适合:快速原型
|
||||
|
||||
3. **方案C**:请设计师定制
|
||||
- 时间:1-3天
|
||||
- 质量:高
|
||||
- 适合:正式发布
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **版权问题**:
|
||||
- 使用免费图标库时,注意查看授权协议
|
||||
- 商用项目建议购买商业授权或定制
|
||||
|
||||
2. **文件命名**:
|
||||
- 必须与代码中的文件名完全一致
|
||||
- 区分大小写
|
||||
|
||||
3. **图片优化**:
|
||||
- 使用TinyPNG等工具压缩图片
|
||||
- 减少小程序包体积
|
||||
|
||||
4. **测试**:
|
||||
- 在不同设备上测试显示效果
|
||||
- 确保图标清晰不模糊
|
||||
|
||||
---
|
||||
|
||||
## 📞 需要帮助?
|
||||
|
||||
如果您在获取或使用图标时遇到问题,可以:
|
||||
1. 联系设计团队
|
||||
2. 查看微信小程序官方文档
|
||||
3. 在项目群中询问
|
||||
|
||||
---
|
||||
|
||||
**更新日期**:2025-01-09
|
||||
|
||||
70
mini_program/farm-monitor-dashboard/images/地图标记图标说明.md
Normal file
70
mini_program/farm-monitor-dashboard/images/地图标记图标说明.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# 地图标记图标说明
|
||||
|
||||
## 📍 所需图标
|
||||
|
||||
电子围栏地图功能需要以下图标文件:
|
||||
|
||||
### marker.png
|
||||
**路径:** `/images/marker.png`
|
||||
|
||||
**用途:** 地图上的围栏中心点标记
|
||||
|
||||
**建议规格:**
|
||||
- 尺寸:60x60 像素(或更大,保持1:1比例)
|
||||
- 格式:PNG(支持透明背景)
|
||||
- 颜色:建议使用绿色或红色(匹配围栏状态)
|
||||
|
||||
**图标样式建议:**
|
||||
- 📍 红色定位针图标
|
||||
- 🔴 实心圆点标记
|
||||
- 🟢 绿色标记点
|
||||
|
||||
---
|
||||
|
||||
## 🎨 获取图标的方法
|
||||
|
||||
### 方法1:使用在线图标库
|
||||
1. 访问 [Iconfont](https://www.iconfont.cn/) 或 [IconPark](https://iconpark.oceanengine.com/)
|
||||
2. 搜索 "地图标记" 或 "定位"
|
||||
3. 选择喜欢的图标
|
||||
4. 下载PNG格式(60x60或更大)
|
||||
5. 重命名为 `marker.png`
|
||||
6. 放置到 `images/` 目录
|
||||
|
||||
### 方法2:使用系统Emoji(临时方案)
|
||||
代码中已使用Emoji作为临时标记,无需额外图标文件。
|
||||
|
||||
如果图标文件不存在,将使用默认的地图气泡标记。
|
||||
|
||||
---
|
||||
|
||||
## 🔄 替代方案
|
||||
|
||||
如果没有图标文件,可以修改代码使用默认标记:
|
||||
|
||||
```javascript
|
||||
// 在 fence.js 中修改 marker 配置
|
||||
const marker = {
|
||||
id: fence.id,
|
||||
latitude: parseFloat(fence.center.lat),
|
||||
longitude: parseFloat(fence.center.lng),
|
||||
title: fence.name,
|
||||
// iconPath: '/images/marker.png', // 注释掉这行
|
||||
width: 30,
|
||||
height: 30,
|
||||
callout: {
|
||||
content: `${fence.name}\n${fence.type}`,
|
||||
fontSize: 12,
|
||||
borderRadius: 5,
|
||||
padding: 8,
|
||||
display: 'ALWAYS'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这样将使用微信地图的默认红色气泡标记。
|
||||
|
||||
---
|
||||
|
||||
**说明:** 当前代码使用了 `/images/marker.png`,如果该文件不存在,地图标记仍然会显示,只是使用默认样式。
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
{
|
||||
"name": "养殖端小程序",
|
||||
"appid": "wx-your-appid-here",
|
||||
"description": "养殖管理系统微信小程序",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
"mp-weixin": {
|
||||
"appid": "wx-your-appid-here",
|
||||
"setting": {
|
||||
"urlCheck": false,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true
|
||||
},
|
||||
"usingComponents": true,
|
||||
"permission": {
|
||||
"scope.userLocation": {
|
||||
"desc": "你的位置信息将用于养殖场定位和地图展示"
|
||||
}
|
||||
},
|
||||
"optimization": {
|
||||
"subPackages": true
|
||||
}
|
||||
},
|
||||
"vueVersion": "3",
|
||||
"splashscreen": {
|
||||
"alwaysShowBeforeRender": true,
|
||||
"autoclose": false,
|
||||
"waiting": true
|
||||
},
|
||||
"app-plus": {
|
||||
"usingComponents": true,
|
||||
"nvueStyle": "flex",
|
||||
"compilerVersion": 3,
|
||||
"splashscreen": {
|
||||
"alwaysShowBeforeRender": true,
|
||||
"autoclose": false,
|
||||
"waiting": true,
|
||||
"delay": 0
|
||||
},
|
||||
"modules": {},
|
||||
"distribute": {
|
||||
"android": {
|
||||
"permissions": [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_LOCATION_EXTRA_COMMANDS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.INTERNET\"/>"
|
||||
]
|
||||
},
|
||||
"ios": {}
|
||||
}
|
||||
},
|
||||
"quickapp": {},
|
||||
"h5": {
|
||||
"router": {
|
||||
"mode": "hash"
|
||||
},
|
||||
"optimization": {
|
||||
"treeShaking": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "养殖端小程序",
|
||||
"appid": "wx-your-appid-here",
|
||||
"description": "养殖管理系统微信小程序",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
"mp-weixin": {
|
||||
"appid": "wx-your-appid-here",
|
||||
"setting": {
|
||||
"urlCheck": false,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true
|
||||
},
|
||||
"usingComponents": true,
|
||||
"permission": {
|
||||
"scope.userLocation": {
|
||||
"desc": "你的位置信息将用于养殖场定位和地图展示"
|
||||
}
|
||||
},
|
||||
"optimization": {
|
||||
"subPackages": true
|
||||
}
|
||||
},
|
||||
"vueVersion": "3",
|
||||
"splashscreen": {
|
||||
"alwaysShowBeforeRender": true,
|
||||
"autoclose": false,
|
||||
"waiting": true
|
||||
},
|
||||
"app-plus": {
|
||||
"usingComponents": true,
|
||||
"nvueStyle": "flex",
|
||||
"compilerVersion": 3,
|
||||
"splashscreen": {
|
||||
"alwaysShowBeforeRender": true,
|
||||
"autoclose": false,
|
||||
"waiting": true,
|
||||
"delay": 0
|
||||
},
|
||||
"modules": {},
|
||||
"distribute": {
|
||||
"android": {
|
||||
"permissions": [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_LOCATION_EXTRA_COMMANDS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.INTERNET\"/>"
|
||||
]
|
||||
},
|
||||
"ios": {}
|
||||
}
|
||||
},
|
||||
"quickapp": {},
|
||||
"h5": {
|
||||
"router": {
|
||||
"mode": "hash"
|
||||
},
|
||||
"optimization": {
|
||||
"treeShaking": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +1,141 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "养殖管理",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f6f6f6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录",
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/cattle/cattle",
|
||||
"style": {
|
||||
"navigationBarTitleText": "牛只管理",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f6f6f6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/cattle-detail/cattle-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "牛只详情",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/farms/farms",
|
||||
"style": {
|
||||
"navigationBarTitleText": "养殖场管理",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/farm-detail/farm-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "养殖场详情",
|
||||
"navigationBarBackgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/growth/growth",
|
||||
"style": {
|
||||
"navigationBarTitleText": "生长数据",
|
||||
"navigationBarBackgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/events/events",
|
||||
"style": {
|
||||
"navigationBarTitleText": "事件记录",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/stats/stats",
|
||||
"style": {
|
||||
"navigationBarTitleText": "统计分析",
|
||||
"navigationBarBackgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/my/my",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人中心",
|
||||
"navigationBarBackgroundColor": "#ffffff"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#1890ff",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "static/tabbar/home.png",
|
||||
"selectedIconPath": "static/tabbar/home-active.png",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/cattle/cattle",
|
||||
"iconPath": "static/tabbar/cattle.png",
|
||||
"selectedIconPath": "static/tabbar/cattle-active.png",
|
||||
"text": "牛只"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/farms/farms",
|
||||
"iconPath": "static/tabbar/farm.png",
|
||||
"selectedIconPath": "static/tabbar/farm-active.png",
|
||||
"text": "养殖场"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/my/my",
|
||||
"iconPath": "static/tabbar/my.png",
|
||||
"selectedIconPath": "static/tabbar/my-active.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "养殖管理系统",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"backgroundColor": "#f6f6f6",
|
||||
"app-plus": {
|
||||
"background": "#efeff4"
|
||||
}
|
||||
},
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
|
||||
"^van-(.*)": "@vant/weapp/dist/$1/index"
|
||||
}
|
||||
},
|
||||
"condition": {
|
||||
"current": 0,
|
||||
"list": [
|
||||
{
|
||||
"name": "牛只详情",
|
||||
"path": "pages/cattle-detail/cattle-detail",
|
||||
"query": "id=1"
|
||||
},
|
||||
{
|
||||
"name": "设备详情",
|
||||
"path": "pages/device-detail/device-detail",
|
||||
"query": "id=1"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "养殖管理",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f6f6f6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录",
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/cattle/cattle",
|
||||
"style": {
|
||||
"navigationBarTitleText": "牛只管理",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f6f6f6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/cattle-detail/cattle-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "牛只详情",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/farms/farms",
|
||||
"style": {
|
||||
"navigationBarTitleText": "养殖场管理",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/farm-detail/farm-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "养殖场详情",
|
||||
"navigationBarBackgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/growth/growth",
|
||||
"style": {
|
||||
"navigationBarTitleText": "生长数据",
|
||||
"navigationBarBackgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/events/events",
|
||||
"style": {
|
||||
"navigationBarTitleText": "事件记录",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/stats/stats",
|
||||
"style": {
|
||||
"navigationBarTitleText": "统计分析",
|
||||
"navigationBarBackgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/my/my",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人中心",
|
||||
"navigationBarBackgroundColor": "#ffffff"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#1890ff",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "static/tabbar/home.png",
|
||||
"selectedIconPath": "static/tabbar/home-active.png",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/cattle/cattle",
|
||||
"iconPath": "static/tabbar/cattle.png",
|
||||
"selectedIconPath": "static/tabbar/cattle-active.png",
|
||||
"text": "牛只"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/farms/farms",
|
||||
"iconPath": "static/tabbar/farm.png",
|
||||
"selectedIconPath": "static/tabbar/farm-active.png",
|
||||
"text": "养殖场"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/my/my",
|
||||
"iconPath": "static/tabbar/my.png",
|
||||
"selectedIconPath": "static/tabbar/my-active.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "养殖管理系统",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"backgroundColor": "#f6f6f6",
|
||||
"app-plus": {
|
||||
"background": "#efeff4"
|
||||
}
|
||||
},
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
|
||||
"^van-(.*)": "@vant/weapp/dist/$1/index"
|
||||
}
|
||||
},
|
||||
"condition": {
|
||||
"current": 0,
|
||||
"list": [
|
||||
{
|
||||
"name": "牛只详情",
|
||||
"path": "pages/cattle-detail/cattle-detail",
|
||||
"query": "id=1"
|
||||
},
|
||||
{
|
||||
"name": "设备详情",
|
||||
"path": "pages/device-detail/device-detail",
|
||||
"query": "id=1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "告警信息",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f6f6f6",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,176 +1,176 @@
|
||||
<!--pages/alert/alert.wxml-->
|
||||
<view class="alert-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input-wrapper">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="搜索预警内容..."
|
||||
value="{{searchKeyword}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="onSearch"
|
||||
/>
|
||||
<text class="search-icon" bindtap="onSearch">🔍</text>
|
||||
</view>
|
||||
<text wx:if="{{searchKeyword}}" class="clear-btn" bindtap="onClearSearch">清空</text>
|
||||
</view>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<!-- 预警类型筛选 -->
|
||||
<view class="filter-group">
|
||||
<view class="filter-label">类型:</view>
|
||||
<view class="filter-options">
|
||||
<view
|
||||
class="filter-option {{typeFilter === 'all' ? 'active' : ''}}"
|
||||
bindtap="onTypeFilter"
|
||||
data-type="all"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
wx:for="{{alertTypes}}"
|
||||
wx:key="value"
|
||||
class="filter-option {{typeFilter === item.value ? 'active' : ''}}"
|
||||
bindtap="onTypeFilter"
|
||||
data-type="{{item.value}}"
|
||||
>
|
||||
{{item.icon}} {{item.label}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 状态筛选 -->
|
||||
<view class="filter-group">
|
||||
<view class="filter-label">状态:</view>
|
||||
<view class="filter-options">
|
||||
<view
|
||||
class="filter-option {{statusFilter === 'all' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="all"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
wx:for="{{alertStatuses}}"
|
||||
wx:key="value"
|
||||
class="filter-option {{statusFilter === item.value ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="{{item.value}}"
|
||||
>
|
||||
{{item.label}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 批量操作栏 -->
|
||||
<view wx:if="{{alertList.length > 0}}" class="batch-actions">
|
||||
<button class="batch-btn" bindtap="batchHandleAlerts">批量处理</button>
|
||||
</view>
|
||||
|
||||
<!-- 预警列表 -->
|
||||
<view class="alert-list">
|
||||
<view
|
||||
wx:for="{{alertList}}"
|
||||
wx:key="id"
|
||||
class="alert-item"
|
||||
bindtap="viewAlertDetail"
|
||||
data-id="{{item.id}}"
|
||||
data-type="{{item.type}}"
|
||||
>
|
||||
<view class="alert-icon">
|
||||
<text class="icon">{{getAlertTypeInfo(item.type).icon}}</text>
|
||||
</view>
|
||||
|
||||
<view class="alert-info">
|
||||
<view class="alert-title">{{item.title}}</view>
|
||||
<view class="alert-content">{{item.content}}</view>
|
||||
<view class="alert-meta">
|
||||
<text class="meta-item">设备: {{item.deviceName || '未知'}}</text>
|
||||
<text class="meta-item">时间: {{formatTime(item.createTime)}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="alert-status">
|
||||
<view
|
||||
class="priority-badge"
|
||||
style="background-color: {{getPriorityColor(item.priority)}}"
|
||||
>
|
||||
{{getPriorityText(item.priority)}}
|
||||
</view>
|
||||
<view
|
||||
class="status-badge"
|
||||
style="background-color: {{getStatusInfo(item.status).color}}"
|
||||
>
|
||||
{{getStatusInfo(item.status).label}}
|
||||
</view>
|
||||
<view class="alert-actions">
|
||||
<text
|
||||
wx:if="{{item.status === 'pending'}}"
|
||||
class="action-btn handle"
|
||||
bindtap="handleAlert"
|
||||
data-id="{{item.id}}"
|
||||
data-type="{{item.type}}"
|
||||
catchtap="true"
|
||||
>
|
||||
处理
|
||||
</text>
|
||||
<text
|
||||
wx:if="{{item.status === 'pending'}}"
|
||||
class="action-btn ignore"
|
||||
bindtap="ignoreAlert"
|
||||
data-id="{{item.id}}"
|
||||
data-type="{{item.type}}"
|
||||
catchtap="true"
|
||||
>
|
||||
忽略
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view wx:if="{{alertList.length === 0 && !loading}}" class="empty-state">
|
||||
<text class="empty-icon">🚨</text>
|
||||
<text class="empty-text">暂无预警数据</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view wx:if="{{hasMore && alertList.length > 0}}" class="load-more">
|
||||
<text wx:if="{{loading}}">加载中...</text>
|
||||
<text wx:else>上拉加载更多</text>
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据 -->
|
||||
<view wx:if="{{!hasMore && alertList.length > 0}}" class="no-more">
|
||||
<text>没有更多数据了</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading && alertList.length === 0}}" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<view wx:if="{{detailVisible}}" class="detail-mask" catchtouchmove="true">
|
||||
<view class="detail-panel">
|
||||
<view class="detail-header">
|
||||
<text class="detail-title">预警详情</text>
|
||||
<text class="detail-close" bindtap="closeDetail">✖</text>
|
||||
</view>
|
||||
<view class="detail-body">
|
||||
<view class="detail-row" wx:for="{{detailPairs}}" wx:key="label">
|
||||
<text class="detail-label">{{item.label}}</text>
|
||||
<text class="detail-value">{{item.value}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-footer">
|
||||
<button class="primary" bindtap="handleAlert" data-id="{{detailData.id}}" data-type="{{typeFilter === 'all' ? (detailData.alertType || 'eartag') : typeFilter}}">处理</button>
|
||||
<button class="plain" bindtap="ignoreAlert" data-id="{{detailData.id}}" data-type="{{typeFilter === 'all' ? (detailData.alertType || 'eartag') : typeFilter}}">忽略</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!--pages/alert/alert.wxml-->
|
||||
<view class="alert-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input-wrapper">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="搜索预警内容..."
|
||||
value="{{searchKeyword}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="onSearch"
|
||||
/>
|
||||
<text class="search-icon" bindtap="onSearch">🔍</text>
|
||||
</view>
|
||||
<text wx:if="{{searchKeyword}}" class="clear-btn" bindtap="onClearSearch">清空</text>
|
||||
</view>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<!-- 预警类型筛选 -->
|
||||
<view class="filter-group">
|
||||
<view class="filter-label">类型:</view>
|
||||
<view class="filter-options">
|
||||
<view
|
||||
class="filter-option {{typeFilter === 'all' ? 'active' : ''}}"
|
||||
bindtap="onTypeFilter"
|
||||
data-type="all"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
wx:for="{{alertTypes}}"
|
||||
wx:key="value"
|
||||
class="filter-option {{typeFilter === item.value ? 'active' : ''}}"
|
||||
bindtap="onTypeFilter"
|
||||
data-type="{{item.value}}"
|
||||
>
|
||||
{{item.icon}} {{item.label}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 状态筛选 -->
|
||||
<view class="filter-group">
|
||||
<view class="filter-label">状态:</view>
|
||||
<view class="filter-options">
|
||||
<view
|
||||
class="filter-option {{statusFilter === 'all' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="all"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
wx:for="{{alertStatuses}}"
|
||||
wx:key="value"
|
||||
class="filter-option {{statusFilter === item.value ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="{{item.value}}"
|
||||
>
|
||||
{{item.label}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 批量操作栏 -->
|
||||
<view wx:if="{{alertList.length > 0}}" class="batch-actions">
|
||||
<button class="batch-btn" bindtap="batchHandleAlerts">批量处理</button>
|
||||
</view>
|
||||
|
||||
<!-- 预警列表 -->
|
||||
<view class="alert-list">
|
||||
<view
|
||||
wx:for="{{alertList}}"
|
||||
wx:key="id"
|
||||
class="alert-item"
|
||||
bindtap="viewAlertDetail"
|
||||
data-id="{{item.id}}"
|
||||
data-type="{{item.type}}"
|
||||
>
|
||||
<view class="alert-icon">
|
||||
<text class="icon">{{getAlertTypeInfo(item.type).icon}}</text>
|
||||
</view>
|
||||
|
||||
<view class="alert-info">
|
||||
<view class="alert-title">{{item.title}}</view>
|
||||
<view class="alert-content">{{item.content}}</view>
|
||||
<view class="alert-meta">
|
||||
<text class="meta-item">设备: {{item.deviceName || '未知'}}</text>
|
||||
<text class="meta-item">时间: {{formatTime(item.createTime)}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="alert-status">
|
||||
<view
|
||||
class="priority-badge"
|
||||
style="background-color: {{getPriorityColor(item.priority)}}"
|
||||
>
|
||||
{{getPriorityText(item.priority)}}
|
||||
</view>
|
||||
<view
|
||||
class="status-badge"
|
||||
style="background-color: {{getStatusInfo(item.status).color}}"
|
||||
>
|
||||
{{getStatusInfo(item.status).label}}
|
||||
</view>
|
||||
<view class="alert-actions">
|
||||
<text
|
||||
wx:if="{{item.status === 'pending'}}"
|
||||
class="action-btn handle"
|
||||
bindtap="handleAlert"
|
||||
data-id="{{item.id}}"
|
||||
data-type="{{item.type}}"
|
||||
catchtap="true"
|
||||
>
|
||||
处理
|
||||
</text>
|
||||
<text
|
||||
wx:if="{{item.status === 'pending'}}"
|
||||
class="action-btn ignore"
|
||||
bindtap="ignoreAlert"
|
||||
data-id="{{item.id}}"
|
||||
data-type="{{item.type}}"
|
||||
catchtap="true"
|
||||
>
|
||||
忽略
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view wx:if="{{alertList.length === 0 && !loading}}" class="empty-state">
|
||||
<text class="empty-icon">🚨</text>
|
||||
<text class="empty-text">暂无预警数据</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view wx:if="{{hasMore && alertList.length > 0}}" class="load-more">
|
||||
<text wx:if="{{loading}}">加载中...</text>
|
||||
<text wx:else>上拉加载更多</text>
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据 -->
|
||||
<view wx:if="{{!hasMore && alertList.length > 0}}" class="no-more">
|
||||
<text>没有更多数据了</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading && alertList.length === 0}}" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<view wx:if="{{detailVisible}}" class="detail-mask" catchtouchmove="true">
|
||||
<view class="detail-panel">
|
||||
<view class="detail-header">
|
||||
<text class="detail-title">预警详情</text>
|
||||
<text class="detail-close" bindtap="closeDetail">✖</text>
|
||||
</view>
|
||||
<view class="detail-body">
|
||||
<view class="detail-row" wx:for="{{detailPairs}}" wx:key="label">
|
||||
<text class="detail-label">{{item.label}}</text>
|
||||
<text class="detail-value">{{item.value}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-footer">
|
||||
<button class="primary" bindtap="handleAlert" data-id="{{detailData.id}}" data-type="{{typeFilter === 'all' ? (detailData.alertType || 'eartag') : typeFilter}}">处理</button>
|
||||
<button class="plain" bindtap="ignoreAlert" data-id="{{detailData.id}}" data-type="{{typeFilter === 'all' ? (detailData.alertType || 'eartag') : typeFilter}}">忽略</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,380 +1,380 @@
|
||||
/* pages/alert/alert.wxss */
|
||||
.alert-container {
|
||||
background-color: #f6f6f6;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 60rpx 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
font-size: 28rpx;
|
||||
color: #3cc51f;
|
||||
padding: 8rpx 16rpx;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
background-color: #ffffff;
|
||||
padding: 16rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.filter-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 12rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.filter-option {
|
||||
padding: 8rpx 16rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
font-size: 22rpx;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.filter-option.active {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.batch-actions {
|
||||
padding: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.batch-btn {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
font-size: 24rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.batch-btn:active {
|
||||
background-color: #2ea617;
|
||||
}
|
||||
|
||||
.alert-list {
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.alert-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
background-color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s;
|
||||
border-left: 6rpx solid #f5222d;
|
||||
}
|
||||
|
||||
.alert-item:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background-color: #fff2f0;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.alert-icon .icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
flex: 1;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 12rpx;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.alert-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 22rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.alert-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.priority-badge {
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 18rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 18rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.alert-actions {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 20rpx;
|
||||
text-align: center;
|
||||
min-width: 50rpx;
|
||||
}
|
||||
|
||||
.action-btn.handle {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.action-btn.ignore {
|
||||
background-color: #f5f5f5;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 120rpx 32rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
display: block;
|
||||
margin-bottom: 24rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 详情弹窗样式 */
|
||||
.detail-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.45);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
width: 100%;
|
||||
max-height: 70vh;
|
||||
background: #fff;
|
||||
border-top-left-radius: 24rpx;
|
||||
border-top-right-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-title { font-size: 32rpx; font-weight: 600; color: #303133; }
|
||||
.detail-close { font-size: 32rpx; color: #909399; }
|
||||
|
||||
.detail-body {
|
||||
padding: 16rpx 24rpx;
|
||||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx dashed #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-label { font-size: 26rpx; color: #606266; }
|
||||
.detail-value { font-size: 26rpx; color: #303133; }
|
||||
|
||||
.detail-footer {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
padding: 24rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-footer .primary {
|
||||
flex: 1;
|
||||
background-color: #3cc51f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.detail-footer .plain {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 120rpx 32rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 16rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.alert-item {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.alert-icon .icon {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
}
|
||||
/* pages/alert/alert.wxss */
|
||||
.alert-container {
|
||||
background-color: #f6f6f6;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 60rpx 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
font-size: 28rpx;
|
||||
color: #3cc51f;
|
||||
padding: 8rpx 16rpx;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
background-color: #ffffff;
|
||||
padding: 16rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.filter-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 12rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.filter-option {
|
||||
padding: 8rpx 16rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
font-size: 22rpx;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.filter-option.active {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.batch-actions {
|
||||
padding: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.batch-btn {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
font-size: 24rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.batch-btn:active {
|
||||
background-color: #2ea617;
|
||||
}
|
||||
|
||||
.alert-list {
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.alert-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
background-color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s;
|
||||
border-left: 6rpx solid #f5222d;
|
||||
}
|
||||
|
||||
.alert-item:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background-color: #fff2f0;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.alert-icon .icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
flex: 1;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 12rpx;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.alert-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 22rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.alert-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.priority-badge {
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 18rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 18rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.alert-actions {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 20rpx;
|
||||
text-align: center;
|
||||
min-width: 50rpx;
|
||||
}
|
||||
|
||||
.action-btn.handle {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.action-btn.ignore {
|
||||
background-color: #f5f5f5;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 120rpx 32rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
display: block;
|
||||
margin-bottom: 24rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 详情弹窗样式 */
|
||||
.detail-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.45);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
width: 100%;
|
||||
max-height: 70vh;
|
||||
background: #fff;
|
||||
border-top-left-radius: 24rpx;
|
||||
border-top-right-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-title { font-size: 32rpx; font-weight: 600; color: #303133; }
|
||||
.detail-close { font-size: 32rpx; color: #909399; }
|
||||
|
||||
.detail-body {
|
||||
padding: 16rpx 24rpx;
|
||||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx dashed #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-label { font-size: 26rpx; color: #606266; }
|
||||
.detail-value { font-size: 26rpx; color: #303133; }
|
||||
|
||||
.detail-footer {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
padding: 24rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-footer .primary {
|
||||
flex: 1;
|
||||
background-color: #3cc51f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.detail-footer .plain {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 120rpx 32rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 16rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.alert-item {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.alert-icon .icon {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
// pages/alert/collar-alert-detail/collar-alert-detail.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
alertId: null,
|
||||
alertData: [],
|
||||
loading: true
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
console.log('预警详情页面加载,参数:', options)
|
||||
|
||||
if (options.id) {
|
||||
this.setData({ alertId: options.id })
|
||||
this.loadAlertDetail(options.id)
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '参数错误',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载预警详情
|
||||
*/
|
||||
async loadAlertDetail(id) {
|
||||
try {
|
||||
this.setData({ loading: true })
|
||||
|
||||
console.log('获取预警详情,ID:', id)
|
||||
const res = await API.getCollarAlertDetail(id)
|
||||
|
||||
console.log('预警详情数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
const alertInfo = res.data || res
|
||||
|
||||
// 格式化数据为展示格式
|
||||
const formattedData = this.formatAlertData(alertInfo)
|
||||
|
||||
this.setData({
|
||||
alertData: formattedData,
|
||||
loading: false
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载预警详情失败:', error)
|
||||
|
||||
this.setData({ loading: false })
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化预警数据
|
||||
*/
|
||||
formatAlertData(data) {
|
||||
if (!data) return []
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: '预警信息',
|
||||
items: [
|
||||
{ label: '预警ID', value: data.id || '-' },
|
||||
{ label: '预警类型', value: this.getAlertTypeText(data.alertType) },
|
||||
{ label: '预警等级', value: this.getAlertLevelText(data.alertLevel) },
|
||||
{ label: '预警时间', value: data.alertTime || '-' },
|
||||
{ label: '预警描述', value: data.description || '-' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '设备信息',
|
||||
items: [
|
||||
{ label: '设备ID', value: data.deviceId || '-' },
|
||||
{ label: '设备名称', value: data.deviceName || '-' },
|
||||
{ label: '项圈编号', value: data.collarNumber || '-' },
|
||||
{ label: '设备状态', value: data.deviceStatus || '-' },
|
||||
{ label: '佩戴状态', value: data.wearStatus || '-' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '监测数据',
|
||||
items: [
|
||||
{ label: '当前温度', value: data.temperature ? `${data.temperature}°C` : '-' },
|
||||
{ label: '电池电量', value: data.battery ? `${data.battery}%` : '-' },
|
||||
{ label: 'GPS信号', value: data.gpsSignal || '-' },
|
||||
{ label: '运动状态', value: data.movementStatus || '-' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '位置信息',
|
||||
items: [
|
||||
{ label: '经度', value: data.longitude || '-' },
|
||||
{ label: '纬度', value: data.latitude || '-' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '运动数据',
|
||||
items: [
|
||||
{ label: '今日步数', value: data.dailySteps || 0 },
|
||||
{ label: '昨日步数', value: data.yesterdaySteps || 0 },
|
||||
{ label: '累计步数', value: data.totalSteps || 0 }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
return sections
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取预警等级文本
|
||||
*/
|
||||
getAlertLevelText(level) {
|
||||
const levelMap = {
|
||||
'low': '低',
|
||||
'medium': '中',
|
||||
'high': '高',
|
||||
'critical': '紧急'
|
||||
}
|
||||
return levelMap[level] || level || '-'
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取预警类型文本
|
||||
*/
|
||||
getAlertTypeText(type) {
|
||||
const typeMap = {
|
||||
'temperature': '温度异常',
|
||||
'battery': '电量异常',
|
||||
'not_collected': '未采集',
|
||||
'collar_cut': '项圈剪断',
|
||||
'fence': '围栏异常',
|
||||
'activity_high': '运动量偏高',
|
||||
'activity_low': '运动量偏低',
|
||||
'transfer_slow': '传输过慢'
|
||||
}
|
||||
return typeMap[type] || type || '-'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "预警详情",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<!--pages/alert/collar-alert-detail/collar-alert-detail.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 预警详情 -->
|
||||
<view class="detail-content" wx:else>
|
||||
<!-- 头部 -->
|
||||
<view class="detail-header">
|
||||
<text class="header-icon">🔔</text>
|
||||
<text class="header-title">项圈预警详情</text>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="section" wx:for="{{alertData}}" wx:key="title">
|
||||
<view class="section-title">{{item.title}}</view>
|
||||
<view class="section-content">
|
||||
<view class="info-item" wx:for="{{item.items}}" wx:key="label" wx:for-item="field">
|
||||
<view class="info-label">{{field.label}}</view>
|
||||
<view class="info-value">{{field.value}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-buttons">
|
||||
<button class="action-btn resolve">标记已处理</button>
|
||||
<button class="action-btn ignore">忽略预警</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/* pages/alert/collar-alert-detail/collar-alert-detail.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 详情内容 */
|
||||
.detail-content {
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.detail-header {
|
||||
background: linear-gradient(135deg, #ff6b35 0%, #ff8c5a 100%);
|
||||
padding: 40rpx;
|
||||
margin: 0 -30rpx 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
font-size: 80rpx;
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 信息块 */
|
||||
.section {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
background-color: #fff8f5;
|
||||
border-bottom: 1rpx solid #ffe5d9;
|
||||
border-left: 6rpx solid #ff6b35;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
width: 220rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 0 30rpx;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
padding: 0;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 12rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn.resolve {
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.action-btn.ignore {
|
||||
background-color: #999;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
// pages/alert/collar-alert/collar-alert.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 0,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
searchValue: '',
|
||||
alertType: '' // 预警类型筛选
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData(isRefresh = false) {
|
||||
if (isRefresh) {
|
||||
this.setData({ refreshing: true })
|
||||
}
|
||||
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: this.data.page,
|
||||
limit: this.data.limit,
|
||||
search: this.data.searchValue,
|
||||
alertType: this.data.alertType,
|
||||
_t: Date.now()
|
||||
}
|
||||
|
||||
console.log('请求参数:', params)
|
||||
|
||||
const res = await API.getCollarAlerts(params)
|
||||
|
||||
console.log('项圈预警数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
let list = res.data?.list || res.data || res.list || []
|
||||
const pagination = res.data?.pagination || {}
|
||||
const total = pagination.total || res.total || list.length || 0
|
||||
|
||||
// 数据预处理:格式化数据
|
||||
list = list.map(item => {
|
||||
return {
|
||||
...item,
|
||||
// 格式化预警时间
|
||||
alertTimeText: item.alertTime ? this.formatDateTime(item.alertTime) : '-',
|
||||
// 预警等级文本
|
||||
alertLevelText: this.getAlertLevelText(item.alertLevel),
|
||||
// 预警类型文本
|
||||
alertTypeText: this.getAlertTypeText(item.alertType),
|
||||
// 设备状态文本
|
||||
deviceStatusText: item.deviceStatus || '未知',
|
||||
// 佩戴状态文本
|
||||
wearStatusText: item.wearStatus || '未知',
|
||||
// 运动状态文本
|
||||
movementStatusText: item.movementStatus || '未知'
|
||||
}
|
||||
})
|
||||
|
||||
const totalPages = pagination.pages || Math.ceil(total / this.data.limit)
|
||||
|
||||
this.setData({
|
||||
list: list,
|
||||
total: total,
|
||||
totalPages: totalPages,
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
// 停止下拉刷新
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
this.setData({
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
formatDateTime(dateString) {
|
||||
if (!dateString) return '-'
|
||||
|
||||
// 如果已经是格式化好的,直接返回
|
||||
if (typeof dateString === 'string' && dateString.includes('-')) {
|
||||
return dateString
|
||||
}
|
||||
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取预警等级文本
|
||||
*/
|
||||
getAlertLevelText(level) {
|
||||
const levelMap = {
|
||||
'low': '低',
|
||||
'medium': '中',
|
||||
'high': '高',
|
||||
'critical': '紧急'
|
||||
}
|
||||
return levelMap[level] || level || '-'
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取预警类型文本
|
||||
*/
|
||||
getAlertTypeText(type) {
|
||||
const typeMap = {
|
||||
'temperature': '温度异常',
|
||||
'battery': '电量异常',
|
||||
'not_collected': '未采集',
|
||||
'collar_cut': '项圈剪断',
|
||||
'fence': '围栏异常',
|
||||
'activity_high': '运动量偏高',
|
||||
'activity_low': '运动量偏低',
|
||||
'transfer_slow': '传输过慢'
|
||||
}
|
||||
return typeMap[type] || type || '-'
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchValue: e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行搜索
|
||||
*/
|
||||
handleSearch() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空搜索
|
||||
*/
|
||||
clearSearch() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
page: 1
|
||||
})
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
*/
|
||||
changePage(e) {
|
||||
const page = e.currentTarget.dataset.page
|
||||
if (page !== this.data.page && page >= 1 && page <= this.data.totalPages) {
|
||||
this.setData({ page })
|
||||
this.loadData()
|
||||
// 滚动到顶部
|
||||
wx.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 300
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
prevPage() {
|
||||
if (this.data.page > 1) {
|
||||
this.setData({ page: this.data.page - 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
nextPage() {
|
||||
if (this.data.page < this.data.totalPages) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 查看预警详情
|
||||
*/
|
||||
viewDetail(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
console.log('查看预警详情:', id)
|
||||
|
||||
// 跳转到详情页
|
||||
wx.navigateTo({
|
||||
url: `/pages/alert/collar-alert-detail/collar-alert-detail?id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 下拉刷新
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 上拉加载更多
|
||||
*/
|
||||
onReachBottom() {
|
||||
if (this.data.page < this.data.totalPages && !this.data.loading) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "项圈预警",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<!--pages/alert/collar-alert/collar-alert.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="🔍 请输入项圈编号或设备名称"
|
||||
value="{{searchValue}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="handleSearch"
|
||||
type="text"
|
||||
/>
|
||||
<button class="search-btn" bindtap="handleSearch" wx:if="{{searchValue}}">搜索</button>
|
||||
<button class="clear-btn" bindtap="clearSearch" wx:if="{{searchValue}}">清空</button>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading && !refreshing}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 预警列表 -->
|
||||
<view class="alert-list" wx:else>
|
||||
<view class="alert-card {{item.alertLevel}}" wx:for="{{list}}" wx:key="id" bindtap="viewDetail" data-id="{{item.id}}">
|
||||
<!-- 预警头部 -->
|
||||
<view class="card-header">
|
||||
<view class="header-left">
|
||||
<text class="alert-type">{{item.alertTypeText}}</text>
|
||||
<text class="alert-level level-{{item.alertLevel}}">{{item.alertLevelText}}</text>
|
||||
</view>
|
||||
<text class="collar-number">{{item.collarNumber}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 预警描述 -->
|
||||
<view class="alert-description">
|
||||
<text>{{item.description}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<text class="label">设备名称:</text>
|
||||
<text class="value">{{item.deviceName}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">设备状态:</text>
|
||||
<text class="value">{{item.deviceStatusText}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">预警时间:</text>
|
||||
<text class="value">{{item.alertTimeText}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">温度:</text>
|
||||
<text class="value">{{item.temperature}}°C</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">电量:</text>
|
||||
<text class="value">{{item.battery}}%</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<view class="card-footer">
|
||||
<text class="view-detail">点击查看详情 ></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-container" wx:if="{{!loading && list.length === 0}}">
|
||||
<text class="empty-icon">🔔</text>
|
||||
<text class="empty-text">暂无预警数据</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination" wx:if="{{totalPages > 1 && list.length > 0}}">
|
||||
<button class="page-btn" bindtap="prevPage" disabled="{{page === 1}}">上一页</button>
|
||||
<view class="page-numbers">
|
||||
<text class="page-number {{page === item ? 'active' : ''}}"
|
||||
wx:for="{{totalPages}}"
|
||||
wx:key="index"
|
||||
wx:for-item="item"
|
||||
wx:for-index="index"
|
||||
data-page="{{index + 1}}"
|
||||
bindtap="changePage">
|
||||
{{index + 1}}
|
||||
</text>
|
||||
</view>
|
||||
<button class="page-btn" bindtap="nextPage" disabled="{{page === totalPages}}">下一页</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
/* pages/alert/collar-alert/collar-alert.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 60rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
width: 120rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #ff6b35;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.search-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
width: 100rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #999;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 预警列表 */
|
||||
.alert-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
/* 预警卡片 */
|
||||
.alert-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
border-left: 6rpx solid #ff6b35;
|
||||
}
|
||||
|
||||
.alert-card.high {
|
||||
border-left-color: #ff4444;
|
||||
}
|
||||
|
||||
.alert-card.medium {
|
||||
border-left-color: #ff9800;
|
||||
}
|
||||
|
||||
.alert-card.low {
|
||||
border-left-color: #ffeb3b;
|
||||
}
|
||||
|
||||
/* 卡片头部 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.alert-type {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.alert-level {
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.alert-level.level-high {
|
||||
background-color: #ff4444;
|
||||
}
|
||||
|
||||
.alert-level.level-medium {
|
||||
background-color: #ff9800;
|
||||
}
|
||||
|
||||
.alert-level.level-low {
|
||||
background-color: #ffeb3b;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.alert-level.level-critical {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
.collar-number {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
/* 预警描述 */
|
||||
.alert-description {
|
||||
padding: 24rpx;
|
||||
background-color: #fff8f5;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.alert-description text {
|
||||
font-size: 28rpx;
|
||||
color: #ff6b35;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 卡片主体 */
|
||||
.card-body {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 12rpx 0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
color: #666;
|
||||
min-width: 180rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-row .value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 卡片底部 */
|
||||
.card-footer {
|
||||
padding: 16rpx 24rpx;
|
||||
background-color: #f8f9fa;
|
||||
text-align: right;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.view-detail {
|
||||
font-size: 26rpx;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 30rpx 0;
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
width: 150rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin: 0 10rpx;
|
||||
background-color: #ff6b35;
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn[disabled] {
|
||||
background-color: #e0e0e0;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
margin: 0 20rpx;
|
||||
max-width: 400rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-number {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.page-number.active {
|
||||
background-color: #ff6b35;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
// pages/alert/eartag-alert-detail/eartag-alert-detail.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
alertId: null,
|
||||
alertData: [],
|
||||
loading: true
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
console.log('预警详情页面加载,参数:', options)
|
||||
|
||||
if (options.id) {
|
||||
this.setData({ alertId: options.id })
|
||||
this.loadAlertDetail(options.id)
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '参数错误',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载预警详情
|
||||
*/
|
||||
async loadAlertDetail(id) {
|
||||
try {
|
||||
this.setData({ loading: true })
|
||||
|
||||
console.log('获取预警详情,ID:', id)
|
||||
const res = await API.getEartagAlertDetail(id)
|
||||
|
||||
console.log('预警详情数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
const alertInfo = res.data || res
|
||||
|
||||
// 格式化数据为展示格式
|
||||
const formattedData = this.formatAlertData(alertInfo)
|
||||
|
||||
this.setData({
|
||||
alertData: formattedData,
|
||||
loading: false
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载预警详情失败:', error)
|
||||
|
||||
this.setData({ loading: false })
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化预警数据
|
||||
*/
|
||||
formatAlertData(data) {
|
||||
if (!data) return []
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: '预警信息',
|
||||
items: [
|
||||
{ label: '预警ID', value: data.id || '-' },
|
||||
{ label: '预警类型', value: this.getAlertTypeText(data.alertType) },
|
||||
{ label: '预警等级', value: this.getAlertLevelText(data.alertLevel) },
|
||||
{ label: '预警时间', value: data.alertTime || '-' },
|
||||
{ label: '预警描述', value: data.description || '-' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '设备信息',
|
||||
items: [
|
||||
{ label: '设备ID', value: data.deviceId || '-' },
|
||||
{ label: '设备名称', value: data.deviceName || '-' },
|
||||
{ label: '耳标编号', value: data.eartagNumber || '-' },
|
||||
{ label: '设备状态', value: data.deviceStatus || '-' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '监测数据',
|
||||
items: [
|
||||
{ label: '当前温度', value: data.temperature ? `${data.temperature}°C` : '-' },
|
||||
{ label: '电池电量', value: data.battery ? `${data.battery}%` : '-' },
|
||||
{ label: 'GPS信号', value: data.gpsSignal || '-' },
|
||||
{ label: '运动状态', value: data.movementStatus || '-' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '位置信息',
|
||||
items: [
|
||||
{ label: '经度', value: data.longitude || '-' },
|
||||
{ label: '纬度', value: data.latitude || '-' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '运动数据',
|
||||
items: [
|
||||
{ label: '今日步数', value: data.dailySteps || 0 },
|
||||
{ label: '昨日步数', value: data.yesterdaySteps || 0 },
|
||||
{ label: '累计步数', value: data.totalSteps || 0 }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
return sections
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取预警等级文本
|
||||
*/
|
||||
getAlertLevelText(level) {
|
||||
const levelMap = {
|
||||
'low': '低',
|
||||
'medium': '中',
|
||||
'high': '高',
|
||||
'critical': '紧急'
|
||||
}
|
||||
return levelMap[level] || level || '-'
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取预警类型文本
|
||||
*/
|
||||
getAlertTypeText(type) {
|
||||
const typeMap = {
|
||||
'temperature': '温度异常',
|
||||
'battery': '电量异常',
|
||||
'not_collected': '未采集',
|
||||
'movement': '运动异常',
|
||||
'fence': '围栏异常',
|
||||
'activity_high': '运动量偏高',
|
||||
'activity_low': '运动量偏低',
|
||||
'transfer_slow': '传输过慢'
|
||||
}
|
||||
return typeMap[type] || type || '-'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "预警详情",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<!--pages/alert/eartag-alert-detail/eartag-alert-detail.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 预警详情 -->
|
||||
<view class="detail-content" wx:else>
|
||||
<!-- 头部 -->
|
||||
<view class="detail-header">
|
||||
<text class="header-icon">🔔</text>
|
||||
<text class="header-title">耳标预警详情</text>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="section" wx:for="{{alertData}}" wx:key="title">
|
||||
<view class="section-title">{{item.title}}</view>
|
||||
<view class="section-content">
|
||||
<view class="info-item" wx:for="{{item.items}}" wx:key="label" wx:for-item="field">
|
||||
<view class="info-label">{{field.label}}</view>
|
||||
<view class="info-value">{{field.value}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-buttons">
|
||||
<button class="action-btn resolve">标记已处理</button>
|
||||
<button class="action-btn ignore">忽略预警</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/* pages/alert/eartag-alert-detail/eartag-alert-detail.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 详情内容 */
|
||||
.detail-content {
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.detail-header {
|
||||
background: linear-gradient(135deg, #4caf50 0%, #66bb6a 100%);
|
||||
padding: 40rpx;
|
||||
margin: 0 -30rpx 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
font-size: 80rpx;
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 信息块 */
|
||||
.section {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
background-color: #f1f8f4;
|
||||
border-bottom: 1rpx solid #c8e6c9;
|
||||
border-left: 6rpx solid #4caf50;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
width: 220rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 0 30rpx;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
padding: 0;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 12rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn.resolve {
|
||||
background-color: #4caf50;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.action-btn.ignore {
|
||||
background-color: #999;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
// pages/alert/eartag-alert/eartag-alert.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 0,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
searchValue: '',
|
||||
alertType: '' // 预警类型筛选
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData(isRefresh = false) {
|
||||
if (isRefresh) {
|
||||
this.setData({ refreshing: true })
|
||||
}
|
||||
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: this.data.page,
|
||||
limit: this.data.limit,
|
||||
search: this.data.searchValue,
|
||||
alertType: this.data.alertType,
|
||||
_t: Date.now()
|
||||
}
|
||||
|
||||
console.log('请求参数:', params)
|
||||
|
||||
const res = await API.getEartagAlerts(params)
|
||||
|
||||
console.log('耳标预警数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
let list = res.data?.list || res.data || res.list || []
|
||||
const pagination = res.data?.pagination || {}
|
||||
const total = pagination.total || res.total || list.length || 0
|
||||
|
||||
// 数据预处理:格式化数据
|
||||
list = list.map(item => {
|
||||
return {
|
||||
...item,
|
||||
// 格式化预警时间
|
||||
alertTimeText: item.alertTime ? this.formatDateTime(item.alertTime) : '-',
|
||||
// 预警等级文本
|
||||
alertLevelText: this.getAlertLevelText(item.alertLevel),
|
||||
// 预警类型文本
|
||||
alertTypeText: this.getAlertTypeText(item.alertType),
|
||||
// 设备状态文本
|
||||
deviceStatusText: item.deviceStatus || '未知',
|
||||
// 运动状态文本
|
||||
movementStatusText: item.movementStatus || '未知'
|
||||
}
|
||||
})
|
||||
|
||||
const totalPages = pagination.pages || Math.ceil(total / this.data.limit)
|
||||
|
||||
this.setData({
|
||||
list: list,
|
||||
total: total,
|
||||
totalPages: totalPages,
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
// 停止下拉刷新
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
this.setData({
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
formatDateTime(dateString) {
|
||||
if (!dateString) return '-'
|
||||
|
||||
// 如果已经是格式化好的,直接返回
|
||||
if (typeof dateString === 'string' && dateString.includes('-')) {
|
||||
return dateString
|
||||
}
|
||||
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取预警等级文本
|
||||
*/
|
||||
getAlertLevelText(level) {
|
||||
const levelMap = {
|
||||
'low': '低',
|
||||
'medium': '中',
|
||||
'high': '高',
|
||||
'critical': '紧急'
|
||||
}
|
||||
return levelMap[level] || level || '-'
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取预警类型文本
|
||||
*/
|
||||
getAlertTypeText(type) {
|
||||
const typeMap = {
|
||||
'temperature': '温度异常',
|
||||
'battery': '电量异常',
|
||||
'not_collected': '未采集',
|
||||
'movement': '运动异常',
|
||||
'fence': '围栏异常',
|
||||
'activity_high': '运动量偏高',
|
||||
'activity_low': '运动量偏低',
|
||||
'transfer_slow': '传输过慢'
|
||||
}
|
||||
return typeMap[type] || type || '-'
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchValue: e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行搜索
|
||||
*/
|
||||
handleSearch() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空搜索
|
||||
*/
|
||||
clearSearch() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
page: 1
|
||||
})
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
*/
|
||||
changePage(e) {
|
||||
const page = e.currentTarget.dataset.page
|
||||
if (page !== this.data.page && page >= 1 && page <= this.data.totalPages) {
|
||||
this.setData({ page })
|
||||
this.loadData()
|
||||
// 滚动到顶部
|
||||
wx.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 300
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
prevPage() {
|
||||
if (this.data.page > 1) {
|
||||
this.setData({ page: this.data.page - 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
nextPage() {
|
||||
if (this.data.page < this.data.totalPages) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 查看预警详情
|
||||
*/
|
||||
viewDetail(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
console.log('查看预警详情:', id)
|
||||
|
||||
// 跳转到详情页
|
||||
wx.navigateTo({
|
||||
url: `/pages/alert/eartag-alert-detail/eartag-alert-detail?id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 下拉刷新
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 上拉加载更多
|
||||
*/
|
||||
onReachBottom() {
|
||||
if (this.data.page < this.data.totalPages && !this.data.loading) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "耳标预警",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<!--pages/alert/eartag-alert/eartag-alert.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="🔍 请输入耳标编号或设备名称"
|
||||
value="{{searchValue}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="handleSearch"
|
||||
type="text"
|
||||
/>
|
||||
<button class="search-btn" bindtap="handleSearch" wx:if="{{searchValue}}">搜索</button>
|
||||
<button class="clear-btn" bindtap="clearSearch" wx:if="{{searchValue}}">清空</button>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading && !refreshing}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 预警列表 -->
|
||||
<view class="alert-list" wx:else>
|
||||
<view class="alert-card {{item.alertLevel}}" wx:for="{{list}}" wx:key="id" bindtap="viewDetail" data-id="{{item.id}}">
|
||||
<!-- 预警头部 -->
|
||||
<view class="card-header">
|
||||
<view class="header-left">
|
||||
<text class="alert-type">{{item.alertTypeText}}</text>
|
||||
<text class="alert-level level-{{item.alertLevel}}">{{item.alertLevelText}}</text>
|
||||
</view>
|
||||
<text class="eartag-number">{{item.eartagNumber}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 预警描述 -->
|
||||
<view class="alert-description">
|
||||
<text>{{item.description}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<text class="label">设备名称:</text>
|
||||
<text class="value">{{item.deviceName}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">设备状态:</text>
|
||||
<text class="value">{{item.deviceStatusText}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">预警时间:</text>
|
||||
<text class="value">{{item.alertTimeText}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">温度:</text>
|
||||
<text class="value">{{item.temperature}}°C</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">电量:</text>
|
||||
<text class="value">{{item.battery}}%</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<view class="card-footer">
|
||||
<text class="view-detail">点击查看详情 ></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-container" wx:if="{{!loading && list.length === 0}}">
|
||||
<text class="empty-icon">🔔</text>
|
||||
<text class="empty-text">暂无预警数据</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination" wx:if="{{totalPages > 1 && list.length > 0}}">
|
||||
<button class="page-btn" bindtap="prevPage" disabled="{{page === 1}}">上一页</button>
|
||||
<view class="page-numbers">
|
||||
<text class="page-number {{page === item ? 'active' : ''}}"
|
||||
wx:for="{{totalPages}}"
|
||||
wx:key="index"
|
||||
wx:for-item="item"
|
||||
wx:for-index="index"
|
||||
data-page="{{index + 1}}"
|
||||
bindtap="changePage">
|
||||
{{index + 1}}
|
||||
</text>
|
||||
</view>
|
||||
<button class="page-btn" bindtap="nextPage" disabled="{{page === totalPages}}">下一页</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
/* pages/alert/eartag-alert/eartag-alert.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
padding-bottom: 100rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 60rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
width: 120rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #4caf50;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.search-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
width: 100rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #999;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 预警列表 */
|
||||
.alert-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
/* 预警卡片 */
|
||||
.alert-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
border-left: 6rpx solid #4caf50;
|
||||
}
|
||||
|
||||
.alert-card.high {
|
||||
border-left-color: #ff4444;
|
||||
}
|
||||
|
||||
.alert-card.medium {
|
||||
border-left-color: #ff9800;
|
||||
}
|
||||
|
||||
.alert-card.low {
|
||||
border-left-color: #ffeb3b;
|
||||
}
|
||||
|
||||
/* 卡片头部 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.alert-type {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.alert-level {
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.alert-level.level-high {
|
||||
background-color: #ff4444;
|
||||
}
|
||||
|
||||
.alert-level.level-medium {
|
||||
background-color: #ff9800;
|
||||
}
|
||||
|
||||
.alert-level.level-low {
|
||||
background-color: #ffeb3b;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.alert-level.level-critical {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
.eartag-number {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
/* 预警描述 */
|
||||
.alert-description {
|
||||
padding: 24rpx;
|
||||
background-color: #f1f8f4;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.alert-description text {
|
||||
font-size: 28rpx;
|
||||
color: #2e7d32;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 卡片主体 */
|
||||
.card-body {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 12rpx 0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
color: #666;
|
||||
min-width: 180rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-row .value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 卡片底部 */
|
||||
.card-footer {
|
||||
padding: 16rpx 24rpx;
|
||||
background-color: #f8f9fa;
|
||||
text-align: right;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.view-detail {
|
||||
font-size: 26rpx;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 30rpx 0;
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
width: 150rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin: 0 10rpx;
|
||||
background-color: #4caf50;
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn[disabled] {
|
||||
background-color: #e0e0e0;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
margin: 0 20rpx;
|
||||
max-width: 400rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-number {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.page-number.active {
|
||||
background-color: #4caf50;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4rpx 12rpx rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"usingComponents": {}
|
||||
{
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
// pages/cattle/archive/archive.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPages: 0,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
searchValue: ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
// 可选:每次显示页面时刷新数据
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData(isRefresh = false) {
|
||||
if (isRefresh) {
|
||||
this.setData({ refreshing: true })
|
||||
}
|
||||
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: this.data.page,
|
||||
pageSize: this.data.pageSize,
|
||||
_t: Date.now()
|
||||
}
|
||||
|
||||
// 如果有搜索条件,添加到参数中
|
||||
if (this.data.searchValue) {
|
||||
params.earNumber = this.data.searchValue.trim() // 按耳号精确查询
|
||||
}
|
||||
|
||||
console.log('请求参数:', params)
|
||||
|
||||
const res = await API.getCattleList(params)
|
||||
|
||||
console.log('牛只档案数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
let list = res.data?.list || res.list || []
|
||||
const pagination = res.data?.pagination || {}
|
||||
const total = pagination.total || res.total || list.length || 0
|
||||
|
||||
// 数据预处理:格式化时间戳和数据
|
||||
list = list.map(item => {
|
||||
return {
|
||||
...item,
|
||||
// 格式化出生日期
|
||||
birthdayText: item.birthday ? this.formatDate(item.birthday) : '-',
|
||||
// 格式化入场时间
|
||||
intoTimeText: item.intoTime ? this.formatDate(item.intoTime) : '-',
|
||||
// 格式化性别
|
||||
sexText: item.sex === 1 ? '公' : item.sex === 2 ? '母' : '未知',
|
||||
// 格式化生理阶段
|
||||
physiologicalStageText: this.getPhysiologicalStage(item.physiologicalStage),
|
||||
// 格式化来源
|
||||
sourceText: item.source === 1 ? '自繁' : item.source === 2 ? '外购' : '未知',
|
||||
// 格式化体重计算时间
|
||||
weightCalculateTimeText: item.weightCalculateTime ? this.formatDateTime(item.weightCalculateTime) : '-'
|
||||
}
|
||||
})
|
||||
|
||||
// 如果有搜索条件,进行前端精确过滤(确保精确匹配)
|
||||
if (this.data.searchValue && this.data.searchValue.trim()) {
|
||||
const searchKey = this.data.searchValue.trim()
|
||||
list = list.filter(item => {
|
||||
const earNumber = String(item.earNumber || '')
|
||||
// 精确匹配:完全相等
|
||||
return earNumber === searchKey
|
||||
})
|
||||
|
||||
console.log('精确查找结果:', list.length, '条')
|
||||
|
||||
if (list.length === 0) {
|
||||
wx.showToast({
|
||||
title: '未找到匹配的牛只',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const totalPages = pagination.pages || Math.ceil((this.data.searchValue ? list.length : total) / this.data.pageSize)
|
||||
|
||||
this.setData({
|
||||
list: list,
|
||||
total: total,
|
||||
totalPages: totalPages,
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
// 停止下拉刷新
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
this.setData({
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期(时间戳转日期)
|
||||
*/
|
||||
formatDate(timestamp) {
|
||||
if (!timestamp) return '-'
|
||||
const date = new Date(timestamp * 1000)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
formatDateTime(dateString) {
|
||||
if (!dateString) return '-'
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取生理阶段文本
|
||||
*/
|
||||
getPhysiologicalStage(stage) {
|
||||
const stages = {
|
||||
0: '犊牛期',
|
||||
1: '育成期',
|
||||
2: '成年期',
|
||||
3: '老年期'
|
||||
}
|
||||
return stages[stage] || '未知'
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchValue: e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行搜索
|
||||
*/
|
||||
handleSearch() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空搜索
|
||||
*/
|
||||
clearSearch() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
page: 1
|
||||
})
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
*/
|
||||
changePage(e) {
|
||||
const page = e.currentTarget.dataset.page
|
||||
if (page !== this.data.page && page >= 1 && page <= this.data.totalPages) {
|
||||
this.setData({ page })
|
||||
this.loadData()
|
||||
// 滚动到顶部
|
||||
wx.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 300
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
prevPage() {
|
||||
if (this.data.page > 1) {
|
||||
this.setData({ page: this.data.page - 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
nextPage() {
|
||||
if (this.data.page < this.data.totalPages) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 查看牛只详情
|
||||
*/
|
||||
viewDetail(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
console.log('查看牛只详情:', id)
|
||||
|
||||
// 跳转到详情页
|
||||
wx.navigateTo({
|
||||
url: `/pages/cattle/detail/detail?id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 新增档案
|
||||
*/
|
||||
addCattle() {
|
||||
wx.showToast({
|
||||
title: '新增功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 下拉刷新
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 上拉加载更多
|
||||
*/
|
||||
onReachBottom() {
|
||||
if (this.data.page < this.data.totalPages && !this.data.loading) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "牛只档案",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark",
|
||||
"backgroundColor": "#f6f6f6"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<!--pages/cattle/archive/archive.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="🔍 请输入耳号"
|
||||
value="{{searchValue}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="handleSearch"
|
||||
type="number"
|
||||
/>
|
||||
<button class="search-btn" bindtap="handleSearch" wx:if="{{searchValue}}">搜索</button>
|
||||
<button class="clear-btn" bindtap="clearSearch" wx:if="{{searchValue}}">清空</button>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading && !refreshing}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 牛只列表 -->
|
||||
<view class="cattle-list" wx:else>
|
||||
<view class="cattle-card" wx:for="{{list}}" wx:key="id" bindtap="viewDetail" data-id="{{item.id}}">
|
||||
<!-- 耳号 -->
|
||||
<view class="card-header">
|
||||
<text class="ear-number">耳号:{{item.earNumber}}</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<text class="label">佩戴设备:</text>
|
||||
<text class="value">{{item.earNumber}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">出生日期:</text>
|
||||
<text class="value">{{item.birthdayText}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">品类:</text>
|
||||
<text class="value">{{item.cate || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">品种:</text>
|
||||
<text class="value">{{item.varieties || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">生理阶段:</text>
|
||||
<text class="value">{{item.physiologicalStageText}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">性别:</text>
|
||||
<text class="value">{{item.sexText}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">栏舍:</text>
|
||||
<text class="value">{{item.penName || '-'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-container" wx:if="{{!loading && list.length === 0}}">
|
||||
<text class="empty-icon">🐄</text>
|
||||
<text class="empty-text">暂无牛只档案数据</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination" wx:if="{{totalPages > 1 && list.length > 0}}">
|
||||
<button class="page-btn" bindtap="prevPage" disabled="{{page === 1}}">上一页</button>
|
||||
<view class="page-numbers">
|
||||
<text class="page-number {{page === item ? 'active' : ''}}"
|
||||
wx:for="{{totalPages}}"
|
||||
wx:key="index"
|
||||
wx:for-item="item"
|
||||
wx:for-index="index"
|
||||
data-page="{{index + 1}}"
|
||||
bindtap="changePage">
|
||||
{{index + 1}}
|
||||
</text>
|
||||
</view>
|
||||
<button class="page-btn" bindtap="nextPage" disabled="{{page === totalPages}}">下一页</button>
|
||||
</view>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<view class="stats-bar" wx:if="{{!loading}}">
|
||||
<text class="stats-text">共 {{total}} 头牛,当前第 {{page}}/{{totalPages}} 页</text>
|
||||
</view>
|
||||
|
||||
<!-- 新增档案按钮 -->
|
||||
<view class="add-btn" bindtap="addCattle">
|
||||
<text>新增档案</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,246 @@
|
||||
/* pages/cattle/archive/archive.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
padding-bottom: 140rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏样式 */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 60rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
width: 120rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.search-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
width: 100rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #fa5151;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 牛只列表 */
|
||||
.cattle-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
/* 牛只卡片 */
|
||||
.cattle-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 卡片头部 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.ear-number {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 48rpx;
|
||||
color: #ccc;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
/* 卡片主体 */
|
||||
.card-body {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12rpx 0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
color: #666;
|
||||
min-width: 160rpx;
|
||||
}
|
||||
|
||||
.info-row .value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 30rpx 0;
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
width: 150rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin: 0 10rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn[disabled] {
|
||||
background-color: #e0e0e0;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
margin: 0 20rpx;
|
||||
max-width: 400rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-number {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.page-number.active {
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
/* 数据统计 */
|
||||
.stats-bar {
|
||||
text-align: center;
|
||||
padding: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.stats-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 新增按钮 */
|
||||
.add-btn {
|
||||
position: fixed;
|
||||
bottom: 30rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 90%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// pages/cattle/batch-detail/batch-detail.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
batchId: null,
|
||||
batchData: [],
|
||||
loading: true
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
console.log('批次详情页面加载,参数:', options)
|
||||
|
||||
if (options.id) {
|
||||
this.setData({ batchId: options.id })
|
||||
this.loadBatchDetail(options.id)
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '参数错误',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载批次详情
|
||||
*/
|
||||
async loadBatchDetail(id) {
|
||||
try {
|
||||
this.setData({ loading: true })
|
||||
|
||||
console.log('获取批次详情,ID:', id)
|
||||
const res = await API.getBatchDetail(id)
|
||||
|
||||
console.log('批次详情数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
const batchInfo = res.data || res
|
||||
|
||||
// 格式化数据为展示格式
|
||||
const formattedData = this.formatBatchData(batchInfo)
|
||||
|
||||
this.setData({
|
||||
batchData: formattedData,
|
||||
loading: false
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载批次详情失败:', error)
|
||||
|
||||
this.setData({ loading: false })
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化批次数据
|
||||
*/
|
||||
formatBatchData(data) {
|
||||
if (!data) return []
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: '基本信息',
|
||||
items: [
|
||||
{ label: 'ID', value: data.id || '-' },
|
||||
{ label: '批次名称', value: data.name || '-' },
|
||||
{ label: '批次编码', value: data.code || '-' },
|
||||
{ label: '批次类型', value: data.type || '-' },
|
||||
{ label: '状态', value: data.status || '-' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '数量信息',
|
||||
items: [
|
||||
{ label: '当前数量', value: data.currentCount || 0 },
|
||||
{ label: '目标数量', value: data.targetCount || 0 }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '时间信息',
|
||||
items: [
|
||||
{ label: '开始日期', value: this.formatDate(data.startDate) },
|
||||
{ label: '预期结束日期', value: this.formatDate(data.expectedEndDate) },
|
||||
{ label: '实际结束日期', value: this.formatDate(data.actualEndDate) }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '管理信息',
|
||||
items: [
|
||||
{ label: '养殖场', value: data.farm?.name || data.farmName || '-' },
|
||||
{ label: '养殖场ID', value: data.farmId || data.farm_id || '-' },
|
||||
{ label: '管理人员', value: data.manager || '-' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '其他信息',
|
||||
items: [
|
||||
{ label: '备注', value: data.remark || '-' },
|
||||
{ label: '创建时间', value: this.formatDateTime(data.created_at) },
|
||||
{ label: '更新时间', value: this.formatDateTime(data.updated_at) }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
return sections
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
*/
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '-'
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
formatDateTime(dateString) {
|
||||
if (!dateString) return '-'
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "批次详情",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<!--pages/cattle/batch-detail/batch-detail.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 批次详情 -->
|
||||
<view class="detail-content" wx:else>
|
||||
<!-- 头部 -->
|
||||
<view class="detail-header">
|
||||
<text class="header-title">批次设置详情</text>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="section" wx:for="{{batchData}}" wx:key="title">
|
||||
<view class="section-title">{{item.title}}</view>
|
||||
<view class="section-content">
|
||||
<view class="info-item" wx:for="{{item.items}}" wx:key="label" wx:for-item="field">
|
||||
<view class="info-label">{{field.label}}</view>
|
||||
<view class="info-value">{{field.value}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/* pages/cattle/batch-detail/batch-detail.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 详情内容 */
|
||||
.detail-content {
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.detail-header {
|
||||
background: linear-gradient(135deg, #07c160 0%, #09a856 100%);
|
||||
padding: 40rpx;
|
||||
margin: 0 -30rpx 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 信息块 */
|
||||
.section {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
width: 220rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
244
mini_program/farm-monitor-dashboard/pages/cattle/batch/batch.js
Normal file
244
mini_program/farm-monitor-dashboard/pages/cattle/batch/batch.js
Normal file
@@ -0,0 +1,244 @@
|
||||
// pages/cattle/batch/batch.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPages: 0,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
searchValue: ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData(isRefresh = false) {
|
||||
if (isRefresh) {
|
||||
this.setData({ refreshing: true })
|
||||
}
|
||||
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: this.data.page,
|
||||
pageSize: this.data.pageSize,
|
||||
exactMatch: true,
|
||||
strictMatch: true,
|
||||
_t: Date.now()
|
||||
}
|
||||
|
||||
// 如果有搜索条件,添加到参数中
|
||||
if (this.data.searchValue) {
|
||||
params.name = this.data.searchValue.trim() // 按批次名称精确查询
|
||||
params.search = this.data.searchValue.trim() // 兼容参数
|
||||
}
|
||||
|
||||
console.log('请求参数:', params)
|
||||
|
||||
const res = await API.getBatchList(params)
|
||||
|
||||
console.log('批次设置数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
let list = res.data?.list || res.data || res.list || []
|
||||
const pagination = res.data?.pagination || {}
|
||||
const total = pagination.total || res.total || list.length || 0
|
||||
|
||||
// 数据预处理:格式化时间和数据
|
||||
list = list.map(item => {
|
||||
return {
|
||||
...item,
|
||||
// 格式化开始日期
|
||||
startDateText: item.startDate ? this.formatDateTime(item.startDate) : '-',
|
||||
// 格式化预期结束日期
|
||||
expectedEndDateText: item.expectedEndDate ? this.formatDateTime(item.expectedEndDate) : '-',
|
||||
// 格式化实际结束日期
|
||||
actualEndDateText: item.actualEndDate ? this.formatDateTime(item.actualEndDate) : '-',
|
||||
// 格式化创建时间
|
||||
createdAtText: item.created_at ? this.formatDateTime(item.created_at) : '-',
|
||||
// 格式化更新时间
|
||||
updatedAtText: item.updated_at ? this.formatDateTime(item.updated_at) : '-',
|
||||
// 养殖场名称
|
||||
farmName: item.farm?.name || '-'
|
||||
}
|
||||
})
|
||||
|
||||
// 如果有搜索条件,进行前端精确过滤(确保精确匹配)
|
||||
if (this.data.searchValue && this.data.searchValue.trim()) {
|
||||
const searchKey = this.data.searchValue.trim()
|
||||
list = list.filter(item => {
|
||||
const name = String(item.name || '')
|
||||
// 精确匹配:完全相等
|
||||
return name === searchKey
|
||||
})
|
||||
|
||||
console.log('精确查找结果:', list.length, '条')
|
||||
|
||||
if (list.length === 0) {
|
||||
wx.showToast({
|
||||
title: '未找到匹配的批次',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const totalPages = pagination.pages || Math.ceil((this.data.searchValue ? list.length : total) / this.data.pageSize)
|
||||
|
||||
this.setData({
|
||||
list: list,
|
||||
total: total,
|
||||
totalPages: totalPages,
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
// 停止下拉刷新
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
this.setData({
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
formatDateTime(dateString) {
|
||||
if (!dateString) return '-'
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchValue: e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行搜索
|
||||
*/
|
||||
handleSearch() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空搜索
|
||||
*/
|
||||
clearSearch() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
page: 1
|
||||
})
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
*/
|
||||
changePage(e) {
|
||||
const page = e.currentTarget.dataset.page
|
||||
if (page !== this.data.page && page >= 1 && page <= this.data.totalPages) {
|
||||
this.setData({ page })
|
||||
this.loadData()
|
||||
// 滚动到顶部
|
||||
wx.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 300
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
prevPage() {
|
||||
if (this.data.page > 1) {
|
||||
this.setData({ page: this.data.page - 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
nextPage() {
|
||||
if (this.data.page < this.data.totalPages) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 查看批次详情
|
||||
*/
|
||||
viewDetail(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
console.log('查看批次详情:', id)
|
||||
|
||||
// 跳转到详情页
|
||||
wx.navigateTo({
|
||||
url: `/pages/cattle/batch-detail/batch-detail?id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 下拉刷新
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 上拉加载更多
|
||||
*/
|
||||
onReachBottom() {
|
||||
if (this.data.page < this.data.totalPages && !this.data.loading) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "批次设置",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<!--pages/cattle/batch/batch.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="🔍 请输入批次号"
|
||||
value="{{searchValue}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="handleSearch"
|
||||
type="text"
|
||||
/>
|
||||
<button class="search-btn" bindtap="handleSearch" wx:if="{{searchValue}}">搜索</button>
|
||||
<button class="clear-btn" bindtap="clearSearch" wx:if="{{searchValue}}">清空</button>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading && !refreshing}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 批次列表 -->
|
||||
<view class="batch-list" wx:else>
|
||||
<view class="batch-card" wx:for="{{list}}" wx:key="id" bindtap="viewDetail" data-id="{{item.id}}">
|
||||
<!-- 批次号 -->
|
||||
<view class="card-header">
|
||||
<text class="batch-number">批次号:{{item.name}}</text>
|
||||
<button class="edit-btn" size="mini" catchtap="editBatch" data-id="{{item.id}}">编辑</button>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<text class="label">种类:</text>
|
||||
<text class="value">{{item.type || '牛'}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">批次内数量:</text>
|
||||
<text class="value">{{item.currentCount || 0}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">最后操作时间:</text>
|
||||
<text class="value">{{item.updatedAtText}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">创建人:</text>
|
||||
<text class="value">{{item.manager || 'admin'}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">备注:</text>
|
||||
<text class="value">{{item.remark || '--'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<view class="card-footer">
|
||||
<text class="page-indicator">{{page}}/{{totalPages}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-container" wx:if="{{!loading && list.length === 0}}">
|
||||
<text class="empty-icon">📦</text>
|
||||
<text class="empty-text">暂无批次设置数据</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination" wx:if="{{totalPages > 1 && list.length > 0}}">
|
||||
<button class="page-btn" bindtap="prevPage" disabled="{{page === 1}}">上一页</button>
|
||||
<view class="page-numbers">
|
||||
<text class="page-number {{page === item ? 'active' : ''}}"
|
||||
wx:for="{{totalPages}}"
|
||||
wx:key="index"
|
||||
wx:for-item="item"
|
||||
wx:for-index="index"
|
||||
data-page="{{index + 1}}"
|
||||
bindtap="changePage">
|
||||
{{index + 1}}
|
||||
</text>
|
||||
</view>
|
||||
<button class="page-btn" bindtap="nextPage" disabled="{{page === totalPages}}">下一页</button>
|
||||
</view>
|
||||
|
||||
<!-- 新增批次按钮 -->
|
||||
<view class="add-btn">
|
||||
<text>新增批次</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
/* pages/cattle/batch/batch.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
padding-bottom: 140rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 60rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
width: 120rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.search-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
width: 100rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #fa5151;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 批次列表 */
|
||||
.batch-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
/* 批次卡片 */
|
||||
.batch-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 卡片头部 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.batch-number {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
padding: 8rpx 24rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.edit-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 卡片主体 */
|
||||
.card-body {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 12rpx 0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
color: #666;
|
||||
min-width: 220rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-row .value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 卡片底部 */
|
||||
.card-footer {
|
||||
padding: 16rpx 24rpx;
|
||||
background-color: #f8f9fa;
|
||||
text-align: center;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.page-indicator {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 30rpx 0;
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
width: 150rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin: 0 10rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn[disabled] {
|
||||
background-color: #e0e0e0;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
margin: 0 20rpx;
|
||||
max-width: 400rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-number {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.page-number.active {
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
/* 新增批次按钮 */
|
||||
.add-btn {
|
||||
position: fixed;
|
||||
bottom: 30rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 90%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
<!-- 牛只批次设置页面 -->
|
||||
<view class="page-container">
|
||||
<!-- 顶部搜索区域 -->
|
||||
<view class="search-bar">
|
||||
<input class="search-input" placeholder="请输入批次名称或编号(精确匹配)" value="{{search}}" bindinput="onSearchInput" confirm-type="search" bindconfirm="onSearchConfirm" />
|
||||
<button class="search-btn" bindtap="onSearch">查询</button>
|
||||
</view>
|
||||
|
||||
<!-- 统计与分页信息 -->
|
||||
<view class="summary-bar">
|
||||
<text>总数:{{total}}</text>
|
||||
<text>第 {{page}} / {{totalPages}} 页</text>
|
||||
</view>
|
||||
|
||||
<!-- 批次列表 -->
|
||||
<scroll-view scroll-y class="list-scroll">
|
||||
<block wx:if="{{loading}}">
|
||||
<view class="loading">加载中...</view>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{!loading && records.length === 0}}">
|
||||
<view class="empty">暂无数据</view>
|
||||
</block>
|
||||
|
||||
<block wx:for="{{records}}" wx:key="id">
|
||||
<view class="record-card">
|
||||
<!-- 头部主信息 -->
|
||||
<view class="record-header">
|
||||
<view class="title-line">
|
||||
<text class="name">{{item.name || item.batch_name || item.batchName || '-'}}</text>
|
||||
<text class="code">编号:{{item.code || item.batch_number || item.batchNumber || '-'}}</text>
|
||||
</view>
|
||||
<view class="meta-line">
|
||||
<text class="status" wx:if="{{item.statusStr}}">状态:{{item.statusStr}}</text>
|
||||
<text class="time" wx:if="{{item.createdAtStr}}">创建时间:{{item.createdAtStr}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 详情字段(显示全部返回字段,中文映射) -->
|
||||
<view class="record-body">
|
||||
<block wx:for="{{item.displayPairs}}" wx:key="key" wx:for-item="pair">
|
||||
<view class="pair-row">
|
||||
<text class="pair-key">{{pair.keyZh}}</text>
|
||||
<text class="pair-val">{{pair.val}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination">
|
||||
<button class="page-btn" bindtap="prevPage" disabled="{{page <= 1}}">上一页</button>
|
||||
<scroll-view scroll-x class="page-numbers">
|
||||
<view class="page-items">
|
||||
<block wx:for="{{pages}}" wx:key="index">
|
||||
<view class="page-item {{current ? 'active' : ''}}" data-page="{{num}}" bindtap="goToPage">
|
||||
<text>{{num}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<button class="page-btn" bindtap="nextPage" disabled="{{page >= totalPages}}">下一页</button>
|
||||
</view>
|
||||
<!-- 牛只批次设置页面 -->
|
||||
<view class="page-container">
|
||||
<!-- 顶部搜索区域 -->
|
||||
<view class="search-bar">
|
||||
<input class="search-input" placeholder="请输入批次名称或编号(精确匹配)" value="{{search}}" bindinput="onSearchInput" confirm-type="search" bindconfirm="onSearchConfirm" />
|
||||
<button class="search-btn" bindtap="onSearch">查询</button>
|
||||
</view>
|
||||
|
||||
<!-- 统计与分页信息 -->
|
||||
<view class="summary-bar">
|
||||
<text>总数:{{total}}</text>
|
||||
<text>第 {{page}} / {{totalPages}} 页</text>
|
||||
</view>
|
||||
|
||||
<!-- 批次列表 -->
|
||||
<scroll-view scroll-y class="list-scroll">
|
||||
<block wx:if="{{loading}}">
|
||||
<view class="loading">加载中...</view>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{!loading && records.length === 0}}">
|
||||
<view class="empty">暂无数据</view>
|
||||
</block>
|
||||
|
||||
<block wx:for="{{records}}" wx:key="id">
|
||||
<view class="record-card">
|
||||
<!-- 头部主信息 -->
|
||||
<view class="record-header">
|
||||
<view class="title-line">
|
||||
<text class="name">{{item.name || item.batch_name || item.batchName || '-'}}</text>
|
||||
<text class="code">编号:{{item.code || item.batch_number || item.batchNumber || '-'}}</text>
|
||||
</view>
|
||||
<view class="meta-line">
|
||||
<text class="status" wx:if="{{item.statusStr}}">状态:{{item.statusStr}}</text>
|
||||
<text class="time" wx:if="{{item.createdAtStr}}">创建时间:{{item.createdAtStr}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 详情字段(显示全部返回字段,中文映射) -->
|
||||
<view class="record-body">
|
||||
<block wx:for="{{item.displayPairs}}" wx:key="key" wx:for-item="pair">
|
||||
<view class="pair-row">
|
||||
<text class="pair-key">{{pair.keyZh}}</text>
|
||||
<text class="pair-val">{{pair.val}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination">
|
||||
<button class="page-btn" bindtap="prevPage" disabled="{{page <= 1}}">上一页</button>
|
||||
<scroll-view scroll-x class="page-numbers">
|
||||
<view class="page-items">
|
||||
<block wx:for="{{pages}}" wx:key="index">
|
||||
<view class="page-item {{current ? 'active' : ''}}" data-page="{{num}}" bindtap="goToPage">
|
||||
<text>{{num}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<button class="page-btn" bindtap="nextPage" disabled="{{page >= totalPages}}">下一页</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -1,119 +1,119 @@
|
||||
/* 页面容器 */
|
||||
.page-container {
|
||||
padding: 12rpx 16rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 64rpx;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 12rpx;
|
||||
background: #fff;
|
||||
}
|
||||
.search-btn {
|
||||
height: 64rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
/* 概览栏 */
|
||||
.summary-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
/* 列表滚动区 */
|
||||
.list-scroll {
|
||||
max-height: calc(100vh - 280rpx);
|
||||
}
|
||||
|
||||
.loading, .empty {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
/* 卡片 */
|
||||
.record-card {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0,0,0,0.06);
|
||||
}
|
||||
.record-header .title-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.record-header .name {
|
||||
font-weight: 600;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.record-header .code {
|
||||
color: #333;
|
||||
}
|
||||
.record-header .meta-line {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.record-body .pair-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8rpx 0;
|
||||
border-bottom: 1px dashed #eee;
|
||||
}
|
||||
.pair-key {
|
||||
color: #888;
|
||||
}
|
||||
.pair-val {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 12rpx;
|
||||
background: #fff;
|
||||
padding: 12rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.page-numbers {
|
||||
width: 60%;
|
||||
}
|
||||
.page-items {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
}
|
||||
.page-item {
|
||||
min-width: 56rpx;
|
||||
height: 56rpx;
|
||||
line-height: 56rpx;
|
||||
text-align: center;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 12rpx;
|
||||
color: #333;
|
||||
}
|
||||
.page-item.active {
|
||||
background: #3cc51f;
|
||||
color: #fff;
|
||||
border-color: #3cc51f;
|
||||
}
|
||||
.page-btn[disabled] {
|
||||
opacity: 0.5;
|
||||
/* 页面容器 */
|
||||
.page-container {
|
||||
padding: 12rpx 16rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 64rpx;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 12rpx;
|
||||
background: #fff;
|
||||
}
|
||||
.search-btn {
|
||||
height: 64rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
/* 概览栏 */
|
||||
.summary-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
/* 列表滚动区 */
|
||||
.list-scroll {
|
||||
max-height: calc(100vh - 280rpx);
|
||||
}
|
||||
|
||||
.loading, .empty {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
/* 卡片 */
|
||||
.record-card {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0,0,0,0.06);
|
||||
}
|
||||
.record-header .title-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.record-header .name {
|
||||
font-weight: 600;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.record-header .code {
|
||||
color: #333;
|
||||
}
|
||||
.record-header .meta-line {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.record-body .pair-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8rpx 0;
|
||||
border-bottom: 1px dashed #eee;
|
||||
}
|
||||
.pair-key {
|
||||
color: #888;
|
||||
}
|
||||
.pair-val {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 12rpx;
|
||||
background: #fff;
|
||||
padding: 12rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.page-numbers {
|
||||
width: 60%;
|
||||
}
|
||||
.page-items {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
}
|
||||
.page-item {
|
||||
min-width: 56rpx;
|
||||
height: 56rpx;
|
||||
line-height: 56rpx;
|
||||
text-align: center;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 12rpx;
|
||||
color: #333;
|
||||
}
|
||||
.page-item.active {
|
||||
background: #3cc51f;
|
||||
color: #fff;
|
||||
border-color: #3cc51f;
|
||||
}
|
||||
.page-btn[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -1,421 +1,73 @@
|
||||
// pages/cattle/cattle.js
|
||||
const { get, del } = require('../../utils/api')
|
||||
const { formatDate, formatTime } = require('../../utils/index')
|
||||
|
||||
// pages/cattle/cattle.js - 生产管理/牛羊管理页面
|
||||
Page({
|
||||
data: {
|
||||
cattleList: [],
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
searchKeyword: '',
|
||||
// 设备编号精确查询
|
||||
deviceNumber: '',
|
||||
statusFilter: 'all',
|
||||
page: 1,
|
||||
// 按需求使用每页10条
|
||||
pageSize: 10,
|
||||
hasMore: true,
|
||||
total: 0,
|
||||
// 分页页码集合
|
||||
pages: [],
|
||||
lastPage: 1
|
||||
// 牛只管理功能列表
|
||||
cattleFunctions: [
|
||||
{ id: 1, name: '牛档案', iconText: '📁', color: 'icon-cyan', url: '/pages/cattle/archive/archive' },
|
||||
{ id: 2, name: '发情记录', iconText: '💗', color: 'icon-orange', url: '/pages/cattle/estrus/estrus' },
|
||||
{ id: 3, name: '配种记录', iconText: '🔬', color: 'icon-blue', url: '/pages/cattle/breeding/breeding' },
|
||||
{ id: 4, name: '妊娠记录', iconText: '🤰', color: 'icon-yellow', url: '/pages/cattle/pregnancy/pregnancy' },
|
||||
{ id: 5, name: '分娩记录', iconText: '👶', color: 'icon-green', url: '/pages/cattle/birth/birth' },
|
||||
{ id: 6, name: '断奶记录', iconText: '🍼', color: 'icon-purple', url: '/pages/cattle/weaning/weaning' },
|
||||
{ id: 7, name: '转栏记录', iconText: '🔄', color: 'icon-red', url: '/pages/cattle/transfer/transfer' },
|
||||
{ id: 8, name: '离栏记录', iconText: '🚪', color: 'icon-teal', url: '/pages/cattle/exit/exit' },
|
||||
{ id: 9, name: '栏舍设置', iconText: '🏠', color: 'icon-indigo', url: '/pages/cattle/pen/pen' },
|
||||
{ id: 10, name: '批次设置', iconText: '📦', color: 'icon-pink', url: '/pages/cattle/batch/batch' },
|
||||
{ id: 11, name: '防疫预警', iconText: '💉', color: 'icon-lime', url: '/pages/cattle/vaccination/vaccination' }
|
||||
],
|
||||
// 猪只管理功能列表
|
||||
pigFunctions: [
|
||||
{ id: 1, name: '猪档案', iconText: '📁', color: 'icon-cyan', url: '/pages/pig/archive/archive' },
|
||||
{ id: 2, name: '发情记录', iconText: '💗', color: 'icon-orange', url: '/pages/pig/estrus/estrus' },
|
||||
{ id: 3, name: '配种记录', iconText: '🔬', color: 'icon-blue', url: '/pages/pig/breeding/breeding' },
|
||||
{ id: 4, name: '妊娠记录', iconText: '🤰', color: 'icon-yellow', url: '/pages/pig/pregnancy/pregnancy' },
|
||||
{ id: 5, name: '分娩记录', iconText: '👶', color: 'icon-green', url: '/pages/pig/birth/birth' },
|
||||
{ id: 6, name: '断奶记录', iconText: '🍼', color: 'icon-purple', url: '/pages/pig/weaning/weaning' },
|
||||
{ id: 7, name: '转栏记录', iconText: '🔄', color: 'icon-red', url: '/pages/pig/transfer/transfer' },
|
||||
{ id: 8, name: '离栏记录', iconText: '🚪', color: 'icon-teal', url: '/pages/pig/exit/exit' },
|
||||
{ id: 9, name: '栏舍设置', iconText: '🏠', color: 'icon-indigo', url: '/pages/pig/pens/pens' },
|
||||
{ id: 10, name: '批次设置', iconText: '📦', color: 'icon-pink', url: '/pages/pig/batches/batches' },
|
||||
{ id: 11, name: '防疫预警', iconText: '💉', color: 'icon-lime', url: '/pages/pig/vaccination/vaccination' }
|
||||
]
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
// 获取筛选参数
|
||||
if (options.status) {
|
||||
this.setData({ statusFilter: options.status })
|
||||
}
|
||||
|
||||
this.loadCattleList()
|
||||
onLoad() {
|
||||
console.log('牛羊管理页面加载')
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadCattleList()
|
||||
console.log('牛羊管理页面显示')
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.setData({
|
||||
page: 1,
|
||||
hasMore: true,
|
||||
cattleList: []
|
||||
})
|
||||
this.loadCattleList().then(() => {
|
||||
// 下拉刷新
|
||||
setTimeout(() => {
|
||||
wx.stopPullDownRefresh()
|
||||
})
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
if (this.data.hasMore && !this.data.loading) {
|
||||
this.loadMoreCattle()
|
||||
}
|
||||
},
|
||||
|
||||
// 加载牛只列表
|
||||
async loadCattleList() {
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
const params = {
|
||||
// 页码与每页条数的常见别名,提升与后端的兼容性
|
||||
page: this.data.page,
|
||||
pageNo: this.data.page,
|
||||
pageIndex: this.data.page,
|
||||
current: this.data.page,
|
||||
pageSize: this.data.pageSize,
|
||||
size: this.data.pageSize,
|
||||
limit: this.data.pageSize,
|
||||
status: this.data.statusFilter === 'all' ? '' : this.data.statusFilter
|
||||
}
|
||||
|
||||
if (this.data.searchKeyword) {
|
||||
params.search = this.data.searchKeyword
|
||||
}
|
||||
// 设备编号精确查询参数(尽可能兼容后端不同命名)
|
||||
if (this.data.deviceNumber) {
|
||||
params.deviceNumber = this.data.deviceNumber
|
||||
params.deviceSn = this.data.deviceNumber
|
||||
params.deviceId = this.data.deviceNumber
|
||||
// 部分接口可能支持exact开关
|
||||
params.exact = true
|
||||
}
|
||||
|
||||
const response = await get('/iot-cattle/public', params)
|
||||
// 统一兼容响应结构(尽可能兼容不同后端命名)
|
||||
const list = (response && response.data && (response.data.list || response.data.records || response.data.items))
|
||||
|| (response && (response.list || response.records || response.items))
|
||||
|| []
|
||||
const totalRaw = (response && response.data && (response.data.total ?? response.data.totalCount ?? response.data.count))
|
||||
|| (response && (response.total ?? response.totalCount ?? response.count))
|
||||
|| (response && response.page && response.page.total)
|
||||
|| 0
|
||||
const totalPagesOverride = (response && response.data && (response.data.totalPages ?? response.data.pageCount))
|
||||
|| (response && (response.totalPages ?? response.pageCount))
|
||||
|| (response && response.page && (response.page.totalPages ?? response.page.pageCount))
|
||||
|| 0
|
||||
const total = Number(totalRaw) || 0
|
||||
const isUnknownTotal = !(Number(totalPagesOverride) > 0) && !(Number(totalRaw) > 0)
|
||||
console.log('牛只档案接口原始响应:', response)
|
||||
const mappedList = list.map(this.mapCattleRecord)
|
||||
console.log('牛只档案字段映射结果(当前页):', mappedList)
|
||||
const cattleList = this.data.page === 1 ? mappedList : [...this.data.cattleList, ...mappedList]
|
||||
// 根据总页数判断是否还有更多(兼容后端直接返回totalPages/pageCount)。
|
||||
// 当后端未返回 total/totalPages 时,使用“本页数据条数 == pageSize”作为是否还有更多的兜底策略。
|
||||
const totalPages = Math.max(1, Number(totalPagesOverride) || Math.ceil(total / this.data.pageSize))
|
||||
const hasMore = isUnknownTotal ? (mappedList.length >= this.data.pageSize) : (this.data.page < totalPages)
|
||||
this.setData({ cattleList, total, hasMore })
|
||||
console.log('分页计算:', { total, pageSize: this.data.pageSize, totalPages, currentPage: this.data.page, isUnknownTotal })
|
||||
// 生成分页页码
|
||||
this.buildPagination(total, totalPages, isUnknownTotal)
|
||||
} catch (error) {
|
||||
console.error('获取牛只列表失败:', error)
|
||||
wx.showToast({
|
||||
title: '获取数据失败',
|
||||
icon: 'none'
|
||||
title: '刷新成功',
|
||||
icon: 'success'
|
||||
})
|
||||
} finally {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
// 加载更多牛只
|
||||
async loadMoreCattle() {
|
||||
if (!this.data.hasMore || this.data.loading) return
|
||||
|
||||
this.setData({
|
||||
page: this.data.page + 1
|
||||
})
|
||||
|
||||
await this.loadCattleList()
|
||||
},
|
||||
|
||||
// 搜索输入
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchKeyword: e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
// 设备编号输入(精确查询)
|
||||
onDeviceInput(e) {
|
||||
this.setData({
|
||||
deviceNumber: e.detail.value.trim()
|
||||
})
|
||||
},
|
||||
|
||||
// 执行搜索
|
||||
onSearch() {
|
||||
this.setData({
|
||||
page: 1,
|
||||
hasMore: true,
|
||||
cattleList: []
|
||||
})
|
||||
this.loadCattleList()
|
||||
},
|
||||
|
||||
// 执行设备编号精确查询
|
||||
onDeviceSearch() {
|
||||
if (!this.data.deviceNumber) {
|
||||
wx.showToast({ title: '请输入设备编号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.setData({
|
||||
page: 1,
|
||||
hasMore: true,
|
||||
cattleList: [],
|
||||
// 精确查询时不使用模糊关键词
|
||||
searchKeyword: ''
|
||||
})
|
||||
this.loadCattleList()
|
||||
},
|
||||
|
||||
// 清空搜索
|
||||
onClearSearch() {
|
||||
this.setData({
|
||||
searchKeyword: '',
|
||||
deviceNumber: '',
|
||||
page: 1,
|
||||
hasMore: true,
|
||||
cattleList: []
|
||||
})
|
||||
this.loadCattleList()
|
||||
},
|
||||
|
||||
// 状态筛选
|
||||
onStatusFilter(e) {
|
||||
const status = e.currentTarget.dataset.status
|
||||
this.setData({
|
||||
statusFilter: status,
|
||||
page: 1,
|
||||
hasMore: true,
|
||||
cattleList: []
|
||||
})
|
||||
this.loadCattleList()
|
||||
},
|
||||
|
||||
// 查看牛只详情
|
||||
viewCattleDetail(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.navigateTo({
|
||||
url: `/pages/cattle/detail/detail?id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
// 添加牛只
|
||||
addCattle() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/cattle/add/add'
|
||||
})
|
||||
},
|
||||
|
||||
// 编辑牛只
|
||||
editCattle(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.navigateTo({
|
||||
url: `/pages/cattle/edit/edit?id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
// 删除牛只
|
||||
async deleteCattle(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
const name = e.currentTarget.dataset.name
|
||||
|
||||
const confirmed = await wx.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定要删除牛只"${name}"吗?`,
|
||||
confirmText: '删除',
|
||||
confirmColor: '#f5222d'
|
||||
})
|
||||
|
||||
if (confirmed) {
|
||||
try {
|
||||
wx.showLoading({ title: '删除中...' })
|
||||
|
||||
const response = await del(`/iot-cattle/${id}`)
|
||||
|
||||
if (response.success) {
|
||||
// 导航到功能页面
|
||||
navigateTo(e) {
|
||||
const url = e.currentTarget.dataset.url
|
||||
if (url) {
|
||||
wx.navigateTo({
|
||||
url,
|
||||
fail: () => {
|
||||
wx.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 刷新列表
|
||||
this.setData({
|
||||
page: 1,
|
||||
hasMore: true,
|
||||
cattleList: []
|
||||
})
|
||||
this.loadCattleList()
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: response.message || '删除失败',
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除牛只失败:', error)
|
||||
wx.showToast({
|
||||
title: '删除失败',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
wx.hideLoading()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(status) {
|
||||
const statusMap = {
|
||||
'normal': '正常',
|
||||
'pregnant': '怀孕',
|
||||
'sick': '生病',
|
||||
'quarantine': '隔离',
|
||||
'sold': '已售',
|
||||
'dead': '死亡'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
},
|
||||
|
||||
// 获取状态颜色
|
||||
getStatusColor(status) {
|
||||
const colorMap = {
|
||||
'normal': '#52c41a',
|
||||
'pregnant': '#faad14',
|
||||
'sick': '#f5222d',
|
||||
'quarantine': '#909399',
|
||||
'sold': '#1890ff',
|
||||
'dead': '#666666'
|
||||
}
|
||||
return colorMap[status] || '#909399'
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(date) {
|
||||
return formatDate(date)
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(time) {
|
||||
return formatTime(time)
|
||||
},
|
||||
|
||||
// 字段中文映射与安全处理
|
||||
mapCattleRecord(item = {}) {
|
||||
// 统一时间戳(后端可能返回秒级或毫秒级)
|
||||
const normalizeTs = (ts) => {
|
||||
if (ts === null || ts === undefined || ts === '') return ''
|
||||
if (typeof ts === 'number') {
|
||||
return ts < 1000000000000 ? ts * 1000 : ts
|
||||
}
|
||||
// 字符串数字
|
||||
const n = Number(ts)
|
||||
if (!Number.isNaN(n)) return n < 1000000000000 ? n * 1000 : n
|
||||
return ts
|
||||
}
|
||||
|
||||
// 性别映射(仅做友好展示,保留原值)
|
||||
const rawSex = item.sex ?? item.gender
|
||||
const sexText = rawSex === 1 || rawSex === '1' ? '公' : (rawSex === 2 || rawSex === '2' ? '母' : (rawSex ?? '-'))
|
||||
|
||||
return {
|
||||
// 基本标识
|
||||
id: item.id ?? item.cattleId ?? item._id ?? '',
|
||||
name: item.name ?? item.cattleName ?? '',
|
||||
earNumber: item.earNumber ?? item.earNo ?? item.earTag ?? '-',
|
||||
|
||||
// 基本属性
|
||||
breed: item.breed ?? item.breedName ?? item.varieties ?? '-',
|
||||
strain: item.strain ?? '-',
|
||||
varieties: item.varieties ?? '-',
|
||||
cate: item.cate ?? '-',
|
||||
gender: rawSex ?? '-',
|
||||
genderText: sexText,
|
||||
age: item.age ?? item.ageYear ?? '-',
|
||||
ageInMonths: item.ageInMonths ?? item.ageMonth ?? '-',
|
||||
|
||||
// 体重与计算
|
||||
weight: item.weight ?? item.currentWeight ?? '-',
|
||||
currentWeight: item.currentWeight ?? '-',
|
||||
birthWeight: item.birthWeight ?? '-',
|
||||
sourceWeight: item.sourceWeight ?? '-',
|
||||
weightCalculateTime: item.weightCalculateTime ? formatDate(normalizeTs(item.weightCalculateTime), 'YYYY-MM-DD HH:mm:ss') : '',
|
||||
|
||||
// 来源信息
|
||||
source: item.source ?? '-',
|
||||
sourceDay: item.sourceDay ?? '-',
|
||||
|
||||
// 关联位置与组织
|
||||
deviceNumber: item.deviceNumber ?? item.deviceSn ?? item.deviceId ?? '-',
|
||||
penId: item.penId ?? '-',
|
||||
penName: item.penName ?? item.barnName ?? '-',
|
||||
batchId: item.batchId ?? '-',
|
||||
batchName: item.batchName ?? '-',
|
||||
farmId: item.farmId ?? '-',
|
||||
farmName: item.farmName ?? '-',
|
||||
|
||||
// 生育与阶段
|
||||
parity: item.parity ?? '-',
|
||||
physiologicalStage: item.physiologicalStage ?? '-',
|
||||
status: item.status ?? item.cattleStatus ?? 'normal',
|
||||
|
||||
// 重要日期
|
||||
birthday: item.birthday ?? item.birthDate ?? item.bornDate ?? item.birthTime ?? '',
|
||||
birthdayStr: item.birthday || item.birthDate || item.bornDate || item.birthTime
|
||||
? formatDate(normalizeTs(item.birthday ?? item.birthDate ?? item.bornDate ?? item.birthTime))
|
||||
: '',
|
||||
dayOfBirthday: item.dayOfBirthday ?? '-',
|
||||
intoTime: item.intoTime ?? '',
|
||||
intoTimeStr: item.intoTime ? formatDate(normalizeTs(item.intoTime)) : ''
|
||||
}
|
||||
},
|
||||
|
||||
// 生成分页页码,控制展示范围并高亮当前页
|
||||
buildPagination(total, totalPagesOverride = 0, isUnknownTotal = false) {
|
||||
const pageSize = this.data.pageSize
|
||||
const current = this.data.page
|
||||
// 当总数未知时,按已加载的当前页生成页码(1..current),允许继续“下一页”。
|
||||
if (isUnknownTotal) {
|
||||
const pages = Array.from({ length: Math.max(1, current) }, (_, i) => i + 1)
|
||||
this.setData({ pages, lastPage: current })
|
||||
return
|
||||
}
|
||||
const totalPages = Math.max(1, Number(totalPagesOverride) || Math.ceil(total / pageSize))
|
||||
let pages = []
|
||||
const maxVisible = 9
|
||||
if (totalPages <= maxVisible) {
|
||||
pages = Array.from({ length: totalPages }, (_, i) => i + 1)
|
||||
})
|
||||
} else {
|
||||
// 滑动窗口
|
||||
let start = Math.max(1, current - 4)
|
||||
let end = Math.min(totalPages, start + maxVisible - 1)
|
||||
// 保证区间长度
|
||||
start = Math.max(1, end - maxVisible + 1)
|
||||
pages = Array.from({ length: end - start + 1 }, (_, i) => start + i)
|
||||
wx.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
this.setData({ pages, lastPage: totalPages })
|
||||
},
|
||||
|
||||
// 上一页
|
||||
onPrevPage() {
|
||||
if (this.data.page <= 1) return
|
||||
this.setData({ page: this.data.page - 1, cattleList: [] })
|
||||
this.loadCattleList()
|
||||
},
|
||||
|
||||
// 下一页
|
||||
onNextPage() {
|
||||
if (!this.data.hasMore) return
|
||||
this.setData({ page: this.data.page + 1, cattleList: [] })
|
||||
this.loadCattleList()
|
||||
},
|
||||
|
||||
// 切换页码
|
||||
onPageTap(e) {
|
||||
const targetPage = Number(e.currentTarget.dataset.page)
|
||||
if (!targetPage || targetPage === this.data.page) return
|
||||
this.setData({ page: targetPage, cattleList: [] })
|
||||
this.loadCattleList()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "生产管理",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f5f5f5",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,159 +1,39 @@
|
||||
<!--pages/cattle/cattle.wxml-->
|
||||
<view class="cattle-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input-wrapper">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="搜索牛只耳号"
|
||||
value="{{searchKeyword}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="onSearch"
|
||||
/>
|
||||
<text class="search-icon" bindtap="onSearch">🔍</text>
|
||||
</view>
|
||||
|
||||
<text wx:if="{{searchKeyword}}" class="clear-btn" bindtap="onClearSearch">清空</text>
|
||||
</view>
|
||||
|
||||
<!-- 状态筛选 -->
|
||||
<view class="status-filter">
|
||||
<view
|
||||
class="filter-item {{statusFilter === 'all' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="all"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
class="filter-item {{statusFilter === 'normal' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="normal"
|
||||
>
|
||||
正常
|
||||
</view>
|
||||
<view
|
||||
class="filter-item {{statusFilter === 'pregnant' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="pregnant"
|
||||
>
|
||||
怀孕
|
||||
</view>
|
||||
<view
|
||||
class="filter-item {{statusFilter === 'sick' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="sick"
|
||||
>
|
||||
生病
|
||||
</view>
|
||||
<view
|
||||
class="filter-item {{statusFilter === 'quarantine' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="quarantine"
|
||||
>
|
||||
隔离
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 牛只列表 -->
|
||||
<view class="cattle-list">
|
||||
<view
|
||||
wx:for="{{cattleList}}"
|
||||
wx:key="id"
|
||||
class="cattle-item"
|
||||
bindtap="viewCattleDetail"
|
||||
data-id="{{item.id}}"
|
||||
>
|
||||
<view class="cattle-avatar">
|
||||
<text class="avatar-icon">🐄</text>
|
||||
<!-- pages/cattle/cattle.wxml - 生产管理/牛羊管理页面 -->
|
||||
<view class="page-container">
|
||||
<scroll-view class="scroll-content" scroll-y>
|
||||
<!-- 牛只管理区块 -->
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<view class="header-line"></view>
|
||||
<text class="section-title">牛只管理</text>
|
||||
</view>
|
||||
|
||||
<view class="cattle-info">
|
||||
<view class="cattle-name">{{item.name || item.earNumber}}</view>
|
||||
<view class="cattle-details">
|
||||
<text class="detail-item">耳号: {{item.earNumber}}</text>
|
||||
<text class="detail-item">品种: {{item.breed || '未知'}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 扩展详情,展示全部字段 -->
|
||||
<view class="cattle-extra">
|
||||
<view class="extra-row"><text class="extra-label">性别:</text><text class="extra-value">{{item.genderText}}</text></view>
|
||||
<view class="extra-row"><text class="extra-label">血统:</text><text class="extra-value">{{item.strain || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">品系:</text><text class="extra-value">{{item.varieties || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">品类:</text><text class="extra-value">{{item.cate || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">年龄(月):</text><text class="extra-value">{{item.ageInMonths || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">出生日期:</text><text class="extra-value">{{item.birthdayStr || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">出生体重(kg):</text><text class="extra-value">{{item.birthWeight || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">当前体重(kg):</text><text class="extra-value">{{item.currentWeight || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">来源:</text><text class="extra-value">{{item.source || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">来源天数:</text><text class="extra-value">{{item.sourceDay || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">来源体重(kg):</text><text class="extra-value">{{item.sourceWeight || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">批次:</text><text class="extra-value">{{item.batchName || '-'}}(ID:{{item.batchId || '-'}})</text></view>
|
||||
<view class="extra-row"><text class="extra-label">农场:</text><text class="extra-value">{{item.farmName || '-'}}(ID:{{item.farmId || '-'}})</text></view>
|
||||
<view class="extra-row"><text class="extra-label">栏舍:</text><text class="extra-value">{{item.penName || '-'}}(ID:{{item.penId || '-'}})</text></view>
|
||||
<view class="extra-row"><text class="extra-label">进场日期:</text><text class="extra-value">{{item.intoTimeStr || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">胎次:</text><text class="extra-value">{{item.parity || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">生理阶段:</text><text class="extra-value">{{item.physiologicalStage || '-'}} </text></view>
|
||||
<view class="extra-row"><text class="extra-label">体重计算时间:</text><text class="extra-value">{{item.weightCalculateTime || '-'}} </text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="cattle-status">
|
||||
<view
|
||||
class="status-badge"
|
||||
style="background-color: {{getStatusColor(item.status)}}"
|
||||
>
|
||||
{{getStatusText(item.status)}}
|
||||
</view>
|
||||
<view class="cattle-actions">
|
||||
<text class="action-btn edit" bindtap="editCattle" data-id="{{item.id}}" catchtap="true">编辑</text>
|
||||
<text class="action-btn delete" bindtap="deleteCattle" data-id="{{item.id}}" data-name="{{item.name || item.earNumber}}" catchtap="true">删除</text>
|
||||
<view class="function-grid">
|
||||
<view class="function-item" wx:for="{{cattleFunctions}}" wx:key="id" bindtap="navigateTo" data-url="{{item.url}}">
|
||||
<view class="icon-box {{item.color}}">
|
||||
<text class="item-icon-text">{{item.iconText}}</text>
|
||||
</view>
|
||||
<text class="item-label">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view wx:if="{{cattleList.length === 0 && !loading}}" class="empty-state">
|
||||
<text class="empty-icon">🐄</text>
|
||||
<text class="empty-text">暂无牛只数据</text>
|
||||
<button class="add-btn" bindtap="addCattle">添加牛只</button>
|
||||
<!-- 猪只管理区块 -->
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<view class="header-line"></view>
|
||||
<text class="section-title">猪只管理</text>
|
||||
</view>
|
||||
<view class="function-grid">
|
||||
<view class="function-item" wx:for="{{pigFunctions}}" wx:key="id" bindtap="navigateTo" data-url="{{item.url}}">
|
||||
<view class="icon-box {{item.color}}">
|
||||
<text class="item-icon-text">{{item.iconText}}</text>
|
||||
</view>
|
||||
<text class="item-label">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view wx:if="{{hasMore && cattleList.length > 0}}" class="load-more">
|
||||
<text wx:if="{{loading}}">加载中...</text>
|
||||
<text wx:else>上拉加载更多</text>
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据 -->
|
||||
<view wx:if="{{!hasMore && cattleList.length > 0}}" class="no-more">
|
||||
<text>没有更多数据了</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页导航 -->
|
||||
<view wx:if="{{cattleList.length > 0}}" class="pagination">
|
||||
<view class="page-item {{page <= 1 ? 'disabled' : ''}}" bindtap="onPrevPage">上一页</view>
|
||||
<view class="page-item" data-page="1" bindtap="onPageTap">首页</view>
|
||||
<view
|
||||
wx:for="{{pages}}"
|
||||
wx:key="*this"
|
||||
class="page-item {{item === page ? 'active' : ''}}"
|
||||
data-page="{{item}}"
|
||||
bindtap="onPageTap"
|
||||
>{{item}}</view>
|
||||
<view class="page-item" data-page="{{lastPage}}" bindtap="onPageTap">末页</view>
|
||||
<view class="page-item {{page >= lastPage ? 'disabled' : ''}}" bindtap="onNextPage">下一页</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加按钮 -->
|
||||
<view class="fab" bindtap="addCattle">
|
||||
<text class="fab-icon">+</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading && cattleList.length === 0}}" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
<!-- 底部留白 -->
|
||||
<view class="bottom-space"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
@@ -1,365 +1,138 @@
|
||||
/* pages/cattle/cattle.wxss */
|
||||
.cattle-container {
|
||||
background-color: #f6f6f6;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 120rpx;
|
||||
/* pages/cattle/cattle.wxss - 生产管理/牛羊管理样式 */
|
||||
page {
|
||||
background-color: #f5f5f5;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
.page-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
.scroll-content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin-right: 16rpx;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 60rpx 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
/* 区块样式 */
|
||||
.section {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
font-size: 28rpx;
|
||||
color: #3cc51f;
|
||||
padding: 8rpx 16rpx;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
display: flex;
|
||||
background-color: #ffffff;
|
||||
padding: 16rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
padding: 12rpx 24rpx;
|
||||
margin-right: 16rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.filter-item.active {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.cattle-list {
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.cattle-item {
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 30rpx 30rpx;
|
||||
}
|
||||
|
||||
.header-line {
|
||||
width: 8rpx;
|
||||
height: 32rpx;
|
||||
background: linear-gradient(180deg, #52c41a 0%, #73d13d 100%);
|
||||
border-radius: 4rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* 功能网格 */
|
||||
.function-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 20rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s;
|
||||
margin: 0 20rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 10rpx;
|
||||
}
|
||||
|
||||
.cattle-item:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
.function-item {
|
||||
width: 20%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.cattle-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background-color: #f0f9ff;
|
||||
.icon-box {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.cattle-info {
|
||||
flex: 1;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.cattle-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.cattle-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.cattle-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 22rpx;
|
||||
color: #909399;
|
||||
margin-bottom: 2rpx;
|
||||
}
|
||||
|
||||
/* 扩展详情样式 */
|
||||
.cattle-extra {
|
||||
margin-top: 8rpx;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 6rpx 12rpx;
|
||||
}
|
||||
.extra-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.extra-label {
|
||||
color: #909399;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
.extra-value {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.cattle-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 20rpx;
|
||||
color: #ffffff;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.cattle-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
text-align: center;
|
||||
min-width: 60rpx;
|
||||
}
|
||||
|
||||
.action-btn.edit {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.action-btn.delete {
|
||||
background-color: #fff2f0;
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 120rpx 32rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
display: block;
|
||||
margin-bottom: 24rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
margin-bottom: 32rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 16rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.add-btn:active {
|
||||
background-color: #2ea617;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
/* 分页导航 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx 16rpx 40rpx;
|
||||
}
|
||||
|
||||
.page-item {
|
||||
min-width: 60rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
background-color: #f5f5f5;
|
||||
color: #606266;
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-item.active {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 分页禁用态 */
|
||||
.page-item.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fab {
|
||||
position: fixed;
|
||||
right: 32rpx;
|
||||
bottom: 120rpx;
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
background-color: #3cc51f;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 16rpx rgba(60, 197, 31, 0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.fab:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.fab-icon {
|
||||
font-size: 48rpx;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 120rpx 32rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
.item-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 16rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #909399;
|
||||
.item-icon-text {
|
||||
font-size: 48rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
.item-label {
|
||||
font-size: 24rpx;
|
||||
color: #333333;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.cattle-item {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.cattle-avatar {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.avatar-icon {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.cattle-name {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.fab {
|
||||
right: 24rpx;
|
||||
bottom: 100rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.fab-icon {
|
||||
font-size: 44rpx;
|
||||
}
|
||||
/* 图标背景颜色 */
|
||||
.icon-cyan {
|
||||
background: linear-gradient(135deg, #E3F9FF 0%, #B3ECFF 100%);
|
||||
}
|
||||
|
||||
.icon-orange {
|
||||
background: linear-gradient(135deg, #FFF3E0 0%, #FFE0B2 100%);
|
||||
}
|
||||
|
||||
.icon-blue {
|
||||
background: linear-gradient(135deg, #E3F2FD 0%, #BBDEFB 100%);
|
||||
}
|
||||
|
||||
.icon-yellow {
|
||||
background: linear-gradient(135deg, #FFF9E6 0%, #FFF3CC 100%);
|
||||
}
|
||||
|
||||
.icon-green {
|
||||
background: linear-gradient(135deg, #E8F9F0 0%, #C3F0D3 100%);
|
||||
}
|
||||
|
||||
.icon-purple {
|
||||
background: linear-gradient(135deg, #F3E5F5 0%, #E1BEE7 100%);
|
||||
}
|
||||
|
||||
.icon-red {
|
||||
background: linear-gradient(135deg, #FFEBEE 0%, #FFCDD2 100%);
|
||||
}
|
||||
|
||||
.icon-teal {
|
||||
background: linear-gradient(135deg, #E0F2F1 0%, #B2DFDB 100%);
|
||||
}
|
||||
|
||||
.icon-indigo {
|
||||
background: linear-gradient(135deg, #E8EAF6 0%, #C5CAE9 100%);
|
||||
}
|
||||
|
||||
.icon-pink {
|
||||
background: linear-gradient(135deg, #FCE4EC 0%, #F8BBD0 100%);
|
||||
}
|
||||
|
||||
.icon-lime {
|
||||
background: linear-gradient(135deg, #F9FBE7 0%, #F0F4C3 100%);
|
||||
}
|
||||
|
||||
/* 底部留白 */
|
||||
.bottom-space {
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// pages/cattle/detail/detail.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
id: null,
|
||||
cattle: null,
|
||||
loading: true
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
if (options.id) {
|
||||
this.setData({ id: options.id })
|
||||
this.loadData()
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '参数错误',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData() {
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
// 方式1:如果有详情接口
|
||||
// const res = await API.getCattleDetail(this.data.id)
|
||||
// const cattle = res.data || res
|
||||
|
||||
// 方式2:从列表中获取(临时方案)
|
||||
// 先获取列表,找到对应ID的牛只
|
||||
const res = await API.getCattleList({
|
||||
page: 1,
|
||||
pageSize: 1000 // 获取足够多的数据
|
||||
})
|
||||
|
||||
const list = res.data?.list || res.list || []
|
||||
const cattle = list.find(item => item.id === parseInt(this.data.id))
|
||||
|
||||
if (!cattle) {
|
||||
wx.showToast({
|
||||
title: '未找到该牛只信息',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化数据
|
||||
const formattedCattle = {
|
||||
...cattle,
|
||||
// 格式化出生日期
|
||||
birthdayText: cattle.birthday ? this.formatDate(cattle.birthday) : '-',
|
||||
// 格式化入场时间
|
||||
intoTimeText: cattle.intoTime ? this.formatDate(cattle.intoTime) : '-',
|
||||
// 格式化性别
|
||||
sexText: cattle.sex === 1 ? '公' : cattle.sex === 2 ? '母' : '未知',
|
||||
// 格式化生理阶段
|
||||
physiologicalStageText: this.getPhysiologicalStage(cattle.physiologicalStage),
|
||||
// 格式化来源
|
||||
sourceText: cattle.source === 1 ? '自繁' : cattle.source === 2 ? '外购' : '未知',
|
||||
// 格式化体重计算时间
|
||||
weightCalculateTimeText: cattle.weightCalculateTime ? this.formatDateTime(cattle.weightCalculateTime) : '-'
|
||||
}
|
||||
|
||||
this.setData({
|
||||
cattle: formattedCattle,
|
||||
loading: false
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
this.setData({ loading: false })
|
||||
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期(时间戳转日期)
|
||||
*/
|
||||
formatDate(timestamp) {
|
||||
if (!timestamp) return '-'
|
||||
const date = new Date(timestamp * 1000)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
formatDateTime(dateString) {
|
||||
if (!dateString) return '-'
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取生理阶段文本
|
||||
*/
|
||||
getPhysiologicalStage(stage) {
|
||||
const stages = {
|
||||
0: '犊牛期',
|
||||
1: '育成期',
|
||||
2: '成年期',
|
||||
3: '老年期'
|
||||
}
|
||||
return stages[stage] || '未知'
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.loadData()
|
||||
setTimeout(() => {
|
||||
wx.stopPullDownRefresh()
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "牛只详情",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
<!--pages/cattle/detail/detail.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 详情内容 -->
|
||||
<view class="detail-content" wx:else>
|
||||
<!-- 头部:耳号 -->
|
||||
<view class="detail-header">
|
||||
<text class="ear-number">耳号:{{cattle.earNumber}}</text>
|
||||
<text class="sex-badge {{cattle.sex === 1 ? 'male' : 'female'}}">{{cattle.sexText}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">基本信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">ID:</text>
|
||||
<text class="value">{{cattle.id}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">耳号:</text>
|
||||
<text class="value">{{cattle.earNumber}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">性别:</text>
|
||||
<text class="value">{{cattle.sexText}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">品种:</text>
|
||||
<text class="value">{{cattle.varieties || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">品系:</text>
|
||||
<text class="value">{{cattle.strain || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">分类:</text>
|
||||
<text class="value">{{cattle.cate || '-'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 出生信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">出生信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">出生日期:</text>
|
||||
<text class="value">{{cattle.birthdayText}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">出生体重:</text>
|
||||
<text class="value">{{cattle.birthWeight || '-'}} kg</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">月龄:</text>
|
||||
<text class="value">{{cattle.ageInMonths || 0}} 个月</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">出生天数:</text>
|
||||
<text class="value">{{cattle.dayOfBirthday || 0}} 天</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 入场信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">入场信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">入场时间:</text>
|
||||
<text class="value">{{cattle.intoTimeText}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">来源:</text>
|
||||
<text class="value">{{cattle.sourceText}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">来源日龄:</text>
|
||||
<text class="value">{{cattle.sourceDay || 0}} 天</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">来源体重:</text>
|
||||
<text class="value">{{cattle.sourceWeight || '-'}} kg</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 生理信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">生理信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">生理阶段:</text>
|
||||
<text class="value">{{cattle.physiologicalStageText}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">胎次:</text>
|
||||
<text class="value">{{cattle.parity || 0}} 胎</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 体重信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">体重信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">当前体重:</text>
|
||||
<text class="value">{{cattle.currentWeight || 0}} kg</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">体重计算时间:</text>
|
||||
<text class="value">{{cattle.weightCalculateTimeText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 场地信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">场地信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">养殖场ID:</text>
|
||||
<text class="value">{{cattle.farmId || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">养殖场名称:</text>
|
||||
<text class="value">{{cattle.farmName || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">栏舍ID:</text>
|
||||
<text class="value">{{cattle.penId || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">栏舍名称:</text>
|
||||
<text class="value">{{cattle.penName || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">批次ID:</text>
|
||||
<text class="value">{{cattle.batchId || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">批次名称:</text>
|
||||
<text class="value">{{cattle.batchName || '-'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 原始数据(调试用) -->
|
||||
<!-- <view class="info-section">
|
||||
<view class="section-title">原始数据</view>
|
||||
<view class="raw-data">
|
||||
<text>{{cattle}}</text>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/* pages/cattle/detail/detail.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 详情内容 */
|
||||
.detail-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 32rpx 24rpx;
|
||||
background: linear-gradient(135deg, #07c160 0%, #05a651 100%);
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
.ear-number {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sex-badge {
|
||||
padding: 8rpx 24rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 20rpx;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
color: #fff;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.sex-badge.male {
|
||||
background-color: rgba(33, 150, 243, 0.3);
|
||||
}
|
||||
|
||||
.sex-badge.female {
|
||||
background-color: rgba(233, 30, 99, 0.3);
|
||||
}
|
||||
|
||||
/* 信息区块 */
|
||||
.info-section {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1rpx solid #e9ecef;
|
||||
}
|
||||
|
||||
.info-list {
|
||||
padding: 12rpx 24rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-item .label {
|
||||
min-width: 220rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-item .value {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 原始数据 */
|
||||
.raw-data {
|
||||
padding: 20rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
margin: 20rpx;
|
||||
}
|
||||
|
||||
.raw-data text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// pages/cattle/exit-detail/exit-detail.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
id: null,
|
||||
record: null,
|
||||
loading: true
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
if (options.id) {
|
||||
this.setData({ id: options.id })
|
||||
this.loadData()
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '参数错误',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData() {
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
// 方式1:如果有详情接口
|
||||
// const res = await API.getExitRecordDetail(this.data.id)
|
||||
// const record = res.data || res
|
||||
|
||||
// 方式2:从列表中获取(临时方案)
|
||||
const res = await API.getExitRecords({
|
||||
page: 1,
|
||||
pageSize: 1000 // 获取足够多的数据
|
||||
})
|
||||
|
||||
const list = res.data?.list || res.data || res.list || []
|
||||
const record = list.find(item => item.id === parseInt(this.data.id))
|
||||
|
||||
if (!record) {
|
||||
wx.showToast({
|
||||
title: '未找到该离栏记录',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化数据
|
||||
const formattedRecord = {
|
||||
...record,
|
||||
// 格式化离栏日期
|
||||
exitDateText: record.exitDate ? this.formatDateTime(record.exitDate) : '-',
|
||||
// 格式化创建时间
|
||||
createdAtText: record.created_at ? this.formatDateTime(record.created_at) : '-',
|
||||
// 格式化更新时间
|
||||
updatedAtText: record.updated_at ? this.formatDateTime(record.updated_at) : '-',
|
||||
// 原栏舍信息
|
||||
originalPenName: record.originalPen?.name || '-',
|
||||
originalPenCode: record.originalPen?.code || '-',
|
||||
// 养殖场信息
|
||||
farmName: record.farm?.name || '-'
|
||||
}
|
||||
|
||||
this.setData({
|
||||
record: formattedRecord,
|
||||
loading: false
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
this.setData({ loading: false })
|
||||
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
formatDateTime(dateString) {
|
||||
if (!dateString) return '-'
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.loadData()
|
||||
setTimeout(() => {
|
||||
wx.stopPullDownRefresh()
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "离栏记录详情",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<!--pages/cattle/exit-detail/exit-detail.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 详情内容 -->
|
||||
<view class="detail-content" wx:else>
|
||||
<!-- 头部:记录编号 -->
|
||||
<view class="detail-header">
|
||||
<view class="header-left">
|
||||
<text class="record-label">记录编号</text>
|
||||
<text class="record-id">{{record.recordId}}</text>
|
||||
</view>
|
||||
<text class="status-badge">{{record.status || '已确认'}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">基本信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">记录ID:</text>
|
||||
<text class="value">{{record.id}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">记录编号:</text>
|
||||
<text class="value">{{record.recordId}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">状态:</text>
|
||||
<text class="value">{{record.status || '已确认'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 牛只信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">牛只信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">牛只ID:</text>
|
||||
<text class="value">{{record.animalId}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">耳号:</text>
|
||||
<text class="value">{{record.earNumber}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 离栏信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">离栏信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">离栏日期:</text>
|
||||
<text class="value">{{record.exitDateText}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">离栏原因:</text>
|
||||
<text class="value">{{record.exitReason || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">去向:</text>
|
||||
<text class="value">{{record.destination || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">处置方式:</text>
|
||||
<text class="value">{{record.disposalMethod || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">原栏舍ID:</text>
|
||||
<text class="value">{{record.originalPenId}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">原栏舍名称:</text>
|
||||
<text class="value">{{record.originalPenName}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">原栏舍编码:</text>
|
||||
<text class="value">{{record.originalPenCode}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 养殖场信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">养殖场信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">养殖场ID:</text>
|
||||
<text class="value">{{record.farmId}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">养殖场名称:</text>
|
||||
<text class="value">{{record.farmName}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">操作信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">操作人:</text>
|
||||
<text class="value">{{record.handler || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">备注:</text>
|
||||
<text class="value">{{record.remark || '-'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">时间信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">创建时间:</text>
|
||||
<text class="value">{{record.createdAtText}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">更新时间:</text>
|
||||
<text class="value">{{record.updatedAtText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/* pages/cattle/exit-detail/exit-detail.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 详情内容 */
|
||||
.detail-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 32rpx 24rpx;
|
||||
background: linear-gradient(135deg, #07c160 0%, #05a651 100%);
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.record-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.record-id {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 8rpx 24rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 20rpx;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
color: #fff;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 信息区块 */
|
||||
.info-section {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1rpx solid #e9ecef;
|
||||
}
|
||||
|
||||
.info-list {
|
||||
padding: 12rpx 24rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-item .label {
|
||||
min-width: 220rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-item .value {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
240
mini_program/farm-monitor-dashboard/pages/cattle/exit/exit.js
Normal file
240
mini_program/farm-monitor-dashboard/pages/cattle/exit/exit.js
Normal file
@@ -0,0 +1,240 @@
|
||||
// pages/cattle/exit/exit.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPages: 0,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
searchValue: ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData(isRefresh = false) {
|
||||
if (isRefresh) {
|
||||
this.setData({ refreshing: true })
|
||||
}
|
||||
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: this.data.page,
|
||||
pageSize: this.data.pageSize,
|
||||
_t: Date.now()
|
||||
}
|
||||
|
||||
// 如果有搜索条件,添加到参数中
|
||||
if (this.data.searchValue) {
|
||||
params.search = this.data.searchValue.trim() // 按耳号精确查询
|
||||
params.earNumber = this.data.searchValue.trim() // 兼容参数
|
||||
}
|
||||
|
||||
console.log('请求参数:', params)
|
||||
|
||||
const res = await API.getExitRecords(params)
|
||||
|
||||
console.log('离栏记录数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
let list = res.data?.list || res.data || res.list || []
|
||||
const pagination = res.data?.pagination || {}
|
||||
const total = pagination.total || res.total || list.length || 0
|
||||
|
||||
// 数据预处理:格式化时间和数据
|
||||
list = list.map(item => {
|
||||
return {
|
||||
...item,
|
||||
// 格式化离栏日期
|
||||
exitDateText: item.exitDate ? this.formatDateTime(item.exitDate) : '-',
|
||||
// 格式化创建时间
|
||||
createdAtText: item.created_at ? this.formatDateTime(item.created_at) : '-',
|
||||
// 格式化更新时间
|
||||
updatedAtText: item.updated_at ? this.formatDateTime(item.updated_at) : '-',
|
||||
// 原栏舍名称
|
||||
originalPenName: item.originalPen?.name || '-',
|
||||
// 养殖场名称
|
||||
farmName: item.farm?.name || '-'
|
||||
}
|
||||
})
|
||||
|
||||
// 如果有搜索条件,进行前端精确过滤(确保精确匹配)
|
||||
if (this.data.searchValue && this.data.searchValue.trim()) {
|
||||
const searchKey = this.data.searchValue.trim()
|
||||
list = list.filter(item => {
|
||||
const earNumber = String(item.earNumber || '')
|
||||
// 精确匹配:完全相等
|
||||
return earNumber === searchKey
|
||||
})
|
||||
|
||||
console.log('精确查找结果:', list.length, '条')
|
||||
|
||||
if (list.length === 0) {
|
||||
wx.showToast({
|
||||
title: '未找到匹配的记录',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const totalPages = pagination.pages || Math.ceil((this.data.searchValue ? list.length : total) / this.data.pageSize)
|
||||
|
||||
this.setData({
|
||||
list: list,
|
||||
total: total,
|
||||
totalPages: totalPages,
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
// 停止下拉刷新
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
this.setData({
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
formatDateTime(dateString) {
|
||||
if (!dateString) return '-'
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchValue: e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行搜索
|
||||
*/
|
||||
handleSearch() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空搜索
|
||||
*/
|
||||
clearSearch() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
page: 1
|
||||
})
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
*/
|
||||
changePage(e) {
|
||||
const page = e.currentTarget.dataset.page
|
||||
if (page !== this.data.page && page >= 1 && page <= this.data.totalPages) {
|
||||
this.setData({ page })
|
||||
this.loadData()
|
||||
// 滚动到顶部
|
||||
wx.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 300
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
prevPage() {
|
||||
if (this.data.page > 1) {
|
||||
this.setData({ page: this.data.page - 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
nextPage() {
|
||||
if (this.data.page < this.data.totalPages) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 查看离栏记录详情
|
||||
*/
|
||||
viewDetail(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
console.log('查看离栏记录详情:', id)
|
||||
|
||||
// 跳转到详情页
|
||||
wx.navigateTo({
|
||||
url: `/pages/cattle/exit-detail/exit-detail?id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 下拉刷新
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 上拉加载更多
|
||||
*/
|
||||
onReachBottom() {
|
||||
if (this.data.page < this.data.totalPages && !this.data.loading) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "离栏记录",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -1,51 +1,93 @@
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<view class="title">牛只管理 - 离栏记录</view>
|
||||
<view class="search-box">
|
||||
<input class="search-input" placeholder="输入耳号精确查询" value="{{searchEarNumber}}" bindinput="onSearchInput" />
|
||||
<button class="search-btn" bindtap="onSearch">查询</button>
|
||||
<button class="clear-btn" bindtap="onClearSearch">清空</button>
|
||||
<!--pages/cattle/exit/exit.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="🔍 请输入耳号"
|
||||
value="{{searchValue}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="handleSearch"
|
||||
type="text"
|
||||
/>
|
||||
<button class="search-btn" bindtap="handleSearch" wx:if="{{searchValue}}">搜索</button>
|
||||
<button class="clear-btn" bindtap="clearSearch" wx:if="{{searchValue}}">清空</button>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading && !refreshing}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 离栏记录列表 -->
|
||||
<view class="exit-list" wx:else>
|
||||
<view class="exit-card" wx:for="{{list}}" wx:key="id" bindtap="viewDetail" data-id="{{item.id}}">
|
||||
<!-- 记录编号 -->
|
||||
<view class="card-header">
|
||||
<text class="record-id">耳号:{{item.earNumber}}</text>
|
||||
<button class="edit-btn" size="mini" catchtap="editRecord" data-id="{{item.id}}">编辑</button>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<text class="label">转舍日期:</text>
|
||||
<text class="value">{{item.exitDateText}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">转入栏舍:</text>
|
||||
<text class="value">{{item.destination || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">转出栏舍:</text>
|
||||
<text class="value">{{item.originalPenName}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">登记人:</text>
|
||||
<text class="value">{{item.handler || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">登记日期:</text>
|
||||
<text class="value">{{item.createdAtText}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">备注:</text>
|
||||
<text class="value">{{item.remark || '--'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<view class="card-footer">
|
||||
<text class="page-indicator">{{page}}/{{totalPages}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-container" wx:if="{{!loading && list.length === 0}}">
|
||||
<text class="empty-icon">📋</text>
|
||||
<text class="empty-text">暂无离栏记录数据</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="list" wx:if="{{records && records.length > 0}}">
|
||||
<block wx:for="{{records}}" wx:key="id">
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<view class="left">
|
||||
<view class="main">耳号:{{item.earNumber}}</view>
|
||||
<view class="sub">离栏栏舍:{{item.pen}}</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="meta">状态:{{item.status ? item.status : '-'}} | 记录编号:{{item.recordId ? item.recordId : '-'}} | 农场:{{item.farmName ? item.farmName : '-'}} </view>
|
||||
<view class="time">离栏时间:{{item.exitTimeStr}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="details">
|
||||
<block wx:for="{{item.details}}" wx:key="key">
|
||||
<view class="row">
|
||||
<text class="label">{{item.label}}</text>
|
||||
<text class="value">{{item.value}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<view class="empty" wx:else>暂无数据</view>
|
||||
|
||||
<view class="pagination">
|
||||
<button class="prev" bindtap="onPrevPage" disabled="{{page<=1}}">上一页</button>
|
||||
<block wx:for="{{pages}}" wx:key="*this">
|
||||
<view class="page-item {{page==item ? 'active' : ''}}" data-page="{{item}}" bindtap="onPageTap">{{item}}</view>
|
||||
</block>
|
||||
<button class="next" bindtap="onNextPage" disabled="{{!hasMore}}">下一页</button>
|
||||
<!-- 分页 -->
|
||||
<view class="pagination" wx:if="{{totalPages > 1 && list.length > 0}}">
|
||||
<button class="page-btn" bindtap="prevPage" disabled="{{page === 1}}">上一页</button>
|
||||
<view class="page-numbers">
|
||||
<text class="page-number {{page === item ? 'active' : ''}}"
|
||||
wx:for="{{totalPages}}"
|
||||
wx:key="index"
|
||||
wx:for-item="item"
|
||||
wx:for-index="index"
|
||||
data-page="{{index + 1}}"
|
||||
bindtap="changePage">
|
||||
{{index + 1}}
|
||||
</text>
|
||||
</view>
|
||||
<button class="page-btn" bindtap="nextPage" disabled="{{page === totalPages}}">下一页</button>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<view class="tips">总数:{{total}};当前页:{{page}} / {{lastPage}}</view>
|
||||
<view class="loading" wx:if="{{loading}}">加载中...</view>
|
||||
<!-- 转栏登记按钮 -->
|
||||
<view class="add-btn">
|
||||
<text>转栏登记</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,32 +1,253 @@
|
||||
page {
|
||||
background: #f7f8fa;
|
||||
/* pages/cattle/exit/exit.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
padding-bottom: 140rpx;
|
||||
}
|
||||
.header { padding: 12px; background: #fff; border-bottom: 1px solid #eee; }
|
||||
.title { font-size: 16px; font-weight: 600; margin-bottom: 8px; }
|
||||
.search-box { display: flex; gap: 8px; }
|
||||
.search-input { flex: 1; border: 1px solid #ddd; padding: 6px 8px; border-radius: 4px; }
|
||||
.search-btn, .clear-btn { padding: 6px 12px; border-radius: 4px; background: #1677ff; color: #fff; }
|
||||
.clear-btn { background: #999; }
|
||||
|
||||
.list { padding: 12px; }
|
||||
.card { background: #fff; border-radius: 8px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); margin-bottom: 12px; }
|
||||
.card-header { display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 8px; margin-bottom: 8px; }
|
||||
.left .main { font-size: 16px; font-weight: bold; }
|
||||
.left .sub { font-size: 14px; color: #666; margin-top: 4px; }
|
||||
.right { text-align: right; }
|
||||
.right .meta { font-size: 12px; color: #333; }
|
||||
.right .time { font-size: 12px; color: #666; margin-top: 4px; }
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.details .row { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px dashed #f0f0f0; }
|
||||
.details .row:last-child { border-bottom: none; }
|
||||
.label { color: #666; }
|
||||
.value { color: #111; font-weight: 500; }
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 60rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.pagination { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px; }
|
||||
.page-item { padding: 4px 10px; border-radius: 4px; background: #fff; border: 1px solid #ddd; }
|
||||
.page-item.active { background: #1677ff; color: #fff; border-color: #1677ff; }
|
||||
.prev, .next { background: #fff; border: 1px solid #ddd; padding: 6px 12px; border-radius: 4px; }
|
||||
.prev[disabled], .next[disabled] { opacity: 0.5; }
|
||||
.search-btn {
|
||||
width: 120rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.footer { padding: 12px; text-align: center; color: #666; }
|
||||
.loading { margin-top: 8px; }
|
||||
.search-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
width: 100rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #fa5151;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 离栏记录列表 */
|
||||
.exit-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
/* 离栏记录卡片 */
|
||||
.exit-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 卡片头部 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.record-id {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
padding: 8rpx 24rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.edit-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 卡片主体 */
|
||||
.card-body {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 12rpx 0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
color: #666;
|
||||
min-width: 180rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-row .value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 卡片底部 */
|
||||
.card-footer {
|
||||
padding: 16rpx 24rpx;
|
||||
background-color: #f8f9fa;
|
||||
text-align: center;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.page-indicator {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 30rpx 0;
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
width: 150rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin: 0 10rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn[disabled] {
|
||||
background-color: #e0e0e0;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
margin: 0 20rpx;
|
||||
max-width: 400rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-number {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.page-number.active {
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
/* 转栏登记按钮 */
|
||||
.add-btn {
|
||||
position: fixed;
|
||||
bottom: 30rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 90%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
// pages/cattle/pen-detail/pen-detail.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
id: null,
|
||||
pen: null,
|
||||
loading: true
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
if (options.id) {
|
||||
this.setData({ id: options.id })
|
||||
this.loadData()
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '参数错误',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData() {
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
// 方式1:如果有详情接口
|
||||
// const res = await API.getPenDetail(this.data.id)
|
||||
// const pen = res.data || res
|
||||
|
||||
// 方式2:从列表中获取(临时方案)
|
||||
const res = await API.getPenList({
|
||||
page: 1,
|
||||
pageSize: 1000 // 获取足够多的数据
|
||||
})
|
||||
|
||||
const list = res.data?.list || res.data || res.list || []
|
||||
const pen = list.find(item => item.id === parseInt(this.data.id))
|
||||
|
||||
if (!pen) {
|
||||
wx.showToast({
|
||||
title: '未找到该栏舍',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化数据
|
||||
const formattedPen = {
|
||||
...pen,
|
||||
// 格式化创建时间
|
||||
createdAtText: pen.created_at ? this.formatDateTime(pen.created_at) : '-',
|
||||
// 格式化更新时间
|
||||
updatedAtText: pen.updated_at ? this.formatDateTime(pen.updated_at) : '-',
|
||||
// 养殖场信息
|
||||
farmName: pen.farm?.name || '-',
|
||||
// 状态文本
|
||||
statusText: pen.status === '启用' || pen.status === 1 ? '启用' : '未启用'
|
||||
}
|
||||
|
||||
this.setData({
|
||||
pen: formattedPen,
|
||||
loading: false
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
this.setData({ loading: false })
|
||||
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
formatDateTime(dateString) {
|
||||
if (!dateString) return '-'
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.loadData()
|
||||
setTimeout(() => {
|
||||
wx.stopPullDownRefresh()
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "栏舍详情",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<!--pages/cattle/pen-detail/pen-detail.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 详情内容 -->
|
||||
<view class="detail-content" wx:else>
|
||||
<!-- 头部:栏舍名称 -->
|
||||
<view class="detail-header">
|
||||
<view class="header-left">
|
||||
<text class="pen-label">栏舍名称</text>
|
||||
<text class="pen-name">{{pen.name}}</text>
|
||||
</view>
|
||||
<text class="status-badge">{{pen.statusText}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">基本信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">栏舍ID:</text>
|
||||
<text class="value">{{pen.id}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">栏舍名称:</text>
|
||||
<text class="value">{{pen.name}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">栏舍编码:</text>
|
||||
<text class="value">{{pen.code}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">栏舍类型:</text>
|
||||
<text class="value">{{pen.type || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">状态:</text>
|
||||
<text class="value">{{pen.statusText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 容量信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">容量信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">最大容量:</text>
|
||||
<text class="value">{{pen.capacity || 0}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">当前数量:</text>
|
||||
<text class="value">{{pen.currentCount || 0}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">剩余容量:</text>
|
||||
<text class="value">{{(pen.capacity || 0) - (pen.currentCount || 0)}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">面积:</text>
|
||||
<text class="value">{{pen.area || 0}}平方米</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 养殖场信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">养殖场信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">养殖场ID:</text>
|
||||
<text class="value">{{pen.farmId || pen.farm_id}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">养殖场名称:</text>
|
||||
<text class="value">{{pen.farmName}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 位置与备注 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">位置与备注</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">位置:</text>
|
||||
<text class="value">{{pen.location || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">备注:</text>
|
||||
<text class="value">{{pen.remark || '-'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">时间信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">创建时间:</text>
|
||||
<text class="value">{{pen.createdAtText}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">更新时间:</text>
|
||||
<text class="value">{{pen.updatedAtText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/* pages/cattle/pen-detail/pen-detail.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 详情内容 */
|
||||
.detail-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 32rpx 24rpx;
|
||||
background: linear-gradient(135deg, #07c160 0%, #05a651 100%);
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.pen-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.pen-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 8rpx 24rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 20rpx;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
color: #fff;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 信息区块 */
|
||||
.info-section {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1rpx solid #e9ecef;
|
||||
}
|
||||
|
||||
.info-list {
|
||||
padding: 12rpx 24rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-item .label {
|
||||
min-width: 200rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-item .value {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
239
mini_program/farm-monitor-dashboard/pages/cattle/pen/pen.js
Normal file
239
mini_program/farm-monitor-dashboard/pages/cattle/pen/pen.js
Normal file
@@ -0,0 +1,239 @@
|
||||
// pages/cattle/pen/pen.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPages: 0,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
searchValue: ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData(isRefresh = false) {
|
||||
if (isRefresh) {
|
||||
this.setData({ refreshing: true })
|
||||
}
|
||||
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: this.data.page,
|
||||
pageSize: this.data.pageSize,
|
||||
_t: Date.now()
|
||||
}
|
||||
|
||||
// 如果有搜索条件,添加到参数中
|
||||
if (this.data.searchValue) {
|
||||
params.name = this.data.searchValue.trim() // 按栏舍名称精确查询
|
||||
params.search = this.data.searchValue.trim() // 兼容参数
|
||||
}
|
||||
|
||||
console.log('请求参数:', params)
|
||||
|
||||
const res = await API.getPenList(params)
|
||||
|
||||
console.log('栏舍设置数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
let list = res.data?.list || res.data || res.list || []
|
||||
const pagination = res.data?.pagination || {}
|
||||
const total = pagination.total || res.total || list.length || 0
|
||||
|
||||
// 数据预处理:格式化时间和数据
|
||||
list = list.map(item => {
|
||||
return {
|
||||
...item,
|
||||
// 格式化创建时间
|
||||
createdAtText: item.created_at ? this.formatDateTime(item.created_at) : '-',
|
||||
// 格式化更新时间
|
||||
updatedAtText: item.updated_at ? this.formatDateTime(item.updated_at) : '-',
|
||||
// 养殖场名称
|
||||
farmName: item.farm?.name || '-',
|
||||
// 状态文本
|
||||
statusText: item.status === '启用' || item.status === 1 ? '启用' : '未启用',
|
||||
statusValue: item.status === '启用' || item.status === 1
|
||||
}
|
||||
})
|
||||
|
||||
// 如果有搜索条件,进行前端精确过滤(确保精确匹配)
|
||||
if (this.data.searchValue && this.data.searchValue.trim()) {
|
||||
const searchKey = this.data.searchValue.trim()
|
||||
list = list.filter(item => {
|
||||
const name = String(item.name || '')
|
||||
// 精确匹配:完全相等
|
||||
return name === searchKey
|
||||
})
|
||||
|
||||
console.log('精确查找结果:', list.length, '条')
|
||||
|
||||
if (list.length === 0) {
|
||||
wx.showToast({
|
||||
title: '未找到匹配的栏舍',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const totalPages = pagination.pages || Math.ceil((this.data.searchValue ? list.length : total) / this.data.pageSize)
|
||||
|
||||
this.setData({
|
||||
list: list,
|
||||
total: total,
|
||||
totalPages: totalPages,
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
// 停止下拉刷新
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
this.setData({
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
formatDateTime(dateString) {
|
||||
if (!dateString) return '-'
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchValue: e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行搜索
|
||||
*/
|
||||
handleSearch() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空搜索
|
||||
*/
|
||||
clearSearch() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
page: 1
|
||||
})
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
*/
|
||||
changePage(e) {
|
||||
const page = e.currentTarget.dataset.page
|
||||
if (page !== this.data.page && page >= 1 && page <= this.data.totalPages) {
|
||||
this.setData({ page })
|
||||
this.loadData()
|
||||
// 滚动到顶部
|
||||
wx.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 300
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
prevPage() {
|
||||
if (this.data.page > 1) {
|
||||
this.setData({ page: this.data.page - 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
nextPage() {
|
||||
if (this.data.page < this.data.totalPages) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 查看栏舍详情
|
||||
*/
|
||||
viewDetail(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
console.log('查看栏舍详情:', id)
|
||||
|
||||
// 跳转到详情页
|
||||
wx.navigateTo({
|
||||
url: `/pages/cattle/pen-detail/pen-detail?id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 下拉刷新
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 上拉加载更多
|
||||
*/
|
||||
onReachBottom() {
|
||||
if (this.data.page < this.data.totalPages && !this.data.loading) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "栏舍设置",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<!--pages/cattle/pen/pen.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="🔍 请输入栏舍名称"
|
||||
value="{{searchValue}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="handleSearch"
|
||||
type="text"
|
||||
/>
|
||||
<button class="search-btn" bindtap="handleSearch" wx:if="{{searchValue}}">搜索</button>
|
||||
<button class="clear-btn" bindtap="clearSearch" wx:if="{{searchValue}}">清空</button>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading && !refreshing}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 栏舍列表 -->
|
||||
<view class="pen-list" wx:else>
|
||||
<view class="pen-card" wx:for="{{list}}" wx:key="id" bindtap="viewDetail" data-id="{{item.id}}">
|
||||
<!-- 栏舍名称 -->
|
||||
<view class="card-header">
|
||||
<text class="pen-name">栏舍名:{{item.name}}</text>
|
||||
<button class="edit-btn" size="mini" catchtap="editPen" data-id="{{item.id}}">编辑</button>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<text class="label">动物类型:</text>
|
||||
<text class="value">牛</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">栏舍类型:</text>
|
||||
<text class="value">{{item.type || '--'}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">负责人:</text>
|
||||
<text class="value">admin</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">容量:</text>
|
||||
<text class="value">{{item.capacity || 0}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">状态:</text>
|
||||
<switch class="status-switch" checked="{{item.statusValue}}" catchtap="toggleStatus" data-id="{{item.id}}" />
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">创建人:</text>
|
||||
<text class="value">admin</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">创建时间:</text>
|
||||
<text class="value">{{item.createdAtText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<view class="card-footer">
|
||||
<text class="page-indicator">{{page}}/{{totalPages}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-container" wx:if="{{!loading && list.length === 0}}">
|
||||
<text class="empty-icon">🏠</text>
|
||||
<text class="empty-text">暂无栏舍设置数据</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination" wx:if="{{totalPages > 1 && list.length > 0}}">
|
||||
<button class="page-btn" bindtap="prevPage" disabled="{{page === 1}}">上一页</button>
|
||||
<view class="page-numbers">
|
||||
<text class="page-number {{page === item ? 'active' : ''}}"
|
||||
wx:for="{{totalPages}}"
|
||||
wx:key="index"
|
||||
wx:for-item="item"
|
||||
wx:for-index="index"
|
||||
data-page="{{index + 1}}"
|
||||
bindtap="changePage">
|
||||
{{index + 1}}
|
||||
</text>
|
||||
</view>
|
||||
<button class="page-btn" bindtap="nextPage" disabled="{{page === totalPages}}">下一页</button>
|
||||
</view>
|
||||
|
||||
<!-- 新增栏舍按钮 -->
|
||||
<view class="add-btn">
|
||||
<text>新增栏舍</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
258
mini_program/farm-monitor-dashboard/pages/cattle/pen/pen.wxss
Normal file
258
mini_program/farm-monitor-dashboard/pages/cattle/pen/pen.wxss
Normal file
@@ -0,0 +1,258 @@
|
||||
/* pages/cattle/pen/pen.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
padding-bottom: 140rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 60rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
width: 120rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.search-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
width: 100rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #fa5151;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 栏舍列表 */
|
||||
.pen-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
/* 栏舍卡片 */
|
||||
.pen-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 卡片头部 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.pen-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
padding: 8rpx 24rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.edit-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 卡片主体 */
|
||||
.card-body {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12rpx 0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
color: #666;
|
||||
min-width: 180rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-row .value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.status-switch {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
/* 卡片底部 */
|
||||
.card-footer {
|
||||
padding: 16rpx 24rpx;
|
||||
background-color: #f8f9fa;
|
||||
text-align: center;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.page-indicator {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 30rpx 0;
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
width: 150rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin: 0 10rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn[disabled] {
|
||||
background-color: #e0e0e0;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
margin: 0 20rpx;
|
||||
max-width: 400rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-number {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.page-number.active {
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
/* 新增栏舍按钮 */
|
||||
.add-btn {
|
||||
position: fixed;
|
||||
bottom: 30rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 90%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
<view class="page">
|
||||
<!-- 头部与搜索栏 -->
|
||||
<view class="header">
|
||||
<text class="title">牛只管理 · 栏舍设置</text>
|
||||
<view class="search-bar">
|
||||
<input class="search-input" placeholder="输入栏舍名称(精确)" bindinput="onSearchInput" confirm-type="search" bindconfirm="onSearchConfirm" value="{{searchName}}" />
|
||||
<button class="search-btn" bindtap="onSearchConfirm">查询</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<view class="list">
|
||||
<block wx:for="{{displayRecords}}" wx:key="index">
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<view class="main">
|
||||
<text class="name">栏舍:{{item.name || '-'}}</text>
|
||||
<text class="code">编码:{{item.code || '-'}}</text>
|
||||
</view>
|
||||
<view class="meta">
|
||||
<text>养殖场:{{item.farmName || '-'}} </text>
|
||||
<text>状态:{{item.status || '-'}} </text>
|
||||
<text>容量:{{item.capacity || '-'}} </text>
|
||||
</view>
|
||||
<view class="time">
|
||||
<text>创建时间:{{item.createdAtStr || '-'}} </text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card-body">
|
||||
<block wx:for="{{item.displayFields}}" wx:key="key">
|
||||
<view class="row">
|
||||
<text class="label">{{item.label}}:</text>
|
||||
<text class="value">{{item.value}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<view wx:if="{{!displayRecords || displayRecords.length === 0}}" class="empty">暂无数据</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination" wx:if="{{pages && pages.length}}">
|
||||
<button class="pager-btn" bindtap="onPrevPage" disabled="{{currentPage<=1}}">上一页</button>
|
||||
<block wx:for="{{pages}}" wx:key="page">
|
||||
<button class="page-item {{currentPage === item ? 'active' : ''}}" data-page="{{item}}" bindtap="onPageTap">{{item}}</button>
|
||||
</block>
|
||||
<button class="pager-btn" bindtap="onNextPage" disabled="{{!hasMore}}">下一页</button>
|
||||
</view>
|
||||
<view class="page">
|
||||
<!-- 头部与搜索栏 -->
|
||||
<view class="header">
|
||||
<text class="title">牛只管理 · 栏舍设置</text>
|
||||
<view class="search-bar">
|
||||
<input class="search-input" placeholder="输入栏舍名称(精确)" bindinput="onSearchInput" confirm-type="search" bindconfirm="onSearchConfirm" value="{{searchName}}" />
|
||||
<button class="search-btn" bindtap="onSearchConfirm">查询</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<view class="list">
|
||||
<block wx:for="{{displayRecords}}" wx:key="index">
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<view class="main">
|
||||
<text class="name">栏舍:{{item.name || '-'}}</text>
|
||||
<text class="code">编码:{{item.code || '-'}}</text>
|
||||
</view>
|
||||
<view class="meta">
|
||||
<text>养殖场:{{item.farmName || '-'}} </text>
|
||||
<text>状态:{{item.status || '-'}} </text>
|
||||
<text>容量:{{item.capacity || '-'}} </text>
|
||||
</view>
|
||||
<view class="time">
|
||||
<text>创建时间:{{item.createdAtStr || '-'}} </text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card-body">
|
||||
<block wx:for="{{item.displayFields}}" wx:key="key">
|
||||
<view class="row">
|
||||
<text class="label">{{item.label}}:</text>
|
||||
<text class="value">{{item.value}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<view wx:if="{{!displayRecords || displayRecords.length === 0}}" class="empty">暂无数据</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination" wx:if="{{pages && pages.length}}">
|
||||
<button class="pager-btn" bindtap="onPrevPage" disabled="{{currentPage<=1}}">上一页</button>
|
||||
<block wx:for="{{pages}}" wx:key="page">
|
||||
<button class="page-item {{currentPage === item ? 'active' : ''}}" data-page="{{item}}" bindtap="onPageTap">{{item}}</button>
|
||||
</block>
|
||||
<button class="pager-btn" bindtap="onNextPage" disabled="{{!hasMore}}">下一页</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -1,27 +1,27 @@
|
||||
/* 栏舍设置页面样式 */
|
||||
.page { padding: 12px; background: #f7f8fa; min-height: 100vh; }
|
||||
.header { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
|
||||
.title { font-size: 16px; font-weight: 600; color: #333; }
|
||||
.search-bar { display: flex; gap: 8px; }
|
||||
.search-input { flex: 1; border: 1px solid #ddd; border-radius: 6px; padding: 8px 10px; background: #fff; }
|
||||
.search-btn { padding: 8px 12px; background: #3cc51f; color: #fff; border-radius: 6px; }
|
||||
|
||||
.list { display: flex; flex-direction: column; gap: 12px; }
|
||||
.card { background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); padding: 12px; }
|
||||
.card-header { display: flex; flex-direction: column; gap: 6px; border-bottom: 1px dashed #eee; padding-bottom: 8px; }
|
||||
.main { display: flex; gap: 10px; align-items: baseline; }
|
||||
.name { font-size: 16px; font-weight: 600; color: #222; }
|
||||
.code { font-size: 14px; color: #666; }
|
||||
.meta { display: flex; flex-wrap: wrap; gap: 10px; color: #666; font-size: 13px; }
|
||||
.time { color: #999; font-size: 12px; }
|
||||
|
||||
.card-body { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
|
||||
.row { display: flex; gap: 8px; }
|
||||
.label { color: #666; min-width: 84px; text-align: right; }
|
||||
.value { color: #333; flex: 1; }
|
||||
|
||||
.pagination { display: flex; gap: 6px; justify-content: center; align-items: center; margin-top: 14px; }
|
||||
.pager-btn { background: #f0f0f0; color: #333; border-radius: 6px; padding: 6px 10px; }
|
||||
.page-item { background: #fff; color: #333; border: 1px solid #ddd; border-radius: 6px; padding: 6px 10px; }
|
||||
.page-item.active { background: #3cc51f; border-color: #3cc51f; color: #fff; }
|
||||
/* 栏舍设置页面样式 */
|
||||
.page { padding: 12px; background: #f7f8fa; min-height: 100vh; }
|
||||
.header { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
|
||||
.title { font-size: 16px; font-weight: 600; color: #333; }
|
||||
.search-bar { display: flex; gap: 8px; }
|
||||
.search-input { flex: 1; border: 1px solid #ddd; border-radius: 6px; padding: 8px 10px; background: #fff; }
|
||||
.search-btn { padding: 8px 12px; background: #3cc51f; color: #fff; border-radius: 6px; }
|
||||
|
||||
.list { display: flex; flex-direction: column; gap: 12px; }
|
||||
.card { background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); padding: 12px; }
|
||||
.card-header { display: flex; flex-direction: column; gap: 6px; border-bottom: 1px dashed #eee; padding-bottom: 8px; }
|
||||
.main { display: flex; gap: 10px; align-items: baseline; }
|
||||
.name { font-size: 16px; font-weight: 600; color: #222; }
|
||||
.code { font-size: 14px; color: #666; }
|
||||
.meta { display: flex; flex-wrap: wrap; gap: 10px; color: #666; font-size: 13px; }
|
||||
.time { color: #999; font-size: 12px; }
|
||||
|
||||
.card-body { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
|
||||
.row { display: flex; gap: 8px; }
|
||||
.label { color: #666; min-width: 84px; text-align: right; }
|
||||
.value { color: #333; flex: 1; }
|
||||
|
||||
.pagination { display: flex; gap: 6px; justify-content: center; align-items: center; margin-top: 14px; }
|
||||
.pager-btn { background: #f0f0f0; color: #333; border-radius: 6px; padding: 6px 10px; }
|
||||
.page-item { background: #fff; color: #333; border: 1px solid #ddd; border-radius: 6px; padding: 6px 10px; }
|
||||
.page-item.active { background: #3cc51f; border-color: #3cc51f; color: #fff; }
|
||||
.empty { text-align: center; color: #999; padding: 20px 0; }
|
||||
@@ -0,0 +1,125 @@
|
||||
// pages/cattle/transfer-detail/transfer-detail.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
id: null,
|
||||
record: null,
|
||||
loading: true
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
if (options.id) {
|
||||
this.setData({ id: options.id })
|
||||
this.loadData()
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '参数错误',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData() {
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
// 方式1:如果有详情接口
|
||||
// const res = await API.getTransferRecordDetail(this.data.id)
|
||||
// const record = res.data || res
|
||||
|
||||
// 方式2:从列表中获取(临时方案)
|
||||
const res = await API.getTransferRecords({
|
||||
page: 1,
|
||||
pageSize: 1000 // 获取足够多的数据
|
||||
})
|
||||
|
||||
const list = res.data?.list || res.data || res.list || []
|
||||
const record = list.find(item => item.id === parseInt(this.data.id))
|
||||
|
||||
if (!record) {
|
||||
wx.showToast({
|
||||
title: '未找到该转栏记录',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化数据
|
||||
const formattedRecord = {
|
||||
...record,
|
||||
// 格式化转栏日期
|
||||
transferDateText: record.transferDate ? this.formatDateTime(record.transferDate) : '-',
|
||||
// 格式化创建时间
|
||||
createdAtText: record.created_at ? this.formatDateTime(record.created_at) : '-',
|
||||
// 格式化更新时间
|
||||
updatedAtText: record.updated_at ? this.formatDateTime(record.updated_at) : '-',
|
||||
// 转出栏舍信息
|
||||
fromPenName: record.fromPen?.name || '-',
|
||||
fromPenCode: record.fromPen?.code || '-',
|
||||
// 转入栏舍信息
|
||||
toPenName: record.toPen?.name || '-',
|
||||
toPenCode: record.toPen?.code || '-',
|
||||
// 养殖场信息
|
||||
farmName: record.farm?.name || '-'
|
||||
}
|
||||
|
||||
this.setData({
|
||||
record: formattedRecord,
|
||||
loading: false
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
this.setData({ loading: false })
|
||||
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
formatDateTime(dateString) {
|
||||
if (!dateString) return '-'
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.loadData()
|
||||
setTimeout(() => {
|
||||
wx.stopPullDownRefresh()
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "转栏记录详情",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<!--pages/cattle/transfer-detail/transfer-detail.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container" wx:if="{{loading}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 详情内容 -->
|
||||
<view class="detail-content" wx:else>
|
||||
<!-- 头部:记录编号 -->
|
||||
<view class="detail-header">
|
||||
<view class="header-left">
|
||||
<text class="record-label">记录编号</text>
|
||||
<text class="record-id">{{record.recordId}}</text>
|
||||
</view>
|
||||
<text class="status-badge">{{record.status || '已完成'}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">基本信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">记录ID:</text>
|
||||
<text class="value">{{record.id}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">记录编号:</text>
|
||||
<text class="value">{{record.recordId}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">状态:</text>
|
||||
<text class="value">{{record.status || '已完成'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 牛只信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">牛只信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">牛只ID:</text>
|
||||
<text class="value">{{record.animalId}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">耳号:</text>
|
||||
<text class="value">{{record.earNumber}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 转栏信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">转栏信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">转栏日期:</text>
|
||||
<text class="value">{{record.transferDateText}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">转出栏舍ID:</text>
|
||||
<text class="value">{{record.fromPenId}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">转出栏舍名称:</text>
|
||||
<text class="value">{{record.fromPenName}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">转出栏舍编码:</text>
|
||||
<text class="value">{{record.fromPenCode}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">转入栏舍ID:</text>
|
||||
<text class="value">{{record.toPenId}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">转入栏舍名称:</text>
|
||||
<text class="value">{{record.toPenName}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">转入栏舍编码:</text>
|
||||
<text class="value">{{record.toPenCode}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 养殖场信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">养殖场信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">养殖场ID:</text>
|
||||
<text class="value">{{record.farmId}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">养殖场名称:</text>
|
||||
<text class="value">{{record.farmName}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">操作信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">操作人:</text>
|
||||
<text class="value">{{record.operator || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">转栏原因:</text>
|
||||
<text class="value">{{record.reason || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">备注:</text>
|
||||
<text class="value">{{record.remark || '-'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">时间信息</view>
|
||||
<view class="info-list">
|
||||
<view class="info-item">
|
||||
<text class="label">创建时间:</text>
|
||||
<text class="value">{{record.createdAtText}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">更新时间:</text>
|
||||
<text class="value">{{record.updatedAtText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/* pages/cattle/transfer-detail/transfer-detail.wxss */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 详情内容 */
|
||||
.detail-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 32rpx 24rpx;
|
||||
background: linear-gradient(135deg, #07c160 0%, #05a651 100%);
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.record-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.record-id {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 8rpx 24rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 20rpx;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
color: #fff;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 信息区块 */
|
||||
.info-section {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1rpx solid #e9ecef;
|
||||
}
|
||||
|
||||
.info-list {
|
||||
padding: 12rpx 24rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-item .label {
|
||||
min-width: 240rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-item .value {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
// pages/cattle/transfer/transfer.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPages: 0,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
searchValue: ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData(isRefresh = false) {
|
||||
if (isRefresh) {
|
||||
this.setData({ refreshing: true })
|
||||
}
|
||||
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: this.data.page,
|
||||
pageSize: this.data.pageSize,
|
||||
_t: Date.now()
|
||||
}
|
||||
|
||||
// 如果有搜索条件,添加到参数中
|
||||
if (this.data.searchValue) {
|
||||
params.search = this.data.searchValue.trim() // 按耳号精确查询
|
||||
params.earNumber = this.data.searchValue.trim() // 兼容参数
|
||||
}
|
||||
|
||||
console.log('请求参数:', params)
|
||||
|
||||
const res = await API.getTransferRecords(params)
|
||||
|
||||
console.log('转栏记录数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
let list = res.data?.list || res.data || res.list || []
|
||||
const pagination = res.data?.pagination || {}
|
||||
const total = pagination.total || res.total || list.length || 0
|
||||
|
||||
// 数据预处理:格式化时间和数据
|
||||
list = list.map(item => {
|
||||
return {
|
||||
...item,
|
||||
// 格式化转栏日期
|
||||
transferDateText: item.transferDate ? this.formatDateTime(item.transferDate) : '-',
|
||||
// 格式化创建时间
|
||||
createdAtText: item.created_at ? this.formatDateTime(item.created_at) : '-',
|
||||
// 格式化更新时间
|
||||
updatedAtText: item.updated_at ? this.formatDateTime(item.updated_at) : '-',
|
||||
// 转出栏舍名称
|
||||
fromPenName: item.fromPen?.name || '-',
|
||||
// 转入栏舍名称
|
||||
toPenName: item.toPen?.name || '-',
|
||||
// 养殖场名称
|
||||
farmName: item.farm?.name || '-'
|
||||
}
|
||||
})
|
||||
|
||||
// 如果有搜索条件,进行前端精确过滤(确保精确匹配)
|
||||
if (this.data.searchValue && this.data.searchValue.trim()) {
|
||||
const searchKey = this.data.searchValue.trim()
|
||||
list = list.filter(item => {
|
||||
const earNumber = String(item.earNumber || '')
|
||||
// 精确匹配:完全相等
|
||||
return earNumber === searchKey
|
||||
})
|
||||
|
||||
console.log('精确查找结果:', list.length, '条')
|
||||
|
||||
if (list.length === 0) {
|
||||
wx.showToast({
|
||||
title: '未找到匹配的记录',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const totalPages = pagination.pages || Math.ceil((this.data.searchValue ? list.length : total) / this.data.pageSize)
|
||||
|
||||
this.setData({
|
||||
list: list,
|
||||
total: total,
|
||||
totalPages: totalPages,
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
// 停止下拉刷新
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
this.setData({
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
formatDateTime(dateString) {
|
||||
if (!dateString) return '-'
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchValue: e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行搜索
|
||||
*/
|
||||
handleSearch() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空搜索
|
||||
*/
|
||||
clearSearch() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
page: 1
|
||||
})
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
*/
|
||||
changePage(e) {
|
||||
const page = e.currentTarget.dataset.page
|
||||
if (page !== this.data.page && page >= 1 && page <= this.data.totalPages) {
|
||||
this.setData({ page })
|
||||
this.loadData()
|
||||
// 滚动到顶部
|
||||
wx.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 300
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
prevPage() {
|
||||
if (this.data.page > 1) {
|
||||
this.setData({ page: this.data.page - 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
nextPage() {
|
||||
if (this.data.page < this.data.totalPages) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 查看转栏记录详情
|
||||
*/
|
||||
viewDetail(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
console.log('查看转栏记录详情:', id)
|
||||
|
||||
// 跳转到详情页
|
||||
wx.navigateTo({
|
||||
url: `/pages/cattle/transfer-detail/transfer-detail?id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 下拉刷新
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 上拉加载更多
|
||||
*/
|
||||
onReachBottom() {
|
||||
if (this.data.page < this.data.totalPages && !this.data.loading) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "转栏记录",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
|
||||
@@ -1,82 +1,93 @@
|
||||
<!-- pages/cattle/transfer/transfer.wxml -->
|
||||
<view class="transfer-container">
|
||||
<!-- 搜索栏:耳号精确查询 -->
|
||||
<!--pages/cattle/transfer/transfer.wxml-->
|
||||
<view class="page-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input-wrapper">
|
||||
<input class="search-input" placeholder="请输入耳号,精确查询" value="{{searchEarNumber}}" bindinput="onSearchInput" confirm-type="search" bindconfirm="onSearch"/>
|
||||
<text class="search-icon" bindtap="onSearch">🔍</text>
|
||||
</view>
|
||||
<text class="clear-btn" bindtap="onClearSearch" wx:if="{{searchEarNumber}}">清空</text>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<view class="record-list">
|
||||
<block wx:if="{{records.length > 0}}">
|
||||
<block wx:for="{{records}}" wx:key="id">
|
||||
<view class="record-item">
|
||||
<!-- 左侧头像/图标 -->
|
||||
<view class="record-avatar"><text class="avatar-icon">🏠</text></view>
|
||||
|
||||
<!-- 中间信息 -->
|
||||
<view class="record-info">
|
||||
<view class="record-title">
|
||||
<text class="ear-number">耳号:{{item.earNumber}}</text>
|
||||
</view>
|
||||
<!-- 扩展详情(全部字段展示,已去重头部字段) -->
|
||||
<view class="record-extra">
|
||||
<block wx:for="{{item.details}}" wx:key="{{item.key}}">
|
||||
<view class="extra-row">
|
||||
<text class="extra-label">{{item.label}}:</text>
|
||||
<text class="extra-value">{{item.value}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧元信息 -->
|
||||
<view class="record-meta">
|
||||
<text class="meta-item">状态:{{item.status || '-'}} </text>
|
||||
<text class="meta-item">编号:{{item.recordId || '-'}} </text>
|
||||
<text class="meta-item">农场:{{item.farmName || '-'}} </text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 加载更多/无更多 -->
|
||||
<view wx:if="{{hasMore}}" class="load-more">
|
||||
<text wx:if="{{loading}}">加载中...</text>
|
||||
<text wx:else>上拉或点击页码加载更多</text>
|
||||
</view>
|
||||
<view wx:if="{{!hasMore}}" class="no-more">
|
||||
<text>没有更多数据了</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 空态 -->
|
||||
<view wx:else class="empty-state">
|
||||
<text class="empty-icon">📄</text>
|
||||
<text class="empty-text">暂无转栏记录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页导航 -->
|
||||
<view wx:if="{{records.length > 0}}" class="pagination">
|
||||
<view class="page-item {{page <= 1 ? 'disabled' : ''}}" bindtap="onPrevPage">上一页</view>
|
||||
<view class="page-item" data-page="1" bindtap="onPageTap">首页</view>
|
||||
<view
|
||||
wx:for="{{pages}}"
|
||||
wx:key="*this"
|
||||
class="page-item {{item === page ? 'active' : ''}}"
|
||||
data-page="{{item}}"
|
||||
bindtap="onPageTap"
|
||||
>{{item}}</view>
|
||||
<view class="page-item" data-page="{{lastPage}}" bindtap="onPageTap">末页</view>
|
||||
<view class="page-item {{page >= lastPage ? 'disabled' : ''}}" bindtap="onNextPage">下一页</view>
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="🔍 请输入耳号"
|
||||
value="{{searchValue}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="handleSearch"
|
||||
type="text"
|
||||
/>
|
||||
<button class="search-btn" bindtap="handleSearch" wx:if="{{searchValue}}">搜索</button>
|
||||
<button class="clear-btn" bindtap="clearSearch" wx:if="{{searchValue}}">清空</button>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading && records.length === 0}}" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
<view class="loading-container" wx:if="{{loading && !refreshing}}">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 转栏记录列表 -->
|
||||
<view class="transfer-list" wx:else>
|
||||
<view class="transfer-card" wx:for="{{list}}" wx:key="id" bindtap="viewDetail" data-id="{{item.id}}">
|
||||
<!-- 记录编号 -->
|
||||
<view class="card-header">
|
||||
<text class="record-id">耳号:{{item.earNumber}}</text>
|
||||
<button class="edit-btn" size="mini" catchtap="editRecord" data-id="{{item.id}}">编辑</button>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<text class="label">转栏日期:</text>
|
||||
<text class="value">{{item.transferDateText}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">转入栏舍:</text>
|
||||
<text class="value">{{item.toPenName}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">转出栏舍:</text>
|
||||
<text class="value">{{item.fromPenName}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">登记人:</text>
|
||||
<text class="value">{{item.operator || '-'}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">登记日期:</text>
|
||||
<text class="value">{{item.createdAtText}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">备注:</text>
|
||||
<text class="value">{{item.remark || '--'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<view class="card-footer">
|
||||
<text class="page-indicator">{{page}}/{{totalPages}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-container" wx:if="{{!loading && list.length === 0}}">
|
||||
<text class="empty-icon">📋</text>
|
||||
<text class="empty-text">暂无转栏记录数据</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination" wx:if="{{totalPages > 1 && list.length > 0}}">
|
||||
<button class="page-btn" bindtap="prevPage" disabled="{{page === 1}}">上一页</button>
|
||||
<view class="page-numbers">
|
||||
<text class="page-number {{page === item ? 'active' : ''}}"
|
||||
wx:for="{{totalPages}}"
|
||||
wx:key="index"
|
||||
wx:for-item="item"
|
||||
wx:for-index="index"
|
||||
data-page="{{index + 1}}"
|
||||
bindtap="changePage">
|
||||
{{index + 1}}
|
||||
</text>
|
||||
</view>
|
||||
<button class="page-btn" bindtap="nextPage" disabled="{{page === totalPages}}">下一页</button>
|
||||
</view>
|
||||
|
||||
<!-- 转栏登记按钮 -->
|
||||
<view class="add-btn">
|
||||
<text>转栏登记</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,211 +1,253 @@
|
||||
/* pages/cattle/transfer/transfer.wxss */
|
||||
.transfer-container {
|
||||
background-color: #f6f6f6;
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
padding-bottom: 120rpx;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
padding-bottom: 140rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin-right: 16rpx;
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
flex: 1;
|
||||
height: 60rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 60rpx 0 24rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
.search-btn {
|
||||
width: 120rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.search-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
width: 100rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin-left: 20rpx;
|
||||
background-color: #fa5151;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
color: #3cc51f;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
padding: 16rpx;
|
||||
.clear-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.record-item:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.record-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background-color: #f0f9ff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
align-items: center;
|
||||
height: 400rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 转栏记录列表 */
|
||||
.transfer-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
/* 转栏记录卡片 */
|
||||
.transfer-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 卡片头部 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.record-id {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
padding: 8rpx 24rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.edit-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 卡片主体 */
|
||||
.card-body {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 12rpx 0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
color: #666;
|
||||
min-width: 180rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.record-info {
|
||||
.info-row .value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
margin-right: 16rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.record-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
/* 卡片底部 */
|
||||
.card-footer {
|
||||
padding: 16rpx 24rpx;
|
||||
background-color: #f8f9fa;
|
||||
text-align: center;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.ear-number {
|
||||
color: #303133;
|
||||
.page-indicator {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.record-details {
|
||||
/* 空状态 */
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.record-extra {
|
||||
margin-top: 8rpx;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 6rpx 12rpx;
|
||||
}
|
||||
|
||||
.extra-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.extra-label {
|
||||
color: #909399;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.extra-value {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.record-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 22rpx;
|
||||
color: #909399;
|
||||
margin-bottom: 2rpx;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
/* 分页导航 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx 16rpx 40rpx;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.page-item {
|
||||
min-width: 60rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
background-color: #f5f5f5;
|
||||
color: #606266;
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.page-item.active {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.page-item.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 120rpx 32rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 16rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #909399;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 30rpx 0;
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
width: 150rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0;
|
||||
margin: 0 10rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn[disabled] {
|
||||
background-color: #e0e0e0;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
margin: 0 20rpx;
|
||||
max-width: 400rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-number {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.page-number.active {
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
/* 转栏登记按钮 */
|
||||
.add-btn {
|
||||
position: fixed;
|
||||
bottom: 30rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 90%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
57
mini_program/farm-monitor-dashboard/pages/cattle/图标说明.md
Normal file
57
mini_program/farm-monitor-dashboard/pages/cattle/图标说明.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 生产管理页面图标说明
|
||||
|
||||
## 需要的图标列表
|
||||
|
||||
### 牛只管理图标(11个)
|
||||
放置目录:`/images/cattle/`
|
||||
|
||||
1. `archive.png` - 牛档案(浅蓝色图标)
|
||||
2. `estrus.png` - 发情记录(橙色图标)
|
||||
3. `breeding.png` - 配种记录(蓝色图标)
|
||||
4. `pregnancy.png` - 妊娠记录(黄色图标)
|
||||
5. `birth.png` - 分娩记录(绿色图标)
|
||||
6. `weaning.png` - 断奶记录(紫色图标)
|
||||
7. `transfer.png` - 转栏记录(红色图标)
|
||||
8. `exit.png` - 离栏记录(青色图标)
|
||||
9. `pens.png` - 栏舍设置(靛蓝色图标)
|
||||
10. `batches.png` - 批次设置(粉色图标)
|
||||
11. `vaccination.png` - 防疫预警(黄绿色图标)
|
||||
|
||||
### 猪只管理图标(11个)
|
||||
放置目录:`/images/pig/`
|
||||
|
||||
1. `archive.png` - 猪档案(浅蓝色图标)
|
||||
2. `estrus.png` - 发情记录(橙色图标)
|
||||
3. `breeding.png` - 配种记录(蓝色图标)
|
||||
4. `pregnancy.png` - 妊娠记录(黄色图标)
|
||||
5. `birth.png` - 分娩记录(绿色图标)
|
||||
6. `weaning.png` - 断奶记录(紫色图标)
|
||||
7. `transfer.png` - 转栏记录(红色图标)
|
||||
8. `exit.png` - 离栏记录(青色图标)
|
||||
9. `pens.png` - 栏舍设置(靛蓝色图标)
|
||||
10. `batches.png` - 批次设置(粉色图标)
|
||||
11. `vaccination.png` - 防疫预警(黄绿色图标)
|
||||
|
||||
## 图标规格
|
||||
- 尺寸:96x96 像素
|
||||
- 格式:PNG(透明背景)
|
||||
- 风格:线性图标,简洁风格
|
||||
|
||||
## 获取方式
|
||||
1. 从 iconfont.cn 搜索下载
|
||||
2. 请设计师制作
|
||||
3. 暂时可不放置图标文件,页面仍可正常显示背景色
|
||||
|
||||
## 推荐搜索关键词
|
||||
- 档案:folder, file, document
|
||||
- 发情:heart, love
|
||||
- 配种:breeding
|
||||
- 妊娠:pregnant
|
||||
- 分娩:birth, baby
|
||||
- 断奶:weaning
|
||||
- 转栏:transfer, move
|
||||
- 离栏:exit, leave
|
||||
- 栏舍:house, pen
|
||||
- 批次:batch, group
|
||||
- 防疫:vaccine, shield
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
// pages/device/collar/collar.js - 智能项圈页面
|
||||
const { API } = require('../../../utils/api.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [], // 数据列表
|
||||
page: 1, // 当前页码
|
||||
limit: 10, // 每页数量
|
||||
total: 0, // 总数量
|
||||
totalPages: 0, // 总页数
|
||||
searchValue: '', // 搜索值
|
||||
loading: false, // 加载状态
|
||||
refreshing: false // 刷新状态
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
console.log('智能项圈页面加载')
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
console.log('智能项圈页面显示')
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.setData({ page: 1, refreshing: true })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData(isRefresh = false) {
|
||||
if (this.data.loading) return
|
||||
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: this.data.page,
|
||||
limit: this.data.limit,
|
||||
_t: Date.now(),
|
||||
refresh: isRefresh
|
||||
}
|
||||
|
||||
// 如果有搜索条件,添加到参数中
|
||||
if (this.data.searchValue) {
|
||||
params.sn = this.data.searchValue.trim() // 按项圈SN编号精确查询
|
||||
params.number = this.data.searchValue.trim() // 兼容参数
|
||||
}
|
||||
|
||||
console.log('请求参数:', params)
|
||||
|
||||
const res = await API.getCollarList(params)
|
||||
|
||||
console.log('智能项圈数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
let list = res.data?.list || res.data || res.list || []
|
||||
const total = res.data?.total || res.total || 0
|
||||
|
||||
// 如果有搜索条件,进行前端精确过滤(确保精确匹配)
|
||||
if (this.data.searchValue && this.data.searchValue.trim()) {
|
||||
const searchKey = this.data.searchValue.trim()
|
||||
list = list.filter(item => {
|
||||
const sn = String(item.sn || item.number || '')
|
||||
// 精确匹配:完全相等
|
||||
return sn === searchKey
|
||||
})
|
||||
|
||||
console.log('精确查找结果:', list.length, '条')
|
||||
|
||||
if (list.length === 0) {
|
||||
wx.showToast({
|
||||
title: '未找到匹配的设备',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const totalPages = Math.ceil((this.data.searchValue ? list.length : total) / this.data.limit)
|
||||
|
||||
this.setData({
|
||||
list: list,
|
||||
total: total,
|
||||
totalPages: totalPages,
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
// 停止下拉刷新
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
this.setData({
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchValue: e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行搜索
|
||||
*/
|
||||
handleSearch() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空搜索
|
||||
*/
|
||||
clearSearch() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
page: 1
|
||||
})
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
*/
|
||||
changePage(e) {
|
||||
const page = e.currentTarget.dataset.page
|
||||
if (page === this.data.page || page < 1 || page > this.data.totalPages) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({ page: page })
|
||||
this.loadData()
|
||||
|
||||
// 滚动到顶部
|
||||
wx.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 300
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
prevPage() {
|
||||
if (this.data.page > 1) {
|
||||
this.setData({ page: this.data.page - 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
nextPage() {
|
||||
if (this.data.page < this.data.totalPages) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
*/
|
||||
viewDetail(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.navigateTo({
|
||||
url: `/pages/device/collar-detail/collar-detail?id=${id}`,
|
||||
fail: () => {
|
||||
wx.showToast({
|
||||
title: '详情页面开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "智能项圈",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f5f5f5",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,217 +1,102 @@
|
||||
|
||||
<view class="container">
|
||||
<!-- 搜索区域 -->
|
||||
<view class="search-box">
|
||||
<input
|
||||
placeholder="请输入项圈编号"
|
||||
bindinput="onSearchInput"
|
||||
value="{{searchValue}}"
|
||||
/>
|
||||
<button bindtap="onSearch" class="search-btn">查询</button>
|
||||
<button wx:if="{{isSearching}}" bindtap="clearSearch" class="clear-btn">清除</button>
|
||||
</view>
|
||||
|
||||
<!-- 搜索状态提示 -->
|
||||
<view wx:if="{{isSearching}}" class="search-status">
|
||||
<text>搜索项圈编号: {{searchValue}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<view wx:if="{{isSearching && searchResult}}" class="search-result">
|
||||
<view class="result-header">
|
||||
<text class="result-title">搜索结果</text>
|
||||
<text class="result-subtitle">找到匹配的设备</text>
|
||||
</view>
|
||||
<view class="item search-item" bindtap="viewCollarDetail" data-id="{{searchResult.id}}">
|
||||
<!-- 设备基本信息 -->
|
||||
<view class="item-header">
|
||||
<text class="device-sn">{{searchResult.snText}}</text>
|
||||
<text class="device-status {{searchResult.status === '在线' ? 'online' : 'offline'}}">{{searchResult.statusText}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 设备详细信息 -->
|
||||
<view class="item-content">
|
||||
<view class="info-row">
|
||||
<text class="label">项圈编号:</text>
|
||||
<text class="value">{{searchResult.sn}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">佩戴状态:</text>
|
||||
<text class="value {{searchResult.is_wear === 1 ? 'wear-on' : 'wear-off'}}">{{searchResult.wearStatusText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">连接状态:</text>
|
||||
<text class="value {{searchResult.is_connect === 1 ? 'connect-on' : 'connect-off'}}">{{searchResult.connectStatusText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">电池电量:</text>
|
||||
<text class="value battery-{{searchResult.batteryPercent > 50 ? 'high' : searchResult.batteryPercent > 20 ? 'medium' : 'low'}}">{{searchResult.batteryText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">体温:</text>
|
||||
<text class="value">{{searchResult.temperatureText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">步数:</text>
|
||||
<text class="value">{{searchResult.stepsText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">信号强度:</text>
|
||||
<text class="value">{{searchResult.signalText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">GPS信号:</text>
|
||||
<text class="value">{{searchResult.gpsText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">位置状态:</text>
|
||||
<text class="value">{{searchResult.locationText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">最后更新:</text>
|
||||
<text class="value">{{searchResult.lastUpdateText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">更新间隔:</text>
|
||||
<text class="value">{{searchResult.updateIntervalText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="item-actions">
|
||||
<button class="btn-detail" size="mini" bindtap="viewCollarDetail" data-id="{{searchResult.id}}" catchtap="true">查看详情</button>
|
||||
<button class="btn-edit" size="mini" bindtap="editCollar" data-id="{{searchResult.id}}" catchtap="true">编辑</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 无搜索结果 -->
|
||||
<view wx:if="{{isSearching && !searchResult}}" class="no-result">
|
||||
<view class="no-result-icon">🔍</view>
|
||||
<text class="no-result-text">未找到项圈编号为 "{{searchValue}}" 的设备</text>
|
||||
<button class="retry-btn" bindtap="clearSearch">重新搜索</button>
|
||||
</view>
|
||||
|
||||
<!-- 数据列表 -->
|
||||
<view wx:if="{{!isSearching}}" class="list">
|
||||
<block wx:for="{{list}}" wx:key="id">
|
||||
<view class="item" bindtap="viewCollarDetail" data-id="{{item.id}}">
|
||||
<!-- 设备基本信息 -->
|
||||
<view class="item-header">
|
||||
<text class="device-sn">{{item.snText}}</text>
|
||||
<text class="device-status {{item.status === '在线' ? 'online' : 'offline'}}">{{item.statusText}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 设备详细信息 -->
|
||||
<view class="item-content">
|
||||
<view class="info-row">
|
||||
<text class="label">项圈编号:</text>
|
||||
<text class="value">{{item.sn}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">佩戴状态:</text>
|
||||
<text class="value {{item.is_wear === 1 ? 'wear-on' : 'wear-off'}}">{{item.wearStatusText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">连接状态:</text>
|
||||
<text class="value {{item.is_connect === 1 ? 'connect-on' : 'connect-off'}}">{{item.connectStatusText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">电池电量:</text>
|
||||
<text class="value battery-{{item.batteryPercent > 50 ? 'high' : item.batteryPercent > 20 ? 'medium' : 'low'}}">{{item.batteryText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">体温:</text>
|
||||
<text class="value">{{item.temperatureText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">步数:</text>
|
||||
<text class="value">{{item.stepsText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">信号强度:</text>
|
||||
<text class="value">{{item.signalText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">GPS信号:</text>
|
||||
<text class="value">{{item.gpsText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">位置状态:</text>
|
||||
<text class="value">{{item.locationText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">最后更新:</text>
|
||||
<text class="value">{{item.lastUpdateText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">更新间隔:</text>
|
||||
<text class="value">{{item.updateIntervalText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="item-actions">
|
||||
<button class="btn-detail" size="mini" bindtap="viewCollarDetail" data-id="{{item.id}}" catchtap="true">查看详情</button>
|
||||
<button class="btn-edit" size="mini" bindtap="editCollar" data-id="{{item.id}}" catchtap="true">编辑</button>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<view class="pagination" wx:if="{{!isSearching && totalPages > 1}}">
|
||||
<view class="pagination-info">
|
||||
<text>共 {{total}} 条数据,第 {{currentPage}} / {{totalPages}} 页</text>
|
||||
</view>
|
||||
<view class="pagination-buttons">
|
||||
<button
|
||||
class="page-btn prev-btn {{currentPage <= 1 ? 'disabled' : ''}}"
|
||||
bindtap="onPrevPage"
|
||||
disabled="{{currentPage <= 1}}"
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
|
||||
<view class="page-numbers">
|
||||
<block wx:for="{{pageNumbers}}" wx:key="index">
|
||||
<text
|
||||
class="page-number {{currentPage === item ? 'active' : ''}}"
|
||||
bindtap="onPageChange"
|
||||
data-page="{{item}}"
|
||||
>
|
||||
{{item}}
|
||||
</text>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<button
|
||||
class="page-btn next-btn {{currentPage >= totalPages ? 'disabled' : ''}}"
|
||||
bindtap="onNextPage"
|
||||
disabled="{{currentPage >= totalPages}}"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- pages/device/collar/collar.wxml - 智能项圈页面 -->
|
||||
<view class="page-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input-wrapper">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="请输入项圈编号"
|
||||
value="{{searchValue}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="handleSearch"
|
||||
/>
|
||||
<view class="search-clear" wx:if="{{searchValue}}" bindtap="clearSearch">✕</view>
|
||||
</view>
|
||||
<button class="search-btn" bindtap="handleSearch">搜索</button>
|
||||
</view>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<view class="data-stats">
|
||||
<text class="stats-text">共 {{total}} 条数据</text>
|
||||
<text class="stats-text">第 {{page}}/{{totalPages}} 页</text>
|
||||
</view>
|
||||
|
||||
<!-- 数据列表 -->
|
||||
<view class="data-list">
|
||||
<view class="list-item" wx:for="{{list}}" wx:key="id" bindtap="viewDetail" data-id="{{item.id}}">
|
||||
<view class="item-header">
|
||||
<text class="item-number">{{item.sn || item.number}}</text>
|
||||
<text class="item-status {{item.status === '在线' ? 'status-online' : 'status-offline'}}">
|
||||
{{item.status || '离线'}}
|
||||
</text>
|
||||
</view>
|
||||
<view class="item-content">
|
||||
<view class="item-row">
|
||||
<text class="item-label">设备状态:</text>
|
||||
<text class="item-value">{{item.deviceStatus || '-'}}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">佩戴状态:</text>
|
||||
<text class="item-value">{{item.undefinedInfo || (item.is_wear ? '已佩戴' : '未佩戴')}}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">电量:</text>
|
||||
<text class="item-value">{{item.battery || 0}}%</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">温度:</text>
|
||||
<text class="item-value">{{item.temperature || '-'}}℃</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">步数:</text>
|
||||
<text class="item-value">{{item.steps || 0}}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">定位:</text>
|
||||
<text class="item-value">{{item.location || '-'}}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">更新时间:</text>
|
||||
<text class="item-value">{{item.lastUpdate || item.updateTime || '-'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:if="{{!loading && list.length === 0}}">
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" wx:if="{{loading}}">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页器 -->
|
||||
<view class="pagination" wx:if="{{totalPages > 1}}">
|
||||
<view class="page-btn {{page === 1 ? 'disabled' : ''}}" bindtap="prevPage">
|
||||
<text>上一页</text>
|
||||
</view>
|
||||
|
||||
<view class="page-numbers">
|
||||
<block wx:for="{{totalPages}}" wx:key="index">
|
||||
<view
|
||||
class="page-number {{page === index + 1 ? 'active' : ''}}"
|
||||
wx:if="{{index + 1 === 1 || index + 1 === totalPages || (index + 1 >= page - 2 && index + 1 <= page + 2)}}"
|
||||
bindtap="changePage"
|
||||
data-page="{{index + 1}}"
|
||||
>
|
||||
{{index + 1}}
|
||||
</view>
|
||||
<view class="page-ellipsis" wx:if="{{index + 1 === page - 3 || index + 1 === page + 3}}">
|
||||
...
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<view class="page-btn {{page === totalPages ? 'disabled' : ''}}" bindtap="nextPage">
|
||||
<text>下一页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,356 +1,215 @@
|
||||
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
margin-bottom: 20rpx;
|
||||
gap: 15rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
flex: 1;
|
||||
border: 2rpx solid #e8e8e8;
|
||||
border-radius: 25rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
font-size: 28rpx;
|
||||
background: #fafafa;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
border-color: #1890ff;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 0 4rpx rgba(24, 144, 255, 0.1);
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: linear-gradient(135deg, #1890ff, #40a9ff);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 25rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.search-btn:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 2rpx 8rpx rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
background: linear-gradient(135deg, #ff4d4f, #ff7875);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 25rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 77, 79, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.clear-btn:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 2rpx 8rpx rgba(255, 77, 79, 0.3);
|
||||
}
|
||||
|
||||
.search-status {
|
||||
background: linear-gradient(135deg, #e6f7ff, #bae7ff);
|
||||
border: 2rpx solid #91d5ff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.search-status text {
|
||||
color: #1890ff;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-result {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
background: linear-gradient(135deg, #f6ffed, #d9f7be);
|
||||
border: 2rpx solid #b7eb8f;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #52c41a;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.result-subtitle {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #73d13d;
|
||||
}
|
||||
|
||||
.search-item {
|
||||
border: 2rpx solid #52c41a;
|
||||
box-shadow: 0 4rpx 16rpx rgba(82, 196, 26, 0.2);
|
||||
}
|
||||
|
||||
.no-result {
|
||||
text-align: center;
|
||||
padding: 80rpx 40rpx;
|
||||
background: #fafafa;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.no-result-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.no-result-text {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 40rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: linear-gradient(135deg, #1890ff, #40a9ff);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 25rpx;
|
||||
padding: 20rpx 40rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
border: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 15rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.device-sn {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.device-status.online {
|
||||
background: #e8f5e8;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.device-status.offline {
|
||||
background: #fff2e8;
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
padding: 8rpx 0;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.info-row .value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 状态颜色 */
|
||||
.wear-on {
|
||||
color: #52c41a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.wear-off {
|
||||
color: #ff4d4f;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.connect-on {
|
||||
color: #52c41a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.connect-off {
|
||||
color: #ff4d4f;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.battery-high {
|
||||
color: #52c41a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.battery-medium {
|
||||
color: #fa8c16;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.battery-low {
|
||||
color: #ff4d4f;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 20rpx;
|
||||
padding-top: 15rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.btn-detail {
|
||||
background: #1890ff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background: #52c41a;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 40rpx;
|
||||
padding: 30rpx;
|
||||
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
|
||||
border: 2rpx solid #e8e8e8;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
text-align: center;
|
||||
margin-bottom: 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
padding: 15rpx;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #e8e8e8;
|
||||
}
|
||||
|
||||
.pagination-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 16rpx 28rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
border: 2rpx solid #d9d9d9;
|
||||
background: linear-gradient(135deg, #fff, #f8f9fa);
|
||||
color: #333;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
|
||||
.page-btn:not(.disabled):active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.page-btn.disabled {
|
||||
background: linear-gradient(135deg, #f5f5f5, #e8e8e8);
|
||||
color: #ccc;
|
||||
border-color: #d9d9d9;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
|
||||
.page-number {
|
||||
padding: 16rpx 20rpx;
|
||||
border: 2rpx solid #d9d9d9;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
background: linear-gradient(135deg, #fff, #f8f9fa);
|
||||
min-width: 60rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.page-number:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.page-number.active {
|
||||
background: linear-gradient(135deg, #1890ff, #40a9ff);
|
||||
color: white;
|
||||
border-color: #1890ff;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
/* pages/device/collar/collar.wxss - 智能项圈样式 */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background-color: #ffffff;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 64rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.search-clear {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #cccccc;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
width: 120rpx;
|
||||
height: 64rpx;
|
||||
line-height: 64rpx;
|
||||
background-color: #52c41a;
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.search-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 数据统计 */
|
||||
.data-stats {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.stats-text {
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/* 数据列表 */
|
||||
.data-list {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
background-color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.item-number {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.item-status {
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
background-color: #e8f9f0;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: #fff1f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.item-row {
|
||||
display: flex;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
color: #999999;
|
||||
min-width: 160rpx;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
color: #333333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-state {
|
||||
padding: 80rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
/* 分页器 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 20rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 12rpx 24rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.page-btn.disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.page-number {
|
||||
min-width: 56rpx;
|
||||
height: 56rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.page-number.active {
|
||||
background-color: #52c41a;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.page-ellipsis {
|
||||
padding: 0 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
84
mini_program/farm-monitor-dashboard/pages/device/device.js
Normal file
84
mini_program/farm-monitor-dashboard/pages/device/device.js
Normal file
@@ -0,0 +1,84 @@
|
||||
// pages/device/device.js - 设备管理页面
|
||||
Page({
|
||||
data: {
|
||||
deviceList: [],
|
||||
activeTab: 'all',
|
||||
tabs: [
|
||||
{ key: 'all', name: '全部' },
|
||||
{ key: 'eartag', name: '耳标' },
|
||||
{ key: 'collar', name: '项圈' },
|
||||
{ key: 'host', name: '主机' },
|
||||
{ key: 'fence', name: '围栏' }
|
||||
],
|
||||
loading: false
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
console.log('设备管理页加载')
|
||||
this.loadDeviceList()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
console.log('设备管理页显示')
|
||||
this.loadDeviceList()
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadDeviceList()
|
||||
setTimeout(() => {
|
||||
wx.stopPullDownRefresh()
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
// 切换标签
|
||||
switchTab(e) {
|
||||
const tab = e.currentTarget.dataset.tab
|
||||
this.setData({ activeTab: tab })
|
||||
this.loadDeviceList()
|
||||
},
|
||||
|
||||
// 加载设备列表
|
||||
loadDeviceList() {
|
||||
this.setData({ loading: true })
|
||||
|
||||
// TODO: 从API加载设备列表
|
||||
// 模拟数据
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
deviceList: [
|
||||
{ id: 1, name: '耳标-001', type: 'eartag', status: 'online', battery: 85 },
|
||||
{ id: 2, name: '项圈-001', type: 'collar', status: 'online', battery: 92 },
|
||||
{ id: 3, name: '主机-001', type: 'host', status: 'online', battery: 100 },
|
||||
{ id: 4, name: '围栏-001', type: 'fence', status: 'online', battery: 78 }
|
||||
],
|
||||
loading: false
|
||||
})
|
||||
}, 500)
|
||||
},
|
||||
|
||||
// 查看设备详情
|
||||
viewDeviceDetail(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
const type = e.currentTarget.dataset.type
|
||||
|
||||
// 根据设备类型跳转到不同的详情页
|
||||
const urlMap = {
|
||||
eartag: `/pages/device/eartag/eartag?id=${id}`,
|
||||
collar: `/pages/device/collar/collar?id=${id}`,
|
||||
host: `/pages/device/host/host?id=${id}`,
|
||||
fence: `/pages/device/fence/fence?id=${id}`
|
||||
}
|
||||
|
||||
const url = urlMap[type] || `/pages/device/eartag/eartag?id=${id}`
|
||||
|
||||
wx.navigateTo({ url })
|
||||
},
|
||||
|
||||
// 添加设备
|
||||
addDevice() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/device/eartag-add/eartag-add'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "设备管理",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f6f6f6",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,148 +1,148 @@
|
||||
<!--pages/device/device.wxml-->
|
||||
<view class="device-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input-wrapper">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="搜索设备编号、名称..."
|
||||
value="{{searchKeyword}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="onSearch"
|
||||
/>
|
||||
<text class="search-icon" bindtap="onSearch">🔍</text>
|
||||
</view>
|
||||
<text wx:if="{{searchKeyword}}" class="clear-btn" bindtap="onClearSearch">清空</text>
|
||||
</view>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<!-- 设备类型筛选 -->
|
||||
<view class="filter-group">
|
||||
<view class="filter-label">类型:</view>
|
||||
<view class="filter-options">
|
||||
<view
|
||||
class="filter-option {{typeFilter === 'all' ? 'active' : ''}}"
|
||||
bindtap="onTypeFilter"
|
||||
data-type="all"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
wx:for="{{deviceTypes}}"
|
||||
wx:key="value"
|
||||
class="filter-option {{typeFilter === item.value ? 'active' : ''}}"
|
||||
bindtap="onTypeFilter"
|
||||
data-type="{{item.value}}"
|
||||
>
|
||||
{{item.icon}} {{item.label}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 状态筛选 -->
|
||||
<view class="filter-group">
|
||||
<view class="filter-label">状态:</view>
|
||||
<view class="filter-options">
|
||||
<view
|
||||
class="filter-option {{statusFilter === 'all' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="all"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
class="filter-option {{statusFilter === 'online' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="online"
|
||||
>
|
||||
在线
|
||||
</view>
|
||||
<view
|
||||
class="filter-option {{statusFilter === 'offline' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="offline"
|
||||
>
|
||||
离线
|
||||
</view>
|
||||
<view
|
||||
class="filter-option {{statusFilter === 'error' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="error"
|
||||
>
|
||||
故障
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设备列表 -->
|
||||
<view class="device-list">
|
||||
<view
|
||||
wx:for="{{deviceList}}"
|
||||
wx:key="id"
|
||||
class="device-item"
|
||||
bindtap="viewDeviceDetail"
|
||||
data-id="{{item.id}}"
|
||||
data-type="{{item.type}}"
|
||||
>
|
||||
<view class="device-icon">
|
||||
<text class="icon">{{getDeviceTypeInfo(item.type).icon}}</text>
|
||||
</view>
|
||||
|
||||
<view class="device-info">
|
||||
<view class="device-name">{{item.name || item.deviceNumber}}</view>
|
||||
<view class="device-details">
|
||||
<text class="detail-item">编号: {{item.deviceNumber}}</text>
|
||||
<text class="detail-item">类型: {{getDeviceTypeInfo(item.type).label}}</text>
|
||||
</view>
|
||||
<view class="device-meta">
|
||||
<text class="meta-item">位置: {{item.location || '未知'}}</text>
|
||||
<text class="meta-item">最后更新: {{formatTime(item.lastUpdateTime)}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="device-status">
|
||||
<view
|
||||
class="status-badge"
|
||||
style="background-color: {{getStatusColor(item.status)}}"
|
||||
>
|
||||
{{getStatusText(item.status)}}
|
||||
</view>
|
||||
<view class="device-actions">
|
||||
<text class="action-btn edit" bindtap="editDevice" data-id="{{item.id}}" data-type="{{item.type}}" catchtap="true">编辑</text>
|
||||
<text class="action-btn delete" bindtap="deleteDevice" data-id="{{item.id}}" data-name="{{item.name || item.deviceNumber}}" catchtap="true">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view wx:if="{{deviceList.length === 0 && !loading}}" class="empty-state">
|
||||
<text class="empty-icon">📱</text>
|
||||
<text class="empty-text">暂无设备数据</text>
|
||||
<button class="add-btn" bindtap="addDevice">添加设备</button>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view wx:if="{{hasMore && deviceList.length > 0}}" class="load-more">
|
||||
<text wx:if="{{loading}}">加载中...</text>
|
||||
<text wx:else>上拉加载更多</text>
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据 -->
|
||||
<view wx:if="{{!hasMore && deviceList.length > 0}}" class="no-more">
|
||||
<text>没有更多数据了</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加按钮 -->
|
||||
<view class="fab" bindtap="addDevice">
|
||||
<text class="fab-icon">+</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading && deviceList.length === 0}}" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
<!--pages/device/device.wxml-->
|
||||
<view class="device-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input-wrapper">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="搜索设备编号、名称..."
|
||||
value="{{searchKeyword}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="onSearch"
|
||||
/>
|
||||
<text class="search-icon" bindtap="onSearch">🔍</text>
|
||||
</view>
|
||||
<text wx:if="{{searchKeyword}}" class="clear-btn" bindtap="onClearSearch">清空</text>
|
||||
</view>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<!-- 设备类型筛选 -->
|
||||
<view class="filter-group">
|
||||
<view class="filter-label">类型:</view>
|
||||
<view class="filter-options">
|
||||
<view
|
||||
class="filter-option {{typeFilter === 'all' ? 'active' : ''}}"
|
||||
bindtap="onTypeFilter"
|
||||
data-type="all"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
wx:for="{{deviceTypes}}"
|
||||
wx:key="value"
|
||||
class="filter-option {{typeFilter === item.value ? 'active' : ''}}"
|
||||
bindtap="onTypeFilter"
|
||||
data-type="{{item.value}}"
|
||||
>
|
||||
{{item.icon}} {{item.label}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 状态筛选 -->
|
||||
<view class="filter-group">
|
||||
<view class="filter-label">状态:</view>
|
||||
<view class="filter-options">
|
||||
<view
|
||||
class="filter-option {{statusFilter === 'all' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="all"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
class="filter-option {{statusFilter === 'online' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="online"
|
||||
>
|
||||
在线
|
||||
</view>
|
||||
<view
|
||||
class="filter-option {{statusFilter === 'offline' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="offline"
|
||||
>
|
||||
离线
|
||||
</view>
|
||||
<view
|
||||
class="filter-option {{statusFilter === 'error' ? 'active' : ''}}"
|
||||
bindtap="onStatusFilter"
|
||||
data-status="error"
|
||||
>
|
||||
故障
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设备列表 -->
|
||||
<view class="device-list">
|
||||
<view
|
||||
wx:for="{{deviceList}}"
|
||||
wx:key="id"
|
||||
class="device-item"
|
||||
bindtap="viewDeviceDetail"
|
||||
data-id="{{item.id}}"
|
||||
data-type="{{item.type}}"
|
||||
>
|
||||
<view class="device-icon">
|
||||
<text class="icon">{{getDeviceTypeInfo(item.type).icon}}</text>
|
||||
</view>
|
||||
|
||||
<view class="device-info">
|
||||
<view class="device-name">{{item.name || item.deviceNumber}}</view>
|
||||
<view class="device-details">
|
||||
<text class="detail-item">编号: {{item.deviceNumber}}</text>
|
||||
<text class="detail-item">类型: {{getDeviceTypeInfo(item.type).label}}</text>
|
||||
</view>
|
||||
<view class="device-meta">
|
||||
<text class="meta-item">位置: {{item.location || '未知'}}</text>
|
||||
<text class="meta-item">最后更新: {{formatTime(item.lastUpdateTime)}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="device-status">
|
||||
<view
|
||||
class="status-badge"
|
||||
style="background-color: {{getStatusColor(item.status)}}"
|
||||
>
|
||||
{{getStatusText(item.status)}}
|
||||
</view>
|
||||
<view class="device-actions">
|
||||
<text class="action-btn edit" bindtap="editDevice" data-id="{{item.id}}" data-type="{{item.type}}" catchtap="true">编辑</text>
|
||||
<text class="action-btn delete" bindtap="deleteDevice" data-id="{{item.id}}" data-name="{{item.name || item.deviceNumber}}" catchtap="true">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view wx:if="{{deviceList.length === 0 && !loading}}" class="empty-state">
|
||||
<text class="empty-icon">📱</text>
|
||||
<text class="empty-text">暂无设备数据</text>
|
||||
<button class="add-btn" bindtap="addDevice">添加设备</button>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view wx:if="{{hasMore && deviceList.length > 0}}" class="load-more">
|
||||
<text wx:if="{{loading}}">加载中...</text>
|
||||
<text wx:else>上拉加载更多</text>
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据 -->
|
||||
<view wx:if="{{!hasMore && deviceList.length > 0}}" class="no-more">
|
||||
<text>没有更多数据了</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加按钮 -->
|
||||
<view class="fab" bindtap="addDevice">
|
||||
<text class="fab-icon">+</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading && deviceList.length === 0}}" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,332 +1,332 @@
|
||||
/* pages/device/device.wxss */
|
||||
.device-container {
|
||||
background-color: #f6f6f6;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 60rpx 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
font-size: 28rpx;
|
||||
color: #3cc51f;
|
||||
padding: 8rpx 16rpx;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
background-color: #ffffff;
|
||||
padding: 16rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.filter-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 12rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.filter-option {
|
||||
padding: 8rpx 16rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
font-size: 22rpx;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.filter-option.active {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.device-list {
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.device-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.device-item:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.device-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background-color: #f0f9ff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.device-icon .icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
flex: 1;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.device-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.device-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 22rpx;
|
||||
color: #909399;
|
||||
margin-bottom: 2rpx;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 20rpx;
|
||||
color: #ffffff;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.device-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
text-align: center;
|
||||
min-width: 60rpx;
|
||||
}
|
||||
|
||||
.action-btn.edit {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.action-btn.delete {
|
||||
background-color: #fff2f0;
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 120rpx 32rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
display: block;
|
||||
margin-bottom: 24rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
margin-bottom: 32rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 16rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.add-btn:active {
|
||||
background-color: #2ea617;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.fab {
|
||||
position: fixed;
|
||||
right: 32rpx;
|
||||
bottom: 120rpx;
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
background-color: #3cc51f;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 16rpx rgba(60, 197, 31, 0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.fab:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.fab-icon {
|
||||
font-size: 48rpx;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 120rpx 32rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 16rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.device-item {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.device-icon {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.device-icon .icon {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.fab {
|
||||
right: 24rpx;
|
||||
bottom: 100rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.fab-icon {
|
||||
font-size: 44rpx;
|
||||
}
|
||||
}
|
||||
/* pages/device/device.wxss */
|
||||
.device-container {
|
||||
background-color: #f6f6f6;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 60rpx 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
font-size: 28rpx;
|
||||
color: #3cc51f;
|
||||
padding: 8rpx 16rpx;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
background-color: #ffffff;
|
||||
padding: 16rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.filter-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 12rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.filter-option {
|
||||
padding: 8rpx 16rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
font-size: 22rpx;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.filter-option.active {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.device-list {
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.device-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.device-item:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.device-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background-color: #f0f9ff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.device-icon .icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
flex: 1;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.device-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.device-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 22rpx;
|
||||
color: #909399;
|
||||
margin-bottom: 2rpx;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 20rpx;
|
||||
color: #ffffff;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.device-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
text-align: center;
|
||||
min-width: 60rpx;
|
||||
}
|
||||
|
||||
.action-btn.edit {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.action-btn.delete {
|
||||
background-color: #fff2f0;
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 120rpx 32rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
display: block;
|
||||
margin-bottom: 24rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #909399;
|
||||
margin-bottom: 32rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
background-color: #3cc51f;
|
||||
color: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 16rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.add-btn:active {
|
||||
background-color: #2ea617;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.fab {
|
||||
position: fixed;
|
||||
right: 32rpx;
|
||||
bottom: 120rpx;
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
background-color: #3cc51f;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 16rpx rgba(60, 197, 31, 0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.fab:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.fab-icon {
|
||||
font-size: 48rpx;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 120rpx 32rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 16rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.device-item {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.device-icon {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.device-icon .icon {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.fab {
|
||||
right: 24rpx;
|
||||
bottom: 100rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.fab-icon {
|
||||
font-size: 44rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +1,68 @@
|
||||
<!--pages/device/eartag-add/eartag-add.wxml-->
|
||||
<view class="eartag-add-container">
|
||||
<!-- 顶部导航 -->
|
||||
<view class="header">
|
||||
<view class="back-btn" bindtap="goBack">
|
||||
<text class="back-icon">←</text>
|
||||
</view>
|
||||
<text class="title">添加耳标</text>
|
||||
<view class="placeholder"></view>
|
||||
</view>
|
||||
|
||||
<!-- 表单 -->
|
||||
<view class="form-container">
|
||||
<view class="form-item">
|
||||
<text class="label">耳标编号</text>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入耳标编号"
|
||||
bindinput="onEartagNumberInput"
|
||||
value="{{formData.eartagNumber}}"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">主机号</text>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入主机号"
|
||||
bindinput="onHostNumberInput"
|
||||
value="{{formData.hostNumber}}"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">初始电量</text>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入初始电量"
|
||||
type="number"
|
||||
bindinput="onBatteryInput"
|
||||
value="{{formData.batteryLevel}}"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">备注</text>
|
||||
<textarea
|
||||
class="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
bindinput="onRemarkInput"
|
||||
value="{{formData.remark}}"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="submit-container">
|
||||
<view class="btn submit-btn" bindtap="onSubmit">
|
||||
<text>添加耳标</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading}}" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">添加中...</text>
|
||||
</view>
|
||||
</view>
|
||||
<!--pages/device/eartag-add/eartag-add.wxml-->
|
||||
<view class="eartag-add-container">
|
||||
<!-- 顶部导航 -->
|
||||
<view class="header">
|
||||
<view class="back-btn" bindtap="goBack">
|
||||
<text class="back-icon">←</text>
|
||||
</view>
|
||||
<text class="title">添加耳标</text>
|
||||
<view class="placeholder"></view>
|
||||
</view>
|
||||
|
||||
<!-- 表单 -->
|
||||
<view class="form-container">
|
||||
<view class="form-item">
|
||||
<text class="label">耳标编号</text>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入耳标编号"
|
||||
bindinput="onEartagNumberInput"
|
||||
value="{{formData.eartagNumber}}"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">主机号</text>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入主机号"
|
||||
bindinput="onHostNumberInput"
|
||||
value="{{formData.hostNumber}}"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">初始电量</text>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入初始电量"
|
||||
type="number"
|
||||
bindinput="onBatteryInput"
|
||||
value="{{formData.batteryLevel}}"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">备注</text>
|
||||
<textarea
|
||||
class="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
bindinput="onRemarkInput"
|
||||
value="{{formData.remark}}"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="submit-container">
|
||||
<view class="btn submit-btn" bindtap="onSubmit">
|
||||
<text>添加耳标</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading}}" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">添加中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,154 +1,154 @@
|
||||
/* pages/device/eartag-add/eartag-add.wxss */
|
||||
.eartag-add-container {
|
||||
background: #f8f9fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 顶部导航 */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 32rpx;
|
||||
background: #3cc51f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
/* 表单容器 */
|
||||
.form-container {
|
||||
background: #ffffff;
|
||||
margin: 24rpx 32rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.form-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 20rpx 24rpx;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
border-color: #3cc51f;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
min-height: 120rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
background: #ffffff;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.textarea:focus {
|
||||
border-color: #3cc51f;
|
||||
}
|
||||
|
||||
/* 提交按钮 */
|
||||
.submit-container {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
padding: 24rpx;
|
||||
background: #3cc51f;
|
||||
color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.submit-btn:active {
|
||||
background: #2ea617;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 80rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.header {
|
||||
padding: 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
margin: 20rpx 24rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.submit-container {
|
||||
padding: 24rpx;
|
||||
}
|
||||
}
|
||||
/* pages/device/eartag-add/eartag-add.wxss */
|
||||
.eartag-add-container {
|
||||
background: #f8f9fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 顶部导航 */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 32rpx;
|
||||
background: #3cc51f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
/* 表单容器 */
|
||||
.form-container {
|
||||
background: #ffffff;
|
||||
margin: 24rpx 32rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.form-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 20rpx 24rpx;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
border-color: #3cc51f;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
min-height: 120rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
background: #ffffff;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.textarea:focus {
|
||||
border-color: #3cc51f;
|
||||
}
|
||||
|
||||
/* 提交按钮 */
|
||||
.submit-container {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
padding: 24rpx;
|
||||
background: #3cc51f;
|
||||
color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.submit-btn:active {
|
||||
background: #2ea617;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 80rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.header {
|
||||
padding: 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
margin: 20rpx 24rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.submit-container {
|
||||
padding: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,112 +1,112 @@
|
||||
<!--pages/device/eartag-detail/eartag-detail.wxml-->
|
||||
<view class="eartag-detail-container">
|
||||
<!-- 顶部导航 -->
|
||||
<view class="header">
|
||||
<view class="back-btn" bindtap="goBack">
|
||||
<text class="back-icon">←</text>
|
||||
</view>
|
||||
<text class="title">耳标详情</text>
|
||||
<view class="placeholder"></view>
|
||||
</view>
|
||||
|
||||
<!-- 耳标信息 -->
|
||||
<view class="eartag-info">
|
||||
<view class="info-header">
|
||||
<text class="eartag-number">{{eartagData.eartagNumber}}</text>
|
||||
<view class="bind-status {{eartagData.isBound ? 'bound' : 'unbound'}}">
|
||||
{{eartagData.isBound ? '已绑定' : '未绑定'}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-details">
|
||||
<view class="detail-item">
|
||||
<text class="label">设备电量</text>
|
||||
<text class="value">{{eartagData.batteryLevel}}%</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">设备温度</text>
|
||||
<text class="value">{{eartagData.temperature}}°C</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">被采集主机</text>
|
||||
<text class="value">{{eartagData.hostNumber}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">总运动量</text>
|
||||
<text class="value">{{eartagData.totalMovement}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">今日运动量</text>
|
||||
<text class="value">{{eartagData.todayMovement}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">佩戴状态</text>
|
||||
<text class="value">{{eartagData.wearStatus}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">设备状态</text>
|
||||
<text class="value">{{eartagData.deviceStatus}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">位置信息</text>
|
||||
<text class="value">{{eartagData.location}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">GPS状态</text>
|
||||
<text class="value">{{eartagData.gpsState}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">纬度</text>
|
||||
<text class="value">{{eartagData.latitude}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">经度</text>
|
||||
<text class="value">{{eartagData.longitude}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">绑定状态</text>
|
||||
<text class="value">{{eartagData.bindingStatus}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">电压值</text>
|
||||
<text class="value">{{eartagData.voltage}}V</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">数据更新时间</text>
|
||||
<text class="value">{{eartagData.updateTime}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-buttons">
|
||||
<view class="btn primary" bindtap="onBind" wx:if="{{!eartagData.isBound}}">
|
||||
<text>绑定牛只</text>
|
||||
</view>
|
||||
<view class="btn secondary" bindtap="onUnbind" wx:if="{{eartagData.isBound}}">
|
||||
<text>解绑牛只</text>
|
||||
</view>
|
||||
<view class="btn default" bindtap="onEdit">
|
||||
<text>编辑信息</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading}}" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
<!--pages/device/eartag-detail/eartag-detail.wxml-->
|
||||
<view class="eartag-detail-container">
|
||||
<!-- 顶部导航 -->
|
||||
<view class="header">
|
||||
<view class="back-btn" bindtap="goBack">
|
||||
<text class="back-icon">←</text>
|
||||
</view>
|
||||
<text class="title">耳标详情</text>
|
||||
<view class="placeholder"></view>
|
||||
</view>
|
||||
|
||||
<!-- 耳标信息 -->
|
||||
<view class="eartag-info">
|
||||
<view class="info-header">
|
||||
<text class="eartag-number">{{eartagData.eartagNumber}}</text>
|
||||
<view class="bind-status {{eartagData.isBound ? 'bound' : 'unbound'}}">
|
||||
{{eartagData.isBound ? '已绑定' : '未绑定'}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-details">
|
||||
<view class="detail-item">
|
||||
<text class="label">设备电量</text>
|
||||
<text class="value">{{eartagData.batteryLevel}}%</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">设备温度</text>
|
||||
<text class="value">{{eartagData.temperature}}°C</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">被采集主机</text>
|
||||
<text class="value">{{eartagData.hostNumber}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">总运动量</text>
|
||||
<text class="value">{{eartagData.totalMovement}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">今日运动量</text>
|
||||
<text class="value">{{eartagData.todayMovement}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">佩戴状态</text>
|
||||
<text class="value">{{eartagData.wearStatus}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">设备状态</text>
|
||||
<text class="value">{{eartagData.deviceStatus}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">位置信息</text>
|
||||
<text class="value">{{eartagData.location}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">GPS状态</text>
|
||||
<text class="value">{{eartagData.gpsState}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">纬度</text>
|
||||
<text class="value">{{eartagData.latitude}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">经度</text>
|
||||
<text class="value">{{eartagData.longitude}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">绑定状态</text>
|
||||
<text class="value">{{eartagData.bindingStatus}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">电压值</text>
|
||||
<text class="value">{{eartagData.voltage}}V</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-item">
|
||||
<text class="label">数据更新时间</text>
|
||||
<text class="value">{{eartagData.updateTime}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-buttons">
|
||||
<view class="btn primary" bindtap="onBind" wx:if="{{!eartagData.isBound}}">
|
||||
<text>绑定牛只</text>
|
||||
</view>
|
||||
<view class="btn secondary" bindtap="onUnbind" wx:if="{{eartagData.isBound}}">
|
||||
<text>解绑牛只</text>
|
||||
</view>
|
||||
<view class="btn default" bindtap="onEdit">
|
||||
<text>编辑信息</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading}}" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,183 +1,183 @@
|
||||
/* pages/device/eartag-detail/eartag-detail.wxss */
|
||||
.eartag-detail-container {
|
||||
background: #f8f9fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 顶部导航 */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 32rpx;
|
||||
background: #3cc51f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
/* 耳标信息 */
|
||||
.eartag-info {
|
||||
background: #ffffff;
|
||||
margin: 24rpx 32rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.info-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
padding-bottom: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.eartag-number {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.bind-status {
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bind-status.bound {
|
||||
background: #52c41a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bind-status.unbound {
|
||||
background: #1890ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.info-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-buttons {
|
||||
padding: 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
background: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn.secondary {
|
||||
background: #f5222d;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn.default {
|
||||
background: #ffffff;
|
||||
color: #333;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 80rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.header {
|
||||
padding: 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.eartag-info {
|
||||
margin: 20rpx 24rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
padding: 24rpx;
|
||||
}
|
||||
}
|
||||
/* pages/device/eartag-detail/eartag-detail.wxss */
|
||||
.eartag-detail-container {
|
||||
background: #f8f9fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 顶部导航 */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 32rpx;
|
||||
background: #3cc51f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
/* 耳标信息 */
|
||||
.eartag-info {
|
||||
background: #ffffff;
|
||||
margin: 24rpx 32rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.info-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
padding-bottom: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.eartag-number {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.bind-status {
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bind-status.bound {
|
||||
background: #52c41a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bind-status.unbound {
|
||||
background: #1890ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.info-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-buttons {
|
||||
padding: 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
background: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn.secondary {
|
||||
background: #f5222d;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn.default {
|
||||
background: #ffffff;
|
||||
color: #333;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 80rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.header {
|
||||
padding: 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.eartag-info {
|
||||
margin: 20rpx 24rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
padding: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
// pages/device/eartag/eartag.js - 智能耳标页面
|
||||
const { API } = require('../../../utils/api.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [], // 数据列表
|
||||
page: 1, // 当前页码
|
||||
limit: 10, // 每页数量
|
||||
total: 0, // 总数量
|
||||
totalPages: 0, // 总页数
|
||||
searchValue: '', // 搜索值
|
||||
loading: false, // 加载状态
|
||||
refreshing: false // 刷新状态
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
console.log('智能耳标页面加载')
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
console.log('智能耳标页面显示')
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.setData({ page: 1, refreshing: true })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData(isRefresh = false) {
|
||||
if (this.data.loading) return
|
||||
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: this.data.page,
|
||||
limit: this.data.limit,
|
||||
_t: Date.now(),
|
||||
refresh: isRefresh
|
||||
}
|
||||
|
||||
// 如果有搜索条件,添加到参数中
|
||||
if (this.data.searchValue) {
|
||||
params.eartagNumber = this.data.searchValue.trim() // 按耳标编号精确查询
|
||||
params.cid = this.data.searchValue.trim() // 兼容参数
|
||||
params.number = this.data.searchValue.trim() // 兼容参数
|
||||
}
|
||||
|
||||
console.log('请求参数:', params)
|
||||
|
||||
const res = await API.getEartagList(params)
|
||||
|
||||
console.log('智能耳标数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
let list = res.data?.list || res.data || res.list || []
|
||||
const total = res.data?.total || res.total || 0
|
||||
|
||||
// 如果有搜索条件,进行前端精确过滤(确保精确匹配)
|
||||
if (this.data.searchValue && this.data.searchValue.trim()) {
|
||||
const searchKey = this.data.searchValue.trim()
|
||||
list = list.filter(item => {
|
||||
const eartagNumber = String(item.eartagNumber || item.cid || item.number || '')
|
||||
// 精确匹配:完全相等
|
||||
return eartagNumber === searchKey
|
||||
})
|
||||
|
||||
console.log('精确查找结果:', list.length, '条')
|
||||
|
||||
if (list.length === 0) {
|
||||
wx.showToast({
|
||||
title: '未找到匹配的设备',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const totalPages = Math.ceil((this.data.searchValue ? list.length : total) / this.data.limit)
|
||||
|
||||
this.setData({
|
||||
list: list,
|
||||
total: total,
|
||||
totalPages: totalPages,
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
// 停止下拉刷新
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
this.setData({
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchValue: e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行搜索
|
||||
*/
|
||||
handleSearch() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空搜索
|
||||
*/
|
||||
clearSearch() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
page: 1
|
||||
})
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
*/
|
||||
changePage(e) {
|
||||
const page = e.currentTarget.dataset.page
|
||||
if (page === this.data.page || page < 1 || page > this.data.totalPages) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({ page: page })
|
||||
this.loadData()
|
||||
|
||||
// 滚动到顶部
|
||||
wx.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 300
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
prevPage() {
|
||||
if (this.data.page > 1) {
|
||||
this.setData({ page: this.data.page - 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
nextPage() {
|
||||
if (this.data.page < this.data.totalPages) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
wx.pageScrollTo({ scrollTop: 0, duration: 300 })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
*/
|
||||
viewDetail(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.navigateTo({
|
||||
url: `/pages/device/eartag-detail/eartag-detail?id=${id}`,
|
||||
fail: () => {
|
||||
wx.showToast({
|
||||
title: '详情页面开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "智能耳标",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#f5f5f5",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,146 +1,106 @@
|
||||
<!--pages/device/eartag/eartag.wxml-->
|
||||
<view class="eartag-container">
|
||||
<!-- 顶部搜索和添加区域 -->
|
||||
<view class="header-section">
|
||||
<view class="search-box">
|
||||
<text class="search-icon">🔍</text>
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="搜索"
|
||||
bindinput="onSearchInput"
|
||||
value="{{searchKeyword}}"
|
||||
/>
|
||||
</view>
|
||||
<view class="add-btn" bindtap="onAddEartag">
|
||||
<text class="add-icon">+</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选标签 -->
|
||||
<view class="filter-tabs">
|
||||
<view
|
||||
wx:for="{{filterTabs}}"
|
||||
wx:key="index"
|
||||
class="filter-tab {{item.active ? 'active' : ''}}"
|
||||
bindtap="switchFilter"
|
||||
data-type="{{item.type}}"
|
||||
>
|
||||
{{item.name}}:{{item.count}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 耳标列表 -->
|
||||
<view class="eartag-list">
|
||||
<view
|
||||
wx:for="{{eartagList}}"
|
||||
wx:key="eartagNumber"
|
||||
class="eartag-item"
|
||||
bindtap="onEartagClick"
|
||||
data-item="{{item}}"
|
||||
>
|
||||
<view class="eartag-header">
|
||||
<text class="eartag-number">耳标编号: {{item.eartagNumber}}</text>
|
||||
<view class="bind-status {{item.isBound ? 'bound' : 'unbound'}}">
|
||||
{{item.isBound ? '已绑定' : '未绑定'}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="eartag-details">
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">设备电量/%:</text>
|
||||
<text class="detail-value">{{item.batteryLevel}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">设备温度/°C:</text>
|
||||
<text class="detail-value">{{item.temperature}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">被采集主机:</text>
|
||||
<text class="detail-value">{{item.hostNumber}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">总运动量:</text>
|
||||
<text class="detail-value">{{item.totalMovement}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">今日运动量:</text>
|
||||
<text class="detail-value">{{item.todayMovement}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">佩戴状态:</text>
|
||||
<text class="detail-value">{{item.wearStatus}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">设备状态:</text>
|
||||
<text class="detail-value">{{item.deviceStatus}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">位置信息:</text>
|
||||
<text class="detail-value">{{item.location}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">数据更新时间:</text>
|
||||
<text class="detail-value">{{item.updateTime}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view wx:if="{{eartagList.length === 0 && !loading}}" class="empty-state">
|
||||
<text class="empty-icon">📱</text>
|
||||
<text class="empty-text">暂无耳标数据</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view wx:if="{{loading}}" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<view wx:if="{{totalPages > 1}}" class="pagination-container">
|
||||
<view class="pagination-info">
|
||||
<text>共 {{totalCount}} 条记录,第 {{currentPage}}/{{totalPages}} 页</text>
|
||||
</view>
|
||||
|
||||
<view class="pagination-controls">
|
||||
<!-- 上一页按钮 -->
|
||||
<view
|
||||
class="pagination-btn {{currentPage === 1 ? 'disabled' : ''}}"
|
||||
bindtap="prevPage"
|
||||
>
|
||||
上一页
|
||||
</view>
|
||||
|
||||
<!-- 页码列表 -->
|
||||
<view class="pagination-pages">
|
||||
<view
|
||||
wx:for="{{paginationList}}"
|
||||
wx:key="index"
|
||||
class="pagination-page {{item.active ? 'active' : ''}} {{item.page === -1 ? 'ellipsis' : ''}}"
|
||||
bindtap="goToPage"
|
||||
data-page="{{item.page}}"
|
||||
>
|
||||
{{item.text}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 下一页按钮 -->
|
||||
<view
|
||||
class="pagination-btn {{currentPage === totalPages ? 'disabled' : ''}}"
|
||||
bindtap="nextPage"
|
||||
>
|
||||
下一页
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- pages/device/eartag/eartag.wxml - 智能耳标页面 -->
|
||||
<view class="page-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input-wrapper">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="请输入耳标编号"
|
||||
value="{{searchValue}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="handleSearch"
|
||||
/>
|
||||
<view class="search-clear" wx:if="{{searchValue}}" bindtap="clearSearch">✕</view>
|
||||
</view>
|
||||
<button class="search-btn" bindtap="handleSearch">搜索</button>
|
||||
</view>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<view class="data-stats">
|
||||
<text class="stats-text">共 {{total}} 条数据</text>
|
||||
<text class="stats-text">第 {{page}}/{{totalPages}} 页</text>
|
||||
</view>
|
||||
|
||||
<!-- 数据列表 -->
|
||||
<view class="data-list">
|
||||
<view class="list-item" wx:for="{{list}}" wx:key="id" bindtap="viewDetail" data-id="{{item.id}}">
|
||||
<view class="item-header">
|
||||
<text class="item-number">{{item.eartagNumber || item.cid || item.number}}</text>
|
||||
<text class="item-status {{item.deviceStatus === '在线' ? 'status-online' : 'status-offline'}}">
|
||||
{{item.deviceStatus || '离线'}}
|
||||
</text>
|
||||
</view>
|
||||
<view class="item-content">
|
||||
<view class="item-row">
|
||||
<text class="item-label">绑定状态:</text>
|
||||
<text class="item-value">{{item.bindingStatus || '未绑定'}}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">佩戴状态:</text>
|
||||
<text class="item-value">{{item.wearStatus || (item.is_wear ? '已佩戴' : '未佩戴')}}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">采集主机:</text>
|
||||
<text class="item-value">{{item.collectedHost || item.sid || '-'}}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">电量:</text>
|
||||
<text class="item-value">{{item.battery || item.voltage || 0}}%</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">温度:</text>
|
||||
<text class="item-value">{{item.temperature || '-'}}℃</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">今日运动量:</text>
|
||||
<text class="item-value">{{item.dailyMovement || 0}}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">定位:</text>
|
||||
<text class="item-value">{{item.location || item.gps_state || '-'}}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">更新时间:</text>
|
||||
<text class="item-value">{{item.lastUpdate || item.updateTime || '-'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:if="{{!loading && list.length === 0}}">
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" wx:if="{{loading}}">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页器 -->
|
||||
<view class="pagination" wx:if="{{totalPages > 1}}">
|
||||
<view class="page-btn {{page === 1 ? 'disabled' : ''}}" bindtap="prevPage">
|
||||
<text>上一页</text>
|
||||
</view>
|
||||
|
||||
<view class="page-numbers">
|
||||
<block wx:for="{{totalPages}}" wx:key="index">
|
||||
<view
|
||||
class="page-number {{page === index + 1 ? 'active' : ''}}"
|
||||
wx:if="{{index + 1 === 1 || index + 1 === totalPages || (index + 1 >= page - 2 && index + 1 <= page + 2)}}"
|
||||
bindtap="changePage"
|
||||
data-page="{{index + 1}}"
|
||||
>
|
||||
{{index + 1}}
|
||||
</view>
|
||||
<view class="page-ellipsis" wx:if="{{index + 1 === page - 3 || index + 1 === page + 3}}">
|
||||
...
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<view class="page-btn {{page === totalPages ? 'disabled' : ''}}" bindtap="nextPage">
|
||||
<text>下一页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,285 +1,2 @@
|
||||
/* pages/device/eartag/eartag.wxss */
|
||||
.eartag-container {
|
||||
background: #ffffff;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 顶部搜索和添加区域 */
|
||||
.header-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
background: #3cc51f;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 32rpx;
|
||||
color: #3cc51f;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 筛选标签 */
|
||||
.filter-tabs {
|
||||
display: flex;
|
||||
background: #ffffff;
|
||||
padding: 0 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.filter-tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 24rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
border-bottom: 4rpx solid transparent;
|
||||
}
|
||||
|
||||
.filter-tab.active {
|
||||
color: #3cc51f;
|
||||
border-bottom-color: #3cc51f;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 耳标列表 */
|
||||
.eartag-list {
|
||||
padding: 24rpx 32rpx;
|
||||
}
|
||||
|
||||
.eartag-item {
|
||||
background: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
border: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.eartag-item:active {
|
||||
background: #f8f9fa;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.eartag-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.eartag-number {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.bind-status {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bind-status.bound {
|
||||
background: #52c41a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bind-status.unbound {
|
||||
background: #1890ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.eartag-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 120rpx 32rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
display: block;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
padding: 80rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #3cc51f;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.header-section {
|
||||
padding: 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.eartag-list {
|
||||
padding: 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.eartag-item {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.eartag-number {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.detail-label,
|
||||
.detail-value {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 分页组件样式 */
|
||||
.pagination-container {
|
||||
padding: 32rpx;
|
||||
background: #ffffff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
text-align: center;
|
||||
margin-bottom: 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.pagination-btn {
|
||||
padding: 16rpx 24rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.pagination-btn.disabled {
|
||||
background: #f0f0f0;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.pagination-pages {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.pagination-page {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pagination-page.active {
|
||||
background: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.pagination-page.ellipsis {
|
||||
background: transparent;
|
||||
color: #999;
|
||||
font-weight: bold;
|
||||
}
|
||||
/* pages/device/eartag/eartag.wxss - 智能耳标样式 */
|
||||
@import "../collar/collar.wxss";
|
||||
|
||||
@@ -1,183 +1,183 @@
|
||||
<!--pages/device/fence/fence-new.wxml-->
|
||||
<view class="fence-container">
|
||||
<!-- 顶部标题栏 -->
|
||||
<view class="header">
|
||||
<view class="header-left" bindtap="goBack">
|
||||
<text class="back-icon">‹</text>
|
||||
</view>
|
||||
<view class="header-title">电子围栏</view>
|
||||
<view class="header-right">
|
||||
<text class="menu-icon">⋯</text>
|
||||
<text class="settings-icon">⚙</text>
|
||||
<text class="location-icon">⊙</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 地图容器 -->
|
||||
<view class="map-container">
|
||||
<!-- 显示牧场按钮 -->
|
||||
<view class="show-farm-btn">
|
||||
<text>显示牧场</text>
|
||||
</view>
|
||||
|
||||
<!-- 地图组件 -->
|
||||
<map
|
||||
id="fenceMap"
|
||||
class="fence-map"
|
||||
longitude="{{mapCenter.lng}}"
|
||||
latitude="{{mapCenter.lat}}"
|
||||
scale="{{mapScale}}"
|
||||
markers="{{markers}}"
|
||||
polygons="{{polygons}}"
|
||||
bindtap="onMapTap"
|
||||
bindmarkertap="onMarkerTap"
|
||||
bindpolygontap="onPolygonTap"
|
||||
show-location="{{true}}"
|
||||
enable-3D="{{false}}"
|
||||
enable-overlooking="{{false}}"
|
||||
enable-zoom="{{true}}"
|
||||
enable-scroll="{{true}}"
|
||||
enable-rotate="{{false}}"
|
||||
enable-satellite="{{false}}"
|
||||
enable-traffic="{{false}}"
|
||||
>
|
||||
<!-- 地图控件 -->
|
||||
<cover-view class="map-controls">
|
||||
<!-- 设备统计 -->
|
||||
<cover-view class="device-stats">
|
||||
<cover-view class="stat-item">
|
||||
<cover-view class="stat-label">智能采集器:</cover-view>
|
||||
<cover-view class="stat-value">{{deviceStats.collectors}}</cover-view>
|
||||
</cover-view>
|
||||
<cover-view class="stat-item">
|
||||
<cover-view class="stat-label">智能设备:</cover-view>
|
||||
<cover-view class="stat-value">{{deviceStats.devices}}</cover-view>
|
||||
</cover-view>
|
||||
</cover-view>
|
||||
|
||||
<!-- 切换地图按钮 -->
|
||||
<cover-view class="switch-map-btn" bindtap="switchMapType">
|
||||
<cover-view class="switch-text">切换地图</cover-view>
|
||||
</cover-view>
|
||||
</cover-view>
|
||||
|
||||
<!-- 地图上的位置标记 -->
|
||||
<cover-view class="location-markers">
|
||||
<cover-view
|
||||
wx:for="{{fenceList}}"
|
||||
wx:key="id"
|
||||
class="location-marker"
|
||||
style="left: {{item.position.x}}px; top: {{item.position.y}}px;"
|
||||
>
|
||||
<cover-view class="marker-icon">📍</cover-view>
|
||||
<cover-view class="marker-label">{{item.name}}</cover-view>
|
||||
</cover-view>
|
||||
</cover-view>
|
||||
</map>
|
||||
|
||||
<!-- 底部信息栏 -->
|
||||
<view class="bottom-info">
|
||||
<view class="location-info">
|
||||
<text class="location-name">{{currentLocation.name || '各德'}}</text>
|
||||
</view>
|
||||
<view class="coordinates-info">
|
||||
<text class="coordinates">设备:{{deviceStats.total || '24065000912'}}更多>></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作面板 -->
|
||||
<view class="operation-panel" wx:if="{{showOperationPanel}}">
|
||||
<view class="panel-header">
|
||||
<text class="panel-title">{{isCreatingFence ? '新建围栏' : '围栏详情'}}</text>
|
||||
<text class="panel-close" bindtap="closeOperationPanel">×</text>
|
||||
</view>
|
||||
|
||||
<view class="panel-content">
|
||||
<view wx:if="{{isCreatingFence}}" class="create-fence-form">
|
||||
<view class="form-item">
|
||||
<text class="form-label">围栏名称:</text>
|
||||
<input class="form-input" placeholder="请输入围栏名称" value="{{newFence.name}}" bindinput="onFenceNameInput"/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">围栏类型:</text>
|
||||
<picker class="form-picker" range="{{fenceTypes}}" value="{{newFence.typeIndex}}" bindchange="onFenceTypeChange">
|
||||
<view class="picker-text">{{fenceTypes[newFence.typeIndex] || '请选择类型'}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">描述:</text>
|
||||
<textarea class="form-textarea" placeholder="请输入围栏描述" value="{{newFence.description}}" bindinput="onFenceDescInput"/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">坐标点 ({{newFence.coordinates.length}}):</text>
|
||||
<view class="coordinates-list">
|
||||
<view wx:for="{{newFence.coordinates}}" wx:key="index" class="coordinate-item">
|
||||
<text>点{{index + 1}}: {{item.lng}}, {{item.lat}}</text>
|
||||
<text class="remove-point" bindtap="removeCoordinate" data-index="{{index}}">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-actions">
|
||||
<button class="btn-cancel" bindtap="cancelCreateFence">取消</button>
|
||||
<button class="btn-save" bindtap="saveFence" disabled="{{!canSaveFence}}">保存围栏</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:else class="fence-detail">
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">围栏名称:</text>
|
||||
<text class="detail-value">{{selectedFence.name}}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">围栏类型:</text>
|
||||
<text class="detail-value">{{selectedFence.type}}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">面积:</text>
|
||||
<text class="detail-value">{{selectedFence.area}}平方米</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">放牧状态:</text>
|
||||
<text class="detail-value">{{selectedFence.grazingStatus}}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">围栏内数量:</text>
|
||||
<text class="detail-value">{{selectedFence.insideCount}}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">围栏外数量:</text>
|
||||
<text class="detail-value">{{selectedFence.outsideCount}}</text>
|
||||
</view>
|
||||
<view class="detail-actions">
|
||||
<button class="btn-edit" bindtap="editFence">编辑</button>
|
||||
<button class="btn-delete" bindtap="deleteFence">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 浮动操作按钮 -->
|
||||
<view class="fab-container">
|
||||
<view class="fab-main" bindtap="toggleFab">
|
||||
<text class="fab-icon">{{fabExpanded ? '×' : '+'}}</text>
|
||||
</view>
|
||||
<view class="fab-menu" wx:if="{{fabExpanded}}">
|
||||
<view class="fab-item" bindtap="startCreateFence">
|
||||
<text class="fab-item-icon">📍</text>
|
||||
<text class="fab-item-text">新建围栏</text>
|
||||
</view>
|
||||
<view class="fab-item" bindtap="refreshFences">
|
||||
<text class="fab-item-icon">🔄</text>
|
||||
<text class="fab-item-text">刷新数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载提示 -->
|
||||
<view class="loading-overlay" wx:if="{{loading}}">
|
||||
<view class="loading-content">
|
||||
<text class="loading-text">{{loadingText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!--pages/device/fence/fence-new.wxml-->
|
||||
<view class="fence-container">
|
||||
<!-- 顶部标题栏 -->
|
||||
<view class="header">
|
||||
<view class="header-left" bindtap="goBack">
|
||||
<text class="back-icon">‹</text>
|
||||
</view>
|
||||
<view class="header-title">电子围栏</view>
|
||||
<view class="header-right">
|
||||
<text class="menu-icon">⋯</text>
|
||||
<text class="settings-icon">⚙</text>
|
||||
<text class="location-icon">⊙</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 地图容器 -->
|
||||
<view class="map-container">
|
||||
<!-- 显示牧场按钮 -->
|
||||
<view class="show-farm-btn">
|
||||
<text>显示牧场</text>
|
||||
</view>
|
||||
|
||||
<!-- 地图组件 -->
|
||||
<map
|
||||
id="fenceMap"
|
||||
class="fence-map"
|
||||
longitude="{{mapCenter.lng}}"
|
||||
latitude="{{mapCenter.lat}}"
|
||||
scale="{{mapScale}}"
|
||||
markers="{{markers}}"
|
||||
polygons="{{polygons}}"
|
||||
bindtap="onMapTap"
|
||||
bindmarkertap="onMarkerTap"
|
||||
bindpolygontap="onPolygonTap"
|
||||
show-location="{{true}}"
|
||||
enable-3D="{{false}}"
|
||||
enable-overlooking="{{false}}"
|
||||
enable-zoom="{{true}}"
|
||||
enable-scroll="{{true}}"
|
||||
enable-rotate="{{false}}"
|
||||
enable-satellite="{{false}}"
|
||||
enable-traffic="{{false}}"
|
||||
>
|
||||
<!-- 地图控件 -->
|
||||
<cover-view class="map-controls">
|
||||
<!-- 设备统计 -->
|
||||
<cover-view class="device-stats">
|
||||
<cover-view class="stat-item">
|
||||
<cover-view class="stat-label">智能采集器:</cover-view>
|
||||
<cover-view class="stat-value">{{deviceStats.collectors}}</cover-view>
|
||||
</cover-view>
|
||||
<cover-view class="stat-item">
|
||||
<cover-view class="stat-label">智能设备:</cover-view>
|
||||
<cover-view class="stat-value">{{deviceStats.devices}}</cover-view>
|
||||
</cover-view>
|
||||
</cover-view>
|
||||
|
||||
<!-- 切换地图按钮 -->
|
||||
<cover-view class="switch-map-btn" bindtap="switchMapType">
|
||||
<cover-view class="switch-text">切换地图</cover-view>
|
||||
</cover-view>
|
||||
</cover-view>
|
||||
|
||||
<!-- 地图上的位置标记 -->
|
||||
<cover-view class="location-markers">
|
||||
<cover-view
|
||||
wx:for="{{fenceList}}"
|
||||
wx:key="id"
|
||||
class="location-marker"
|
||||
style="left: {{item.position.x}}px; top: {{item.position.y}}px;"
|
||||
>
|
||||
<cover-view class="marker-icon">📍</cover-view>
|
||||
<cover-view class="marker-label">{{item.name}}</cover-view>
|
||||
</cover-view>
|
||||
</cover-view>
|
||||
</map>
|
||||
|
||||
<!-- 底部信息栏 -->
|
||||
<view class="bottom-info">
|
||||
<view class="location-info">
|
||||
<text class="location-name">{{currentLocation.name || '各德'}}</text>
|
||||
</view>
|
||||
<view class="coordinates-info">
|
||||
<text class="coordinates">设备:{{deviceStats.total || '24065000912'}}更多>></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作面板 -->
|
||||
<view class="operation-panel" wx:if="{{showOperationPanel}}">
|
||||
<view class="panel-header">
|
||||
<text class="panel-title">{{isCreatingFence ? '新建围栏' : '围栏详情'}}</text>
|
||||
<text class="panel-close" bindtap="closeOperationPanel">×</text>
|
||||
</view>
|
||||
|
||||
<view class="panel-content">
|
||||
<view wx:if="{{isCreatingFence}}" class="create-fence-form">
|
||||
<view class="form-item">
|
||||
<text class="form-label">围栏名称:</text>
|
||||
<input class="form-input" placeholder="请输入围栏名称" value="{{newFence.name}}" bindinput="onFenceNameInput"/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">围栏类型:</text>
|
||||
<picker class="form-picker" range="{{fenceTypes}}" value="{{newFence.typeIndex}}" bindchange="onFenceTypeChange">
|
||||
<view class="picker-text">{{fenceTypes[newFence.typeIndex] || '请选择类型'}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">描述:</text>
|
||||
<textarea class="form-textarea" placeholder="请输入围栏描述" value="{{newFence.description}}" bindinput="onFenceDescInput"/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">坐标点 ({{newFence.coordinates.length}}):</text>
|
||||
<view class="coordinates-list">
|
||||
<view wx:for="{{newFence.coordinates}}" wx:key="index" class="coordinate-item">
|
||||
<text>点{{index + 1}}: {{item.lng}}, {{item.lat}}</text>
|
||||
<text class="remove-point" bindtap="removeCoordinate" data-index="{{index}}">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-actions">
|
||||
<button class="btn-cancel" bindtap="cancelCreateFence">取消</button>
|
||||
<button class="btn-save" bindtap="saveFence" disabled="{{!canSaveFence}}">保存围栏</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:else class="fence-detail">
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">围栏名称:</text>
|
||||
<text class="detail-value">{{selectedFence.name}}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">围栏类型:</text>
|
||||
<text class="detail-value">{{selectedFence.type}}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">面积:</text>
|
||||
<text class="detail-value">{{selectedFence.area}}平方米</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">放牧状态:</text>
|
||||
<text class="detail-value">{{selectedFence.grazingStatus}}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">围栏内数量:</text>
|
||||
<text class="detail-value">{{selectedFence.insideCount}}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">围栏外数量:</text>
|
||||
<text class="detail-value">{{selectedFence.outsideCount}}</text>
|
||||
</view>
|
||||
<view class="detail-actions">
|
||||
<button class="btn-edit" bindtap="editFence">编辑</button>
|
||||
<button class="btn-delete" bindtap="deleteFence">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 浮动操作按钮 -->
|
||||
<view class="fab-container">
|
||||
<view class="fab-main" bindtap="toggleFab">
|
||||
<text class="fab-icon">{{fabExpanded ? '×' : '+'}}</text>
|
||||
</view>
|
||||
<view class="fab-menu" wx:if="{{fabExpanded}}">
|
||||
<view class="fab-item" bindtap="startCreateFence">
|
||||
<text class="fab-item-icon">📍</text>
|
||||
<text class="fab-item-text">新建围栏</text>
|
||||
</view>
|
||||
<view class="fab-item" bindtap="refreshFences">
|
||||
<text class="fab-item-icon">🔄</text>
|
||||
<text class="fab-item-text">刷新数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载提示 -->
|
||||
<view class="loading-overlay" wx:if="{{loading}}">
|
||||
<view class="loading-content">
|
||||
<text class="loading-text">{{loadingText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -1,473 +1,473 @@
|
||||
/* pages/device/fence/fence-new.wxss */
|
||||
|
||||
.fence-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部标题栏 */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(135deg, #7CB342, #8BC34A);
|
||||
color: white;
|
||||
padding: 0 32rpx;
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.menu-icon, .settings-icon, .location-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
/* 地图容器 */
|
||||
.map-container {
|
||||
width: 100%;
|
||||
height: calc(100vh - 88rpx);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.show-farm-btn {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
left: 20rpx;
|
||||
background: #7CB342;
|
||||
color: white;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 28rpx;
|
||||
z-index: 10;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.fence-map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 地图控件 */
|
||||
.map-controls {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
left: 20rpx;
|
||||
right: 20rpx;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.device-stats {
|
||||
position: absolute;
|
||||
top: 80rpx;
|
||||
left: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 16rpx 24rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stat-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.switch-map-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: #7CB342;
|
||||
color: white;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 28rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 位置标记 */
|
||||
.location-markers {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.location-marker {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.marker-icon {
|
||||
font-size: 32rpx;
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.marker-label {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
/* 底部信息栏 */
|
||||
.bottom-info {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 24rpx 32rpx;
|
||||
border-top: 1rpx solid #e8e8e8;
|
||||
}
|
||||
|
||||
.location-info {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.location-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.coordinates-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.coordinates {
|
||||
font-size: 24rpx;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
/* 操作面板 */
|
||||
.operation-panel {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
z-index: 200;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.panel-close {
|
||||
font-size: 48rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
/* 创建围栏表单 */
|
||||
.create-fence-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-input, .form-textarea {
|
||||
padding: 24rpx;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 120rpx;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.form-picker {
|
||||
padding: 24rpx;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
border-radius: 12rpx;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.coordinates-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
max-height: 200rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.coordinate-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.remove-point {
|
||||
color: #f5222d;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.btn-cancel, .btn-save {
|
||||
flex: 1;
|
||||
padding: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background: #7CB342;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-save:disabled {
|
||||
background: #d9d9d9;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 围栏详情 */
|
||||
.fence-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.btn-edit, .btn-delete {
|
||||
flex: 1;
|
||||
padding: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background: #1890ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background: #f5222d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 浮动操作按钮 */
|
||||
.fab-container {
|
||||
position: fixed;
|
||||
bottom: 120rpx;
|
||||
right: 32rpx;
|
||||
z-index: 150;
|
||||
}
|
||||
|
||||
.fab-main {
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
background: #7CB342;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(124, 179, 66, 0.4);
|
||||
}
|
||||
|
||||
.fab-icon {
|
||||
font-size: 48rpx;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fab-menu {
|
||||
position: absolute;
|
||||
bottom: 140rpx;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.fab-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
background: white;
|
||||
padding: 16rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fab-item-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.fab-item-text {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 加载提示 */
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 300;
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
background: white;
|
||||
padding: 48rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 750rpx) {
|
||||
.header {
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
height: calc(100vh - 80rpx);
|
||||
}
|
||||
|
||||
.fab-container {
|
||||
bottom: 100rpx;
|
||||
right: 24rpx;
|
||||
}
|
||||
/* pages/device/fence/fence-new.wxss */
|
||||
|
||||
.fence-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部标题栏 */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(135deg, #7CB342, #8BC34A);
|
||||
color: white;
|
||||
padding: 0 32rpx;
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.menu-icon, .settings-icon, .location-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
/* 地图容器 */
|
||||
.map-container {
|
||||
width: 100%;
|
||||
height: calc(100vh - 88rpx);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.show-farm-btn {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
left: 20rpx;
|
||||
background: #7CB342;
|
||||
color: white;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 28rpx;
|
||||
z-index: 10;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.fence-map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 地图控件 */
|
||||
.map-controls {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
left: 20rpx;
|
||||
right: 20rpx;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.device-stats {
|
||||
position: absolute;
|
||||
top: 80rpx;
|
||||
left: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 16rpx 24rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stat-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.switch-map-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: #7CB342;
|
||||
color: white;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 28rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 位置标记 */
|
||||
.location-markers {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.location-marker {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.marker-icon {
|
||||
font-size: 32rpx;
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.marker-label {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
/* 底部信息栏 */
|
||||
.bottom-info {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 24rpx 32rpx;
|
||||
border-top: 1rpx solid #e8e8e8;
|
||||
}
|
||||
|
||||
.location-info {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.location-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.coordinates-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.coordinates {
|
||||
font-size: 24rpx;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
/* 操作面板 */
|
||||
.operation-panel {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
z-index: 200;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.panel-close {
|
||||
font-size: 48rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
/* 创建围栏表单 */
|
||||
.create-fence-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-input, .form-textarea {
|
||||
padding: 24rpx;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 120rpx;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.form-picker {
|
||||
padding: 24rpx;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
border-radius: 12rpx;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.coordinates-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
max-height: 200rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.coordinate-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.remove-point {
|
||||
color: #f5222d;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.btn-cancel, .btn-save {
|
||||
flex: 1;
|
||||
padding: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background: #7CB342;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-save:disabled {
|
||||
background: #d9d9d9;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 围栏详情 */
|
||||
.fence-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.btn-edit, .btn-delete {
|
||||
flex: 1;
|
||||
padding: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background: #1890ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background: #f5222d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 浮动操作按钮 */
|
||||
.fab-container {
|
||||
position: fixed;
|
||||
bottom: 120rpx;
|
||||
right: 32rpx;
|
||||
z-index: 150;
|
||||
}
|
||||
|
||||
.fab-main {
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
background: #7CB342;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(124, 179, 66, 0.4);
|
||||
}
|
||||
|
||||
.fab-icon {
|
||||
font-size: 48rpx;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fab-menu {
|
||||
position: absolute;
|
||||
bottom: 140rpx;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.fab-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
background: white;
|
||||
padding: 16rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fab-item-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.fab-item-text {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 加载提示 */
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 300;
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
background: white;
|
||||
padding: 48rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 750rpx) {
|
||||
.header {
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
height: calc(100vh - 80rpx);
|
||||
}
|
||||
|
||||
.fab-container {
|
||||
bottom: 100rpx;
|
||||
right: 24rpx;
|
||||
}
|
||||
}
|
||||
400
mini_program/farm-monitor-dashboard/pages/device/fence/fence.js
Normal file
400
mini_program/farm-monitor-dashboard/pages/device/fence/fence.js
Normal file
@@ -0,0 +1,400 @@
|
||||
// pages/device/fence/fence.js
|
||||
const API = require('../../../utils/api').API
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 0,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
searchValue: '',
|
||||
|
||||
// 地图相关
|
||||
showMap: false, // 是否显示地图视图
|
||||
selectedFence: null, // 当前选中的围栏
|
||||
mapCenter: {
|
||||
latitude: 38.5248248,
|
||||
longitude: 106.2267664
|
||||
},
|
||||
mapMarkers: [], // 地图标记点
|
||||
mapPolygons: [], // 地图多边形
|
||||
mapScale: 12
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
// 可选:每次显示页面时刷新数据
|
||||
// this.loadData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData(isRefresh = false) {
|
||||
if (isRefresh) {
|
||||
this.setData({ refreshing: true })
|
||||
}
|
||||
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
const params = {
|
||||
page: this.data.page,
|
||||
limit: this.data.limit,
|
||||
_t: Date.now(),
|
||||
refresh: isRefresh
|
||||
}
|
||||
|
||||
// 如果有搜索条件,添加到参数中
|
||||
if (this.data.searchValue) {
|
||||
params.name = this.data.searchValue.trim() // 按围栏名称精确查询
|
||||
params.number = this.data.searchValue.trim() // 兼容参数
|
||||
}
|
||||
|
||||
console.log('请求参数:', params)
|
||||
|
||||
const res = await API.getFenceList(params)
|
||||
|
||||
console.log('电子围栏数据:', res)
|
||||
|
||||
// 根据实际返回的数据结构调整
|
||||
let list = res.data?.list || res.data || res.list || []
|
||||
const total = res.data?.total || res.total || list.length || 0
|
||||
|
||||
// 数据预处理:确保所有字段都存在
|
||||
list = list.map(item => {
|
||||
return {
|
||||
...item,
|
||||
// 确保必要字段存在
|
||||
id: item.id,
|
||||
name: item.name || '未命名围栏',
|
||||
type: item.type || '围栏',
|
||||
description: item.description || '',
|
||||
isActive: item.isActive !== undefined ? item.isActive : true,
|
||||
grazingStatus: item.grazingStatus || '未知',
|
||||
area: item.area || '0',
|
||||
center: item.center || { lng: '106.2267664', lat: '38.5248248' },
|
||||
coordinates: item.coordinates || [],
|
||||
insideCount: item.insideCount || 0,
|
||||
outsideCount: item.outsideCount || 0,
|
||||
createdAt: item.createdAt || item.created_at || '',
|
||||
updatedAt: item.updatedAt || item.updated_at || ''
|
||||
}
|
||||
})
|
||||
|
||||
// 如果有搜索条件,进行前端精确过滤(确保精确匹配)
|
||||
if (this.data.searchValue && this.data.searchValue.trim()) {
|
||||
const searchKey = this.data.searchValue.trim()
|
||||
list = list.filter(item => {
|
||||
const name = String(item.name || '')
|
||||
// 精确匹配:完全相等
|
||||
return name === searchKey
|
||||
})
|
||||
|
||||
console.log('精确查找结果:', list.length, '条')
|
||||
|
||||
if (list.length === 0) {
|
||||
wx.showToast({
|
||||
title: '未找到匹配的围栏',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const totalPages = Math.ceil((this.data.searchValue ? list.length : total) / this.data.limit)
|
||||
|
||||
this.setData({
|
||||
list: list,
|
||||
total: total,
|
||||
totalPages: totalPages,
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
// 停止下拉刷新
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
|
||||
this.setData({
|
||||
loading: false,
|
||||
refreshing: false
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
if (isRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
onSearchInput(e) {
|
||||
this.setData({
|
||||
searchValue: e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行搜索
|
||||
*/
|
||||
handleSearch() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空搜索
|
||||
*/
|
||||
clearSearch() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
page: 1
|
||||
})
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
*/
|
||||
changePage(e) {
|
||||
const page = e.currentTarget.dataset.page
|
||||
if (page !== this.data.page && page >= 1 && page <= this.data.totalPages) {
|
||||
this.setData({ page })
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
prevPage() {
|
||||
if (this.data.page > 1) {
|
||||
this.setData({ page: this.data.page - 1 })
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
nextPage() {
|
||||
if (this.data.page < this.data.totalPages) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 查看围栏详情(在地图上显示)
|
||||
*/
|
||||
viewDetail(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
const index = e.currentTarget.dataset.index
|
||||
console.log('查看围栏详情:', id)
|
||||
|
||||
const fence = this.data.list[index]
|
||||
if (!fence) {
|
||||
wx.showToast({
|
||||
title: '围栏数据不存在',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 切换到地图视图并显示该围栏
|
||||
this.showFenceOnMap(fence)
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换视图:列表/地图
|
||||
*/
|
||||
toggleView() {
|
||||
const showMap = !this.data.showMap
|
||||
this.setData({ showMap })
|
||||
|
||||
// 如果切换到地图视图,加载所有围栏到地图
|
||||
if (showMap && this.data.list.length > 0) {
|
||||
this.loadAllFencesToMap()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 在地图上显示单个围栏
|
||||
*/
|
||||
showFenceOnMap(fence) {
|
||||
if (!fence || !fence.coordinates || fence.coordinates.length === 0) {
|
||||
wx.showToast({
|
||||
title: '围栏坐标数据不完整',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 转换坐标格式
|
||||
const points = fence.coordinates.map(coord => ({
|
||||
latitude: parseFloat(coord.lat),
|
||||
longitude: parseFloat(coord.lng)
|
||||
}))
|
||||
|
||||
// 创建多边形
|
||||
const polygon = {
|
||||
points: points,
|
||||
strokeWidth: 3,
|
||||
strokeColor: fence.isActive ? '#52c41a' : '#ff4d4f',
|
||||
fillColor: fence.isActive ? '#52c41a33' : '#ff4d4f33'
|
||||
}
|
||||
|
||||
// 创建中心点标记
|
||||
const marker = {
|
||||
id: fence.id,
|
||||
latitude: parseFloat(fence.center.lat),
|
||||
longitude: parseFloat(fence.center.lng),
|
||||
title: fence.name,
|
||||
iconPath: '/images/marker.png',
|
||||
width: 30,
|
||||
height: 30,
|
||||
callout: {
|
||||
content: `${fence.name}\n${fence.type}\n${fence.grazingStatus}`,
|
||||
fontSize: 12,
|
||||
borderRadius: 5,
|
||||
padding: 8,
|
||||
display: 'ALWAYS'
|
||||
}
|
||||
}
|
||||
|
||||
this.setData({
|
||||
showMap: true,
|
||||
selectedFence: fence,
|
||||
mapCenter: {
|
||||
latitude: parseFloat(fence.center.lat),
|
||||
longitude: parseFloat(fence.center.lng)
|
||||
},
|
||||
mapPolygons: [polygon],
|
||||
mapMarkers: [marker],
|
||||
mapScale: 14
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载所有围栏到地图
|
||||
*/
|
||||
loadAllFencesToMap() {
|
||||
const polygons = []
|
||||
const markers = []
|
||||
|
||||
this.data.list.forEach(fence => {
|
||||
if (fence.coordinates && fence.coordinates.length > 0) {
|
||||
// 添加多边形
|
||||
const points = fence.coordinates.map(coord => ({
|
||||
latitude: parseFloat(coord.lat),
|
||||
longitude: parseFloat(coord.lng)
|
||||
}))
|
||||
|
||||
polygons.push({
|
||||
points: points,
|
||||
strokeWidth: 2,
|
||||
strokeColor: fence.isActive ? '#52c41a' : '#ff4d4f',
|
||||
fillColor: fence.isActive ? '#52c41a22' : '#ff4d4f22'
|
||||
})
|
||||
|
||||
// 添加中心点标记
|
||||
markers.push({
|
||||
id: fence.id,
|
||||
latitude: parseFloat(fence.center.lat),
|
||||
longitude: parseFloat(fence.center.lng),
|
||||
title: fence.name,
|
||||
iconPath: '/images/marker.png',
|
||||
width: 25,
|
||||
height: 25
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 计算所有围栏的中心点
|
||||
let centerLat = 38.5248248
|
||||
let centerLng = 106.2267664
|
||||
|
||||
if (markers.length > 0) {
|
||||
centerLat = markers.reduce((sum, m) => sum + m.latitude, 0) / markers.length
|
||||
centerLng = markers.reduce((sum, m) => sum + m.longitude, 0) / markers.length
|
||||
}
|
||||
|
||||
this.setData({
|
||||
mapPolygons: polygons,
|
||||
mapMarkers: markers,
|
||||
mapCenter: {
|
||||
latitude: centerLat,
|
||||
longitude: centerLng
|
||||
},
|
||||
mapScale: 10
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 地图标记点击事件
|
||||
*/
|
||||
onMarkerTap(e) {
|
||||
const markerId = e.detail.markerId || e.markerId
|
||||
console.log('点击标记:', markerId)
|
||||
|
||||
const fence = this.data.list.find(f => f.id === markerId)
|
||||
if (fence) {
|
||||
this.setData({ selectedFence: fence })
|
||||
// 可以显示围栏详细信息
|
||||
wx.showModal({
|
||||
title: fence.name,
|
||||
content: `类型:${fence.type}\n状态:${fence.grazingStatus}\n面积:${fence.area}平方米\n内部数量:${fence.insideCount}\n外部数量:${fence.outsideCount}`,
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 地图区域变化事件
|
||||
*/
|
||||
onRegionChange(e) {
|
||||
console.log('地图区域变化:', e)
|
||||
},
|
||||
|
||||
/**
|
||||
* 下拉刷新
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadData(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 上拉加载更多
|
||||
*/
|
||||
onReachBottom() {
|
||||
if (this.data.page < this.data.totalPages && !this.data.loading) {
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user