Compare commits

..

19 Commits

Author SHA1 Message Date
xce 997cd9426b 去掉前端关自动停逻辑 2026-01-17 01:49:10 +08:00
xce d01b66256b 微信小程序适配 2026-01-15 16:56:54 +08:00
xce 2c224682d2 生产配置修改 2026-01-15 04:26:05 +08:00
xce 0a2bf1ff9a 生产配置修改 2026-01-15 02:50:38 +08:00
xce 63d0355c27 需求清单---增删改查 2026-01-14 00:51:04 +08:00
xce e6dd93498f 需求清单---增删改查 2026-01-14 00:09:03 +08:00
xce 991fdc11e7 需求清单 查询页 2026-01-13 20:41:21 +08:00
xce b275f706c3 表单提交 2026-01-12 00:51:27 +08:00
xce 6a1f44f6ce 特殊棚添加 2026-01-11 22:38:37 +08:00
xce 9eaf3e7e71 特殊棚添加 2026-01-11 22:29:38 +08:00
xce 7f493da241 特殊棚添加 2026-01-11 22:18:08 +08:00
xce 4108e58459 修改日志+完善发布指令环境 2026-01-10 09:12:33 +08:00
xce 584ada5811 测试定时停 2026-01-10 08:28:55 +08:00
xce 347082bb30 自动暂停+设备暂停运行不弹窗 2026-01-10 07:51:06 +08:00
xce 610814d178 增加限位时间+ 大棚相关api接入+登录密码展示 2026-01-10 06:48:40 +08:00
xce e720c37b68 加七方南棚 2026-01-10 00:47:42 +08:00
xce 51fb80c782 完善运行参数 2026-01-09 15:38:52 +08:00
xce 3105c590bb 暂提-----运行时间 2026-01-09 14:34:29 +08:00
xce 07ab68aba5 基础修改 2026-01-08 20:47:00 +08:00
26 changed files with 2494 additions and 290 deletions

View File

@ -2,6 +2,7 @@
import config from './config' import config from './config'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import mqttUtil from '@/utils/mqtt' import mqttUtil from '@/utils/mqtt'
import {startMqttOnlinePing, stopMqttOnlinePing} from "./utils/mqtt";
export default { export default {
globalData: { globalData: {
@ -55,6 +56,9 @@ export default {
console.info("token存在mqtt重新连接中。。") console.info("token存在mqtt重新连接中。。")
this.reconnectMqtt() this.reconnectMqtt()
} }
// mqtt client connected start
startMqttOnlinePing( 20000);
} }
}, },
onHide() { onHide() {
@ -65,6 +69,7 @@ export default {
// ========== localStorage ========== // ========== localStorage ==========
uni.setStorageSync('mqtt_subscribe_list', this.globalData.mqtt.subscribeList) uni.setStorageSync('mqtt_subscribe_list', this.globalData.mqtt.subscribeList)
mqttUtil.disconnectMqtt() mqttUtil.disconnectMqtt()
stopMqttOnlinePing();
} }
}, },
methods: { methods: {

52
api/data/specialData.js Normal file
View File

@ -0,0 +1,52 @@
import request from '@/utils/request'
// 查询无线传输线管数据列表
export function listSpecialData(query) {
return request({
url: '/data/specialData/list',
method: 'get',
params: query
})
}
// 查询无线传输线管数据详细
export function getSpecialData(id) {
return request({
url: '/data/specialData/' + id,
method: 'get'
})
}
// 新增无线传输线管数据
export function addSpecialData(data) {
return request({
url: '/data/specialData',
method: 'post',
data: data
})
}
// 修改无线传输线管数据
export function updateSpecialData(data) {
return request({
url: '/data/specialData',
method: 'put',
data: data
})
}
// 删除无线传输线管数据
export function delSpecialData(id) {
return request({
url: '/data/specialData/' + id,
method: 'delete'
})
}
// 取最新一条数据
export function getNewSpecialData() {
return request({
url: '/data/specialData/getNewSpecialData',
method: 'get'
})
}

44
api/system/assets/agri.js Normal file
View File

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询大棚管理列表
export function listAgri(query) {
return request({
url: '/assets/agri/list',
method: 'get',
params: query
})
}
// 查询大棚管理详细
export function getAgri(id) {
return request({
url: '/assets/agri/' + id,
method: 'get'
})
}
// 新增大棚管理
export function addAgri(data) {
return request({
url: '/assets/agri',
method: 'post',
data: data
})
}
// 修改大棚管理
export function updateAgri(data) {
return request({
url: '/assets/agri',
method: 'put',
data: data
})
}
// 删除大棚管理
export function delAgri(id) {
return request({
url: '/assets/agri/' + id,
method: 'delete'
})
}

View File

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询告警方式设置列表
export function listAlarm(query) {
return request({
url: '/assets/alarm/list',
method: 'get',
params: query
})
}
// 查询告警方式设置详细
export function getAlarm(id) {
return request({
url: '/assets/alarm/' + id,
method: 'get'
})
}
// 新增告警方式设置
export function addAlarm(data) {
return request({
url: '/assets/alarm',
method: 'post',
data: data
})
}
// 修改告警方式设置
export function updateAlarm(data) {
return request({
url: '/assets/alarm',
method: 'put',
data: data
})
}
// 删除告警方式设置
export function delAlarm(id) {
return request({
url: '/assets/alarm/' + id,
method: 'delete'
})
}

View File

@ -0,0 +1,52 @@
import request from '@/utils/request'
// 查询执行时间限位管理列表
export function listLimit(query) {
return request({
url: '/assets/limit/list',
method: 'get',
params: query
})
}
// 查询执行时间限位管理详细
export function getLimit(id) {
return request({
url: '/assets/limit/' + id,
method: 'get'
})
}
// 新增执行时间限位管理
export function addLimit(data) {
return request({
url: '/assets/limit',
method: 'post',
data: data
})
}
// 修改执行时间限位管理
export function updateLimit(data) {
return request({
url: '/assets/limit',
method: 'put',
data: data
})
}
// 删除执行时间限位管理
export function delLimit(id) {
return request({
url: '/assets/limit/' + id,
method: 'delete'
})
}
// 大棚运行时间限位查询
export function getAgriByImei(id) {
return request({
url: '/assets/limit/getAgriByImei/' + id,
method: 'get'
})
}

51
api/system/require.js Normal file
View File

@ -0,0 +1,51 @@
import request from '@/utils/request'
// 查询需求清单列表
export function listRequire(query) {
return request({
url: '/system/require/list',
method: 'get',
params: query
})
}
// 查询需求清单详细
export function getRequire(id) {
return request({
url: '/system/require/' + id,
method: 'get'
})
}
// 新增需求清单
export function addRequire(data) {
return request({
url: '/system/require',
method: 'post',
data: data
})
}
// 修改需求清单
export function updateRequire(data) {
return request({
url: '/system/require',
method: 'put',
data: data
})
}
// 删除需求清单
export function delRequire(id) {
return request({
url: '/system/require/' + id,
method: 'delete'
})
}
export function delRequires(ids) {
return request({
url: '/system/require/remove',
method: 'delete',
data: ids
})
}

View File

@ -1,6 +1,7 @@
// 应用全局配置 // 应用全局配置
module.exports = { module.exports = {
baseUrl: process.env.UNI_PLATFORM === 'mp-weixin'?"http://122.51.109.52:8088":'http://localhost:8088', // baseUrl: process.env.UNI_PLATFORM === 'mp-weixin'?"http://122.51.109.52:8088":(process.env.NODE_ENV === "production" ? "/api" : "http://localhost:8088"),
baseUrl: process.env.NODE_ENV === "production"?"https://api.xiaoces.com/api":"http://localhost:8088",
// 应用信息 // 应用信息
appInfo: { appInfo: {
// 应用名称 // 应用名称

View File

@ -13,7 +13,6 @@ Vue.use(plugins)
Vue.config.productionTip = false Vue.config.productionTip = false
Vue.prototype.$store = store Vue.prototype.$store = store
Vue.prototype.getDicts = getDicts Vue.prototype.getDicts = getDicts
App.mpType = 'app' App.mpType = 'app'
const app = new Vue({ const app = new Vue({

View File

@ -69,7 +69,7 @@
"title" : "Agri-App", "title" : "Agri-App",
"router" : { "router" : {
"mode" : "hash", "mode" : "hash",
"base" : "/m/" "base" : "/"
}, },
"optimization" : { "optimization" : {
"minimize" : true, "minimize" : true,

View File

@ -1,5 +1,5 @@
{ {
"dependencies": { "dependencies": {
"mqtt": "^3.0.0" "mqtt": "^2.18.8"
} }
} }

View File

@ -25,11 +25,6 @@
"style": { "style": {
"navigationBarTitleText": "我的" "navigationBarTitleText": "我的"
} }
}, {
"path": "pages/demo/index",
"style": {
"navigationBarTitleText": "示例"
}
}, { }, {
"path": "pages/control/index", "path": "pages/control/index",
"style": { "style": {
@ -65,6 +60,16 @@
"style": { "style": {
"navigationBarTitleText": "常见问题" "navigationBarTitleText": "常见问题"
} }
}, {
"path": "pages/mine/mqtt/index",
"style": {
"navigationBarTitleText": "mqtt工具"
}
}, {
"path": "pages/mine/require/index",
"style": {
"navigationBarTitleText": "需求清单"
}
}, { }, {
"path": "pages/mine/about/index", "path": "pages/mine/about/index",
"style": { "style": {
@ -100,11 +105,6 @@
"text": "工作台" "text": "工作台"
}, },
{ {
"pagePath": "pages/demo/index",
"iconPath": "static/images/tabbar/work.png",
"selectedIconPath": "static/images/tabbar/work_.png",
"text": "示例"
},{
"pagePath": "pages/mine/index", "pagePath": "pages/mine/index",
"iconPath": "static/images/tabbar/mine.png", "iconPath": "static/images/tabbar/mine.png",
"selectedIconPath": "static/images/tabbar/mine_.png", "selectedIconPath": "static/images/tabbar/mine_.png",

View File

@ -2,221 +2,160 @@
<view class="container"> <view class="container">
<!-- 控制设置标题 --> <!-- 控制设置标题 -->
<view class="control-title">控制设置</view> <view class="control-title">控制设置</view>
<uni-section title="请选择大棚:" titleFontSize="18px" type="line"> <uni-section title="请选择大棚:" :subTitle="imei" titleFontSize="18px" type="line">
<view class="uni-px-5 uni-pb-5"> <view class="uni-px-5 uni-pb-5">
<uni-data-select v-model="value" :localdata="range" @change="change"></uni-data-select> <uni-data-select v-model="value" :localdata="range" @change="change"></uni-data-select>
</view> </view>
</uni-section> </uni-section>
<uni-section title="实时温湿度" titleFontSize="16px" type="line" v-if="value!== 1"> <uni-section title="实时温湿度" titleFontSize="16px" type="line" v-if="value!== 1">
<template v-slot:right > <template v-slot:right >
{{ temp }} {{ temp }}
</template> </template>
<view> <view>
<!-- 优化温度卡片循环渲染 -->
<view class="uni-flex_control uni-row" > <view class="uni-flex_control uni-row" >
<view class="text uni-flex_control_one uni-view"> <view
<text class="data" :style="fontStyle">{{ liveData.temp1 }}<p v-if=(testNumber(liveData.temp1)) class="tempStyle"></p></text> class="text uni-flex_control_one uni-view"
<text class="data">温度1</text> v-for="item in sensorCards.temp"
</view> :key="item.key"
<view class="text uni-flex_control_one uni-view"> >
<text class="data" :style="fontStyle">{{ liveData.temp2 }}<p v-if=(testNumber(liveData.temp2)) class="tempStyle"></p></text> <text class="data" :style="fontStyle">
<text class="data">温度2</text> {{ liveData[item.key] }}
</view> <text v-if="isEffectiveValue(liveData[item.key])" class="tempStyle"></text>
<view class="text uni-flex_control_one uni-view"> </text>
<text class="data" :style="fontStyle">{{ liveData.temp3 }}<p v-if=(testNumber(liveData.temp3)) class="tempStyle"></p></text> <text class="data">{{ item.label }}</text>
<text class="data">温度3</text>
</view>
<view class="text uni-flex_control_one uni-view">
<text class="data" :style="fontStyle">{{ liveData.temp4 }}<p v-if=(testNumber(liveData.temp4)) class="tempStyle"></p></text>
<text class="data">温度4</text>
</view> </view>
</view> </view>
<!-- 优化湿度卡片循环渲染 -->
<view class="uni-flex_control uni-row" > <view class="uni-flex_control uni-row" >
<view class="text uni-flex_control_two uni-view"> <view
<text class="data" :style="fontStyle">{{ liveData.humi1 }}<p v-if=(testNumber(liveData.humi1)) class="humiStyle"> %RH</p></text> class="text uni-flex_control_two uni-view"
<text class="data">湿度1</text> v-for="item in sensorCards.humi"
</view> :key="item.key"
<view class="text uni-flex_control_two uni-view"> >
<text class="data" :style="fontStyle">{{ liveData.humi2 }}<p v-if=(testNumber(liveData.humi2)) class="humiStyle"> %RH</p></text> <text class="data" :style="fontStyle">
<text class="data">湿度2</text> {{ liveData[item.key] }}
</view> <text v-if="isEffectiveValue(liveData[item.key])" class="humiStyle"> %RH</text>
<view class="text uni-flex_control_two uni-view"> </text>
<text class="data" :style="fontStyle">{{ liveData.humi3 }}<p v-if=(testNumber(liveData.humi3)) class="humiStyle"> %RH</p></text> <text class="data">{{ item.label }}</text>
<text class="data">湿度3</text>
</view>
<view class="text uni-flex_control_two uni-view">
<text class="data" :style="fontStyle">{{ liveData.humi4 }}<p v-if=(testNumber(liveData.humi4)) class="humiStyle"> %RH</p></text>
<text class="data">湿度4</text>
</view> </view>
</view> </view>
</view> </view>
</uni-section> </uni-section>
<uni-section title="设备控制" titleFontSize="16px" type="line" v-if="value!== 1"> <uni-section title="设备控制" titleFontSize="16px" type="line" v-if="value!== 1 && !['862538065276939','A','B','C'].includes(imei)">
<template v-slot:right > <template v-slot:right >
{{ control }} {{ control }}
</template> </template>
<!-- 卷膜/卷被卡片容器2列栅格布局 --> <!-- 优化设备卡片循环渲染 -->
<view class="card-grid"> <view class="card-grid">
<!-- 卷被开卡片 --> <view
<view class="control-card" @click="handleCardClick(1-status.jbk, 'jbk')"> class="control-card"
v-for="card in deviceCards"
:key="card.type"
@click="openTimeModal(card)"
>
<view class="card-text"> <view class="card-text">
<text class="card-main">卷被开</text> <text class="card-main">{{ card.name }}</text>
<!-- 核心修改添加限位时间与暂停/运行同行靠右 -->
<view class="card-sub-wrapper"> <view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jbk }}</text> <text class="card-sub" v-if="showStatusText">{{ show[card.type] || '' }}</text>
<text class="limit-time">运行时间{{ limitTimes.jbk }} s</text> <text class="limit-time">
运行时间{{ limitTimes[`${card.type}Limit`] && limitTimes[`${card.type}Limit`]!=='0' ? `${limitTimes[`${card.type}Limit`]} s` : '- -' }}
</text>
</view> </view>
</view> </view>
<view class="card-icon" :class="{ active: status.jbk === 1 }"> <view class="card-icon" :class="{ active: status[card.type] === 1 }" @click.stop="handleCardClick(1 - status[card.type], card.type)">
<uni-icons :type=" (status.jbk === 1)?'circle':'circle-filled'" size="24" color="#fff"/> <!-- @click.stop防止冒泡触发卡片点击的弹窗事件 -->
</view> <uni-icons
</view> :type="status[card.type] === 1 ? 'circle' : 'circle-filled'"
size="24"
<!-- 卷被关卡片 --> color="#fff"
<view class="control-card" @click="handleCardClick(1-status.jbg,'jbg')"> />
<view class="card-text">
<text class="card-main">卷被关</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jbg }}</text>
<text class="limit-time">运行时间{{ limitTimes.jbg }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jbg === 1 }">
<uni-icons :type=" (status.jbg === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜1开卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm1k, 'jm1k')">
<view class="card-text">
<text class="card-main">卷膜1开</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jm1k }}</text>
<text class="limit-time">运行时间{{ limitTimes.jm1k }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jm1k === 1 }">
<uni-icons :type="(status.jm1k === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜1关卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm1g, 'jm1g')">
<view class="card-text">
<text class="card-main">卷膜1关</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jm1g }}</text>
<text class="limit-time">运行时间{{ limitTimes.jm1g }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jm1g === 1 }">
<uni-icons :type="(status.jm1g === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜2卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm2k, 'jm2k')">
<view class="card-text">
<text class="card-main">卷膜2开</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jm2k }}</text>
<text class="limit-time">运行时间{{ limitTimes.jm2k }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jm2k === 1 }">
<uni-icons :type="(status.jm2k === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜2关卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm2g, 'jm2g')">
<view class="card-text">
<text class="card-main">卷膜2关</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jm2g }}</text>
<text class="limit-time">运行时间{{ limitTimes.jm2g }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jm2g === 1 }">
<uni-icons :type="(status.jm2g === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜3开卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm3k, 'jm3k')">
<view class="card-text">
<text class="card-main">卷膜3开</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jm3k }}</text>
<text class="limit-time">运行时间{{ limitTimes.jm3k }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jm3k === 1 }">
<uni-icons :type="(status.jm3k === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view>
</view>
<!-- 卷膜3关卡片 -->
<view class="control-card" @click="handleCardClick(1-status.jm3g, 'jm3g')">
<view class="card-text">
<text class="card-main">卷膜3关</text>
<view class="card-sub-wrapper">
<text class="card-sub" v-if="hide">{{ show.jm3g }}</text>
<text class="limit-time">运行时间{{ limitTimes.jm3g }} s</text>
</view>
</view>
<view class="card-icon" :class="{ active: status.jm3g === 1 }">
<uni-icons :type="(status.jm3g === 1)?'circle':'circle-filled'" size="24" color="#fff"/>
</view> </view>
</view> </view>
</view> </view>
</uni-section> </uni-section>
<uni-popup ref="inputDialog" mode="center" >
<!-- 新增修改运行时间的弹窗 -->
<view class="modal-container">
<view class="modal-title">{{ `${selectedText} - ${currentCard.name}】运行时间` }}</view>
<view class="modal-input-wrap">
<text class="modal-label">当前时间</text>
<text class="modal-current">{{ currentCardTime ? `${currentCardTime}` : '未设置' }}</text>
</view>
<view class="modal-input-wrap" >
<text class="modal-label">修改后时间</text>
<input
class="modal-input"
type="number"
v-model.number="newLimitTime"
/>
<!-- <uni-number-box v-model="newLimitTime" />-->
<text class="modal-unit"></text>
</view>
<view class="modal-btn-wrap">
<button class="modal-btn cancel" @click="close"></button>
<button class="modal-btn confirm" @click="confirmModifyTime"></button>
</view>
</view>
</uni-popup>
</view> </view>
</template> </template>
<script> <script>
// ========== mqtt ========== //
// import mqtt from 'mqtt' // mqtt import UniPopupDialog from "../../uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue";
const SENSOR_MAP = {
temp1: "201", temp2: "202", temp3: "203", temp4: "204",
humi1: "101", humi2: "102", humi3: "103", humi4: "104"
};
const MQTT_TOPIC_SUFFIX = { UP: "/up", DOWN: "/down" };
import UniDatetimePicker from "../../uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue"; import UniDatetimePicker from "../../uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue";
import UniPopup from "../../uni_modules/uni-popup/components/uni-popup/uni-popup.vue"; //
import { findDtuDataByInfo } from "@/api/system/data"; import { findDtuDataByInfo } from "@/api/system/data";
import mqttUtil from '@/utils/mqtt'; // MQTT import mqttUtil from '@/utils/mqtt';
import UniNumberBox from "../../uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue";
import {addLimit, getAgriByImei, updateLimit} from "../../api/system/assets/limit";
import {listAgri} from "../../api/system/assets/agri";
import {getNewSpecialData} from "../../api/data/specialData";
import store from "../../store";
export default { export default {
dicts: ['sys_data_map'], dicts: ['sys_data_map'],
components: { components: {
UniDatetimePicker UniNumberBox,
UniPopupDialog,
UniDatetimePicker,
UniPopup //
}, },
data() { data() {
return { return {
// ========== mqttClient ==========
// mqttClient: null,
temp: "", temp: "",
// ========== mqttConfig/ ==========
mqttConfig: { mqttConfig: {
subscribeTopic:'/up', subscribeTopic:'/up',
}, },
value: 1, value: 1,
hide: false, selectedText: '',
// hide: false
showStatusText: false,
control: '正在加载中...', control: '正在加载中...',
range: [{ range: [],
"value": '864865085016294', agriId:'',
"text": "十方北棚"
}, {
"value": '864536071808560',
"text": "七方北棚",
}, {
"value": '864865085008135',
"text": "八方北棚"
}],
imei:'', imei:'',
publishTopic: '/down', publishTopic: '/down',
title:'', title:'',
message: {}, message: {},
// ========== ========== // connected
// connected:false, connected: false,
liveData: { liveData: {
temp1: '数据加载中...', temp1: '数据加载中...',
temp2: '数据加载中...', temp2: '数据加载中...',
@ -227,7 +166,33 @@ export default {
humi3: '数据加载中...', humi3: '数据加载中...',
humi4: '数据加载中...' humi4: '数据加载中...'
}, },
// // 湿1234湿1234
sensorCards: {
temp: [
{ label: '温度1', key: 'temp1' },
{ label: '温度2', key: 'temp2' },
{ label: '温度3', key: 'temp3' },
{ label: '温度4', key: 'temp4' }
],
humi: [
{ label: '湿度1', key: 'humi1' },
{ label: '湿度2', key: 'humi2' },
{ label: '湿度3', key: 'humi3' },
{ label: '湿度4', key: 'humi4' }
]
},
//
deviceCards: [
{ type: 'jbk', name: '卷被开' },
{ type: 'jbg', name: '卷被关' },
{ type: 'jm1k', name: '卷膜1开' },
{ type: 'jm1g', name: '卷膜1关' },
{ type: 'jm2k', name: '卷膜2开' },
{ type: 'jm2g', name: '卷膜2关' },
{ type: 'jm3k', name: '卷膜3开' },
{ type: 'jm3g', name: '卷膜3关' }
],
//
show: { show: {
jbk: "暂停", jbk: "暂停",
jbg: "暂停", jbg: "暂停",
@ -238,16 +203,16 @@ export default {
jm3k: "暂停", jm3k: "暂停",
jm3g: "暂停" jm3g: "暂停"
}, },
// //
limitTimes: { limitTimes: {
jbk: 30, jbkLimit: 0,
jbg: 25, jbgLimit: 0,
jm1k: 30, jm1kLimit: 0,
jm1g: 28, jm1gLimit: 0,
jm2k: 30, jm2kLimit: 0,
jm2g: 26, jm2gLimit: 0,
jm3k: 30, jm3kLimit: 0,
jm3g: 29 jm3gLimit: 0
}, },
deviceType: '', deviceType: '',
status: { status: {
@ -260,7 +225,11 @@ export default {
jm3k: 0, jm3k: 0,
jm3g: 0 jm3g: 0
}, },
fontStyle: '' fontStyle: '',
//
currentCard: {}, //
currentCardTime: 0, //
newLimitTime: 0, //
}; };
}, },
onLoad() { onLoad() {
@ -280,28 +249,43 @@ export default {
}); });
}, },
onShow() { onShow() {
// ========== connectMqtt ========== this.getAgriList();
// this.connectMqtt();
// MQTT // MQTT
mqttUtil.setOnMessageCallback(this.ackMessage); mqttUtil.setOnMessageCallback(this.ackMessage);
// //
this.connected = mqttUtil.getMqttState().isConnected; this.connected = mqttUtil.getMqttState().isConnected;
}, },
onUnload() { onUnload() {
// ========== disconnectMqtt ==========
// this.disconnectMqtt()
// MQTT // MQTT
mqttUtil.removeOnMessageCallback(); mqttUtil.removeOnMessageCallback();
}, },
methods: { methods: {
getNewSpecialData() {
getNewSpecialData().then(response => {
if (response.code === 200 && response.data) {
this.makeSpecialData(response.data,false);
this.temp = "最后更新时间:"+response.data.time;
this.fontStyle = 'font-size:16px;'
}
})
},
change(e) { change(e) {
this.imei = e; this.imei = e;
this.publishTopic = "dtu/"+this.imei+"/down"; if ((e === 'A' || e==='B' || e==='C')
this.mqttConfig.subscribeTopic = "dtu/"+this.imei+"/up"; && store.getters && store.getters.name === 'admin' ) {
this.getNewSpecialData(e);
this.mqttConfig.subscribeTopic = `dtu/862538063921866${MQTT_TOPIC_SUFFIX.UP}`;
}
const selectedItem = this.range.find(item => item.value === e); const selectedItem = this.range.find(item => item.value === e);
if (selectedItem) { if (selectedItem) {
this.selectedText = selectedItem.text; // this.selectedText = selectedItem.text; //
this.agriId = selectedItem.agriId;
this.title= this.selectedText; this.title= this.selectedText;
if (e !== 'A' && e!=='B' && e!=='C') {
// 使MQTT
this.publishTopic = `dtu/${this.imei}${MQTT_TOPIC_SUFFIX.DOWN}`;
this.mqttConfig.subscribeTopic = `dtu/${this.imei}${MQTT_TOPIC_SUFFIX.UP}`;
var queryParams = { var queryParams = {
imei: this.imei imei: this.imei
} }
@ -312,15 +296,91 @@ export default {
this.temp = "最后更新时间:"+response.data.time; this.temp = "最后更新时间:"+response.data.time;
this.fontStyle = 'font-size:16px;' this.fontStyle = 'font-size:16px;'
}) })
this.getAgriByImei();
}
} else { } else {
this.selectedText = ''; // this.selectedText = ''; //
this.title=''; this.title='';
this.value=1; this.value=1;
this.agriId = '';
} }
this.reset(); this.reset();
this.style=""; this.style="";
}, },
getAgriByImei() {
getAgriByImei(this.imei).then(response => {
if (response.code === 200) {
if (!response.data) {
return;
}
this.limitTimes = response.data;
}
})
},
getAgriList() {
listAgri().then(response => {
if (response.code === 200) {
this.range = response.rows.map(item => ({
agriId: item.id,
text: item.agriName,
value: item.imei //
}));
if (store.getters && store.getters.name === 'admin') {
this.range.push(
{
text:"八方南棚",
value:"A"
},
{
text:"九方春棚",
value:"B"
},
{
text:"十二方棚",
value:"C"
}
)
}
}
})
},
makeSpecialData(msgData, tag) {
const div10 = (v) => (v == null ? null : Math.round((Number(v)/10)*10)/10);
// 1. B/Ctag
const B_KEYS = {
true: {temp1:'205',humi1:'105',temp2:'206',humi2:'106',temp3:'207',humi3:'107',temp4:'208',humi4:'108'},
false: {temp1:'temp5',humi1:'humi5',temp2:'temp6',humi2:'humi6',temp3:'temp7',humi3:'humi7',temp4:'temp8',humi4:'humi8'}
};
const C_KEYS = {
true: {temp1:'209',humi1:'109',temp2:'210',humi2:'110',temp3:'211',humi3:'111',temp4:'212',humi4:'112'},
false: {temp1:'temp9',humi1:'humi9',temp2:'temp10',humi2:'humi10',temp3:'temp11',humi3:'humi11',temp4:'temp12',humi4:'humi12'}
};
// 2.
const IMEI_SENSOR_MAP = {
A: SENSOR_MAP,
B: B_KEYS[tag], // tag
C: C_KEYS[tag]
};
// 3.
const isAdmin = store.getters?.name === 'admin';
const sk = (isAdmin && ['A','B','C'].includes(this.imei)) ? IMEI_SENSOR_MAP[this.imei] : IMEI_SENSOR_MAP.A;
// 4. liveData
this.liveData = Object.fromEntries(
Object.entries(sk).map(([k, skVal]) => [
k,
(tag
? div10(msgData[skVal])
: (this.imei === 'A' ? msgData[k] : msgData[skVal]))
|| "已离线..."
])
);
},
reset() { reset() {
Object.keys(this.show).forEach(key => { Object.keys(this.show).forEach(key => {
this.show[key] = "暂停"; this.show[key] = "暂停";
@ -331,8 +391,17 @@ export default {
Object.keys(this.liveData).forEach(key => { Object.keys(this.liveData).forEach(key => {
this.liveData[key] = '数据加载中...'; this.liveData[key] = '数据加载中...';
}); });
this.limitTimes= {
jbkLimit: 0,
jbgLimit: 0,
jm1kLimit: 0,
jm1gLimit: 0,
jm2kLimit: 0,
jm2gLimit: 0,
jm3kLimit: 0,
jm3gLimit: 0
};
this.deviceType = ''; this.deviceType = '';
this.control = '正在加载中...'; this.control = '正在加载中...';
this.message = {}; this.message = {};
this.temp = ''; this.temp = '';
@ -387,21 +456,18 @@ export default {
// //
this.deviceType = type; this.deviceType = type;
//todo //todo
// this.testAuto(type);
// this.status[type] = this.status[type] === 0 ? 1 : 0;
// this.show[type] = this.status[type] === 0 ? "" : "";
} }
} }
}) })
}, },
// ========== connectMqtt ==========
// connectMqtt() { ... },
// ========== disconnectMqtt ========== //
// disconnectMqtt() { ... }, testAuto(type) {
this.$set(this.status, type, this.status[type] === 0 ? 1 : 0);
// ========== publishMessage ========== this.$set(this.show, type, this.status[type] === 0 ? "暂停" : "运行");
},
publishMessage() { publishMessage() {
if (!this.connected || !this.publishTopic || !this.message) { if (!this.connected || !this.publishTopic || !this.message) {
uni.showToast({ uni.showToast({
@ -418,7 +484,6 @@ export default {
} else { } else {
this.addMessage(`发布失败:设备:[${this.publishTopic}]`) this.addMessage(`发布失败:设备:[${this.publishTopic}]`)
} }
this.message = {}; this.message = {};
}, },
@ -426,14 +491,12 @@ export default {
ackMessage(topic, payload) { ackMessage(topic, payload) {
// 1. dtu/xxx/up // 1. dtu/xxx/up
if (topic !== this.mqttConfig.subscribeTopic) return; if (topic !== this.mqttConfig.subscribeTopic) return;
// console.log(`topic=${topic}message=${payload}`)
// 2.
let msgData = {}; let msgData = {};
// JSON
try { try {
msgData = JSON.parse(payload); msgData = JSON.parse(payload);
} catch (e) { } catch (e) {
console.error("消息解析失败:", e); console.error("MQTT消息解析失败:", e, payload);
return; return;
} }
@ -444,32 +507,33 @@ export default {
this.handleOtherContent(msgData,payload) this.handleOtherContent(msgData,payload)
} }
}, },
addMessage(content) { addMessage(content) {
console.info("提示消息:" + content) console.info("提示消息:" + content)
}, },
// // +
handleCommandAck(ackData, type) { handleCommandAck(ackData, type) {
// jm2ksuc // jm2ksuc
const commandField = Object.keys(ackData.prop)[0]; // "jm2k" const commandField = Object.keys(ackData.prop)[0]; // "jm2k"
const commandValue = ackData.prop[commandField]; // 0 const commandValue = ackData.prop[commandField]; // 0/1
const isSuccess = ackData.suc; // true const isSuccess = ackData.suc; // true
if (isSuccess) { if (isSuccess) {
this.status[type] = this.status[type] === 0 ? 1 : 0; // 使$set
this.show[type] = this.status[type] === 0 ? "暂停" : "运行"; this.$set(this.status, type, this.status[type] === 0 ? 1 : 0);
this.$set(this.show, type, this.status[type] === 0 ? "暂停" : "运行");
} }
this.deviceType = ''; this.deviceType = '';
this.$modal.msgSuccess("设备操作成功!") // ========== ==========
this.$modal.msgSuccess("设备操作成功!");
console.log(`指令[${commandField}=${commandValue}]执行${isSuccess ? "成功" : "失败"}`); console.log(`指令[${commandField}=${commandValue}]执行${isSuccess ? "成功" : "失败"}`);
}, },
//
handleOtherContent(msgData,payload) { handleOtherContent(msgData,payload) {
// //
// console.log("", msgData);
// 湿线
// //
if (this.value !== 1) { if (this.value !== 1) {
var arr = ['jbk', "jbg", "jm1k", "jm1g", "jm2k", "jm2g", "jm3k", "jm3g"] var arr = ['jbk', "jbg", "jm1k", "jm1g", "jm2k", "jm2g", "jm3k", "jm3g"]
@ -484,20 +548,10 @@ export default {
} }
const allKeysNumeric2 = Object.keys(msgData).every(key => /^\d+$/.test(key)); const allKeysNumeric2 = Object.keys(msgData).every(key => /^\d+$/.test(key));
if (Object.keys(msgData).length > 0 && allKeysNumeric2) { if (Object.keys(msgData).length > 0 && allKeysNumeric2) {
const div10 = (v) => (v == null ? null : Math.round((Number(v)/10)*10)/10)
this.liveData = {
temp1: div10(msgData["201"]) || "已离线...",
humi1: div10(msgData["101"]) || "已离线...",
temp2: div10(msgData["202"]) || "已离线...",
humi2: div10(msgData["102"]) || "已离线...",
temp3: div10(msgData["203"]) || "已离线...",
humi3: div10(msgData["103"]) || "已离线...",
temp4: div10(msgData["204"]) || "已离线...",
humi4: div10(msgData["104"]) || "已离线...",
}
this.temp = "最后更新时间:" + this.getCurrentTime(); this.temp = "最后更新时间:" + this.getCurrentTime();
this.fontStyle = 'font-size:16px;' this.fontStyle = 'font-size:16px;'
this.makeSpecialData(msgData, true);
} }
} }
}, },
@ -526,19 +580,72 @@ export default {
testNumber(data) { testNumber(data) {
const reg = /^-?\d+(\.\d+)?$/; const reg = /^-?\d+(\.\d+)?$/;
return reg.test(String(data).trim()); return reg.test(String(data).trim());
},
// 湿
isEffectiveValue(value) {
return this.testNumber(value);
},
//
openTimeModal(card) {
this.currentCard = card; //
this.currentCardTime = this.limitTimes[`${card.type}Limit`]; //
this.newLimitTime = this.currentCardTime; //
this.$refs.inputDialog.open()
},
close() {
this.$refs.inputDialog.close()
},
//
confirmModifyTime() {
// 1-60
/*if (!this.newLimitTime || this.newLimitTime < 1 || this.newLimitTime > 60) {
uni.showToast({
title: '请输入1-60的有效数字',
icon: 'none'
});
return;
}*/
// limitTimes$set
if (this.newLimitTime !== this.currentCardTime) {
//
uni.showModal({
title: '温馨提示:',
content: `确定修改${this.selectedText}-${this.currentCard.name}运行时间为:${this.newLimitTime} 秒?`,
cancelText: '取消',
confirmText: '确定',
success: (res) => {
if (res.confirm) {
this.$set(this.limitTimes, `${this.currentCard.type}Limit`, this.newLimitTime);
if (this.limitTimes.imei) {
updateLimit(this.limitTimes).then(response => {
response.code === 200 ? this.$modal.msgSuccess("修改成功"):this.$modal.msgSuccess("修改失败")
})
return;
}
this.$set(this.limitTimes, 'imei', this.imei);
this.$set(this.limitTimes, 'agriName', this.selectedText);
this.$set(this.limitTimes, 'agriId', this.agriId);
addLimit(this.limitTimes).then(response => {
response.code === 200 ? this.$modal.msgSuccess("修改成功"):this.$modal.msgSuccess("修改失败")
this.getAgriByImei();
})
}
}
})
}
//
this.$refs.inputDialog.close()
//
// this.saveLimitTimeToServer(this.currentCard.type, this.newLimitTime);
} }
}, },
onHide() { onHide() {
// ========== App.vue ==========
// this.disconnectMqtt();
//
mqttUtil.removeOnMessageCallback(); mqttUtil.removeOnMessageCallback();
}, },
beforeDestroy() { beforeDestroy() {
// ========== client ==========
// if (this.client) {
// this.client.end()
// }
mqttUtil.removeOnMessageCallback(); mqttUtil.removeOnMessageCallback();
}, },
}; };
@ -634,7 +741,7 @@ export default {
} }
.uni-pb-5 { .uni-pb-5 {
padding-bottom: 40rpx; padding-bottom: 15rpx;
} }
.text { .text {
@ -681,4 +788,70 @@ export default {
/deep/ .uni-section-header__slot-right { /deep/ .uni-section-header__slot-right {
color: green; color: green;
} }
/* 新增:弹窗样式 */
.modal-container {
width: 600rpx;
background: #fff;
border-radius: 16rpx;
padding: 30rpx 20rpx;
}
.modal-title {
font-size: 30rpx;
font-weight: 500;
text-align: center;
margin-bottom: 30rpx;
color: #333;
}
.modal-input-wrap {
display: flex;
align-items: center;
margin-bottom: 25rpx;
font-size: 26rpx;
}
.modal-label {
width: 160rpx;
color: #666;
}
.modal-current {
margin-left: 14rpx;
color: #333;
}
.modal-input {
height: 60rpx;
border: 1px solid #eee;
border-radius: 8rpx;
padding: 0 15rpx;
font-size: 26rpx;
width: 80rpx;
}
.modal-unit {
margin-left: 10rpx;
color: #666;
}
.modal-btn-wrap {
display: flex;
gap: 20rpx;
margin-top: 40rpx;
}
.modal-btn {
flex: 1;
height: 70rpx;
border-radius: 8rpx;
font-size: 26rpx;
}
.modal-btn.cancel {
background: #f5f5f5;
color: #666;
}
.modal-btn.confirm {
background: #007aff;
color: #fff;
}
.icon {
margin-left: 15rpx
}
.uni-stat-tooltip {
width: 300rpx;
}
</style> </style>

View File

@ -10,9 +10,19 @@
<view class="iconfont icon-user icon"></view> <view class="iconfont icon-user icon"></view>
<input v-model="loginForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" /> <input v-model="loginForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" />
</view> </view>
<view class="input-item flex align-center"> <view class="input-item flex align-center" style="position: relative;">
<view class="iconfont icon-password icon"></view> <view class="iconfont icon-password icon"></view>
<input v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" /> <input v-if="!isShowPwd"
v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
<input v-else v-model="loginForm.password" type="text" class="input" placeholder="请输入密码" maxlength="20" />
<!-- 替换为 uni-icons 密码图标 -->
<uni-icons
class="pwd-icon"
:type="isShowPwd ? 'eye-filled' : 'eye'"
size="18"
color="#666"
@click="isShowPwd = !isShowPwd"
></uni-icons>
</view> </view>
<view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled"> <view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled">
<view class="iconfont icon-code icon"></view> <view class="iconfont icon-code icon"></view>
@ -41,10 +51,14 @@
<script> <script>
import { getCodeImg } from '@/api/login' import { getCodeImg } from '@/api/login'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import UniIcons from "../uni_modules/uni-icons/components/uni-icons/uni-icons.vue";
import store from "../store";
export default { export default {
components: {UniIcons},
data() { data() {
return { return {
isShowPwd: false,
codeUrl: "", codeUrl: "",
captchaEnabled: true, captchaEnabled: true,
// //
@ -131,7 +145,11 @@
// 864865085016294 // 864865085016294
// 864536071808560 // 864536071808560
// 864865085008135 // 864865085008135
const subscribeList = [`dtu/864865085016294/up`, `dtu/864536071808560/up`,`dtu/864865085008135/up`] const subscribeList = [`dtu/864865085016294/up`, `dtu/864536071808560/up`,`dtu/864865085008135/up`,`dtu/862538065276939/up`]
if (store.getters && store.getters.name === 'admin') {
subscribeList.push(`dtu/862538065276061/up`);
}
// 4. App.vueloginSuccessMQTT // 4. App.vueloginSuccessMQTT
app.loginSuccess(token, subscribeList) app.loginSuccess(token, subscribeList)
@ -147,7 +165,13 @@
page { page {
background-color: #ffffff; background-color: #ffffff;
} }
.pwd-icon {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
}
.normal-login-container { .normal-login-container {
width: 100%; width: 100%;
@ -184,14 +208,19 @@
color: #999; color: #999;
} }
.input { .input,
width: 100%; .input:focus,
.input:active,
.input:hover {
font-size: 14px; font-size: 14px;
line-height: 20px; line-height: 20px;
background-color: #f5f6f7; /* 输入框背景色(和页面背景区分) */
text-align: left; text-align: left;
padding-left: 15px; padding-left: 15px;
border: none;
outline: none !important;
box-shadow: none;
} }
} }
.login-btn { .login-btn {

View File

@ -52,6 +52,19 @@
<view>编辑资料</view> <view>编辑资料</view>
</view> </view>
</view> </view>
<view class="list-cell list-cell-arrow" @click="handleMqtt" v-if="name === 'admin'">
<view class="menu-item-box">
<view class="iconfont icon-service menu-icon"></view>
<view>mqtt工具</view>
</view>
</view>
<view class="list-cell list-cell-arrow" @click="handleRequire" v-if="name === 'admin'">
<view class="menu-item-box">
<uni-icons type="wallet" size="19" color="#007AFF"></uni-icons>
<view style="margin-left: 4rpx"> 需求清单</view>
</view>
</view>
<view class="list-cell list-cell-arrow" @click="handleHelp"> <view class="list-cell list-cell-arrow" @click="handleHelp">
<view class="menu-item-box"> <view class="menu-item-box">
<view class="iconfont icon-help menu-icon"></view> <view class="iconfont icon-help menu-icon"></view>
@ -77,7 +90,10 @@
</template> </template>
<script> <script>
import UniIcons from "../../uni_modules/uni-icons/components/uni-icons/uni-icons.vue";
export default { export default {
components: {UniIcons},
data() { data() {
return { return {
name: this.$store.state.user.name name: this.$store.state.user.name
@ -91,6 +107,7 @@
return uni.getSystemInfoSync().windowHeight - 50 return uni.getSystemInfoSync().windowHeight - 50
} }
}, },
methods: { methods: {
handleToInfo() { handleToInfo() {
this.$tab.navigateTo('/pages/mine/info/index') this.$tab.navigateTo('/pages/mine/info/index')
@ -110,6 +127,12 @@
handleHelp() { handleHelp() {
this.$tab.navigateTo('/pages/mine/help/index') this.$tab.navigateTo('/pages/mine/help/index')
}, },
handleMqtt() {
this.$tab.navigateTo('/pages/mine/mqtt/index')
},
handleRequire() {
this.$tab.navigateTo('/pages/mine/require/index')
},
handleAbout() { handleAbout() {
this.$tab.navigateTo('/pages/mine/about/index') this.$tab.navigateTo('/pages/mine/about/index')
}, },

View File

@ -231,14 +231,14 @@
</template> </template>
<script> <script>
import mqtt from 'mqtt/dist/mqtt.min' import mqtt from 'mqtt/dist/mqtt'
export default { export default {
data() { data() {
return { return {
// MQTT // MQTT
mqttConfig: { mqttConfig: {
broker: 'ws://122.51.109.52:9001/mqtt', // broker: 'wxs://mq.xiaoces.com:443/mqtt', //
clientId: 'uniapp_mqtt_' + Math.random().toString(16).substr(2, 8), clientId: 'uniapp_mqtt_' + Math.random().toString(16).substr(2, 8),
username: 'admin', username: 'admin',
password: 'Admin#12345678', password: 'Admin#12345678',

View File

@ -0,0 +1,641 @@
<template>
<view class="container">
<view class="form-container">
<uni-section title="基础卡片" type="line">
<!-- 基础表单校验缩小字体+一行两列布局 -->
<uni-forms
ref="valiForm"
label-position="left"
:modelValue="form"
style="font-size: 10px;"
>
<!-- 一行两列布局容器 -->
<view class="form-row">
<uni-forms-item label="标题:" name="title" class="form-item">
<uni-easyinput
v-model="form.title"
placeholder="请输入标题搜索"
/>
</uni-forms-item>
<uni-forms-item label="详情:" name="detail" class="form-item">
<uni-easyinput
v-model="form.detail"
placeholder="请输入详情搜索"
/>
</uni-forms-item>
</view>
<!-- 第二行两列 -->
<view class="form-row">
<uni-forms-item label="优先级:" name="priority" class="form-item">
<uni-data-select
v-model="form.priority"
:localdata="priorityList"
/>
</uni-forms-item>
<uni-forms-item label="进度:" name="progress" class="form-item">
<uni-data-select
v-model="form.progress"
:localdata="progressList"
/>
</uni-forms-item>
</view>
</uni-forms>
<view class="btn-group">
<view class="btn-left">
<!-- 新增按钮点击打开弹窗 -->
<button
class="iconfont icon-add mini-btn add-btn"
@click="openDialog('add')"
hover-class="is-hover"
type="default"
size="mini"
> 新增</button>
<!-- 删除按钮未选中数据时禁用 -->
<button
class="iconfont icon-ashbin mini-btn del-btn"
hover-class="is-hover"
type="default"
size="mini"
:disabled="selectedIndexs.length === 0"
@click="batchDel"
> 删除</button>
<!-- 编辑按钮未选中数据时禁用 -->
<button
class="iconfont icon-edit mini-btn edit-btn"
hover-class="is-hover"
type="default"
size="mini"
:disabled="selectedIndexs.length === 0"
@click="openDialog('edit')"
> 编辑</button>
</view>
<view class="btn-right">
<button
class="iconfont icon-search mini-btn"
hover-class="is-hover"
type="default"
size="mini"
@click="search"
> 查询</button>
</view>
</view>
</uni-section>
</view>
<view class="uni-container">
<!-- 表格区域保持原有紧凑样式并优化 -->
<uni-table
ref="table"
:loading="loading"
border
stripe
type="selection"
emptyText="暂无更多数据"
@selection-change="selectionChange"
style="width: 100%;"
:border-width="1"
>
<uni-tr>
<uni-th align="center" class="ui-th" sortable width="200rpx" @sort-change="idSort">需求单号</uni-th>
<uni-th align="center" width="300rpx" class="ui-th">需求标题</uni-th>
<uni-th align="center" width="240rpx" class="ui-th">需求详情</uni-th>
<uni-th align="center" width="140rpx" @sort-change="prioritySort" sortable class="ui-th">优先级</uni-th>
<uni-th align="center" width="140rpx" class="ui-th">进度</uni-th>
<uni-th align="center" width="200rpx" class="ui-th">需求类型</uni-th>
<uni-th align="center" width="200rpx" class="ui-th">经办人名称</uni-th>
<uni-th align="center" width="300rpx" class="ui-th">操作</uni-th>
</uni-tr>
<uni-tr v-for="(item, index) in tableData" :key="index" style="height: 30px; line-height: 30px;">
<uni-td style="padding: 1px 0; font-size: 13px;">{{ item.id }}</uni-td>
<uni-td style="padding: 1px 0; font-size: 13px;">{{ item.title }}</uni-td>
<uni-td style="padding: 1px 0; font-size: 13px;">
<view class="name">{{ item.detail }}</view>
</uni-td>
<uni-td align="center" style=" padding: 1px 0; font-size: 13px;">{{ item.priority }}</uni-td>
<uni-td align="center" style=" padding: 1px 0; font-size: 13px;">{{ item.progress }}</uni-td>
<uni-td align="center" style=" padding: 1px 0; font-size: 13px;">{{ item.reqType }}</uni-td>
<uni-td align="center" style=" padding: 1px 0; font-size: 13px;">{{ item.assigneeName }}</uni-td>
<uni-td style="width: auto; padding: 1px 0; font-size: 13px;">
<view class="uni-group">
<!-- 行内修改按钮点击打开编辑弹窗 -->
<button size="mini" type="primary" style="padding: 1px 6px; font-size: 10px;" @click="openDialog('edit', item)">修改</button>
<!-- 行内删除按钮点击单行删除 -->
<button size="mini" type="warn" style="padding: 1px 6px; font-size: 10px;" @click="delSingle(item)"></button>
</view>
</uni-td>
</uni-tr>
</uni-table>
<view class="uni-pagination-box" style="margin-top: 8px;">
<uni-pagination
show-icon
:page-size="form.pageSize"
:current="form.pageNum"
:total="total"
@change="change"
style="font-size: 11px;"
/>
</view>
</view>
<!-- 新增/修改弹窗 -->
<uni-popup ref="formDialog" type="center" :mask-click="false">
<view class="dialog-container">
<view class="dialog-title">{{ dialogType === 'add' ? '新增需求' : '编辑需求' }}</view>
<!-- 弹窗表单 -->
<uni-forms
ref="dialogForm"
label-width="65px"
label-position="left"
:modelValue="dialogFormData"
:rules="formRules"
style="padding: 10rpx;"
>
<!-- 需求标题必填 -->
<uni-forms-item label="需求标题:" name="title" required>
<uni-easyinput
v-model="dialogFormData.title"
placeholder="请输入需求标题"
style="font-size: 12px;"
/>
</uni-forms-item>
<!-- 详情必填 -->
<uni-forms-item label="详情:" name="detail" required>
<uni-easyinput
v-model="dialogFormData.detail"
type="textarea"
placeholder="请输入需求详情"
style="font-size: 12px; height: 100px;"
/>
</uni-forms-item>
<!-- 优先级下拉框默认高 -->
<uni-forms-item label="优先级:" name="priority">
<uni-data-select
v-model="dialogFormData.priority"
:localdata="priorityList"
style="font-size: 12px;"
/>
</uni-forms-item>
<!-- 进度下拉框 -->
<uni-forms-item label="进度:" name="progress">
<uni-data-select
v-model="dialogFormData.progress"
:localdata="progressList"
style="font-size: 12px;"
/>
</uni-forms-item>
<!-- 需求类型下拉框 -->
<uni-forms-item label="需求类型:" name="reqType">
<uni-data-select
v-model="dialogFormData.reqType"
:localdata="reqTypeList"
style="font-size: 12px;"
/>
</uni-forms-item>
<!-- 经办人可选输入框 -->
<uni-forms-item label="经办人:" name="assigneeName">
<uni-easyinput
v-model="dialogFormData.assigneeName"
placeholder="请输入经办人名称"
style="font-size: 12px;"
/>
</uni-forms-item>
</uni-forms>
<!-- 弹窗按钮 -->
<view class="dialog-btn-group">
<button class="cancel-btn" @click="closeDialog"></button>
<button class="confirm-btn" @click="submitDialog"></button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import UniTh from "../../../uni_modules/uni-table/components/uni-th/uni-th.vue";
import UniTr from "../../../uni_modules/uni-table/components/uni-tr/uni-tr.vue";
import UniTd from "../../../uni_modules/uni-table/components/uni-td/uni-td.vue";
import {addRequire, delRequire, delRequires, listRequire, updateRequire} from "../../../api/system/require";
import UniTable from "../../../uni_modules/uni-table/components/uni-table/uni-table.vue";
import UniPopup from "../../../uni_modules/uni-popup/components/uni-popup/uni-popup.vue";
import UniForms from "../../../uni_modules/uni-forms/components/uni-forms/uni-forms.vue";
import UniFormsItem from "../../../uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue";
import UniEasyinput from "../../../uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue";
import UniDataSelect from "../../../uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue";
export default {
components: {
UniTable, UniTd, UniTr, UniTh,
UniPopup, UniForms, UniFormsItem, UniEasyinput, UniDataSelect
},
data() {
return {
searchVal: '',
tableData: [],
total: 0,
//
priorityList: [
{ value: 0, text: '高' },
{ value: 1, text: '中' },
{ value: 2, text: '低' }
],
//
progressList: [
{ value: 0, text: '未完成' },
{ value: 1, text: '进行中' },
{ value: 2, text: '已完成' }
],
//
reqTypeList: [
{ value: 0, text: '功能需求' },
{ value: 1, text: '优化需求' },
{ value: 2, text: 'BUG修复' }
],
form: {
title: null,
detail: null,
priority: null,
progress: null,
pageNum: 1,
pageSize: 10
},
loading: false,
selectIds:[],
selectedIndexs: [], //
//
dialogType: 'add', // add- edit-
dialogFormData: {
title: '',
detail: '',
priority: 0, //
progress: 0,
reqType: '',
assigneeName: ''
},
//
formRules: {
title: {
rules: [{ required: true, errorMessage: '请输入需求标题' }]
},
detail: {
rules: [{ required: true, errorMessage: '请输入需求详情' }]
}
},
currentEditItem: null //
}
},
onLoad() {
this.getData()
},
methods: {
//
selectedItems() {
this.selectIds = this.selectedIndexs.map(i => this.tableData[i].id);
return this.selectedIndexs.map(i => this.tableData[i])
},
// selection-change
selectionChange(e) {
this.selectedIndexs = e.detail.index
console.info(e)
},
idSort(e) {
if (e["order"] === 'descending') {
this.tableData.sort((a, b) => b.id - a.id);
return;
}
this.tableData.sort((a, b) => a.id - b.id);
},
prioritySort(e) {
if (e.order === 'descending') {
this.tableData.sort((a, b) => b.priority - a.priority);
return;
}
this.tableData.sort((a, b) => a.priority - b.priority);
},
// ========== ==========
//
batchDel() {
const data = this.tableData.filter(item => !this.selectIds.includes(item.id));
console.info(`${this.selectIds}选中的数据:${JSON.stringify(data)} `)
// todo bug
const selectedItems = this.selectedItems()
if (selectedItems.length === 0) {
uni.showToast({ title: '请选择要删除的数据', icon: 'none' })
return
}
const selectedIds = selectedItems.map(item => Number(item.id))
.filter(id => id !== undefined && id !== null && id !== '');
uni.showModal({
title: '提示',
content: `确定要删除选中的${selectedItems.length}条数据吗?`,
success: (res) => {
if (res.confirm) {
delRequires(selectedIds).then((response) => {
if (response.code === 200) {
uni.showToast({ title: '批量删除成功', icon: 'success' })
} else {
uni.showToast({ title: '批量删除失败', icon: 'error' })
}
this.getData()
this.selectedIndexs=[]
})
}
}
})
},
//
change(e) {
this.selectedIndexs = []
this.getData(e.current)
},
//
search() {
this.getData()
},
//
getData(pageNum) {
this.loading = true
this.form.pageNum = pageNum || this.form.pageNum
listRequire(this.form).then(response => {
if (response.code === 200 && response.rows) {
this.tableData = response.rows
this.total = response.total
}
this.loading = false
}).catch(error => {
console.error('获取数据失败:', error)
this.loading = false
})
},
// ========== ==========
//
openDialog(type, item = null) {
this.dialogType = type
//
this.dialogFormData = {
title: '',
detail: '',
priority: 0,
progress: 0,
reqType: '',
assigneeName: ''
}
//
if (type === 'edit') {
// item
const editItem = item || this.selectedItems()[0]
if (editItem) {
this.currentEditItem = editItem
this.dialogFormData = { ...editItem }
} else {
uni.showToast({ title: '请选择要编辑的数据', icon: 'none' })
return
}
}
//
this.$refs.formDialog.open()
},
//
closeDialog() {
this.$refs.formDialog.close()
//
this.$refs.dialogForm.clearValidate()
},
//
submitDialog() {
//
this.$refs.dialogForm.validate().then(() => {
if (this.dialogType === 'add') {
addRequire(this.dialogFormData).then(response => {
if (response.code===200) {
this.$modal.msgSuccess("新增成功")
} else {
this.$modal.msgError("新增失败")
}
this.getData()
})
} else {
updateRequire(this.dialogFormData).then(response => {
if (response.code===200) {
this.$modal.msgSuccess("修改成功")
} else {
this.$modal.msgError("修改失败")
}
this.getData()
})
}
//
this.closeDialog()
}).catch(err => {
console.error('表单校验失败:', err)
})
},
//
delSingle(item) {
uni.showModal({
title: '提示',
content: '确定要删除这条数据吗?',
success: (res) => {
if (res.confirm) {
delRequire(item.id).then(response => {
if (response.code === 200) {
uni.showToast({title: '删除成功', icon: 'success'})
} else {
uni.showToast({title: '删除失败', icon: 'success'})
}
})
this.getData()
this.selectedIndexs=[]
}
}
})
}
}
}
</script>
<style scoped>
/* 页面基础样式重置 */
page {
padding-top: 0 !important;
font-size: 12px;
}
/* 整体容器样式 */
.container {
margin: 20rpx;
border-radius: 20rpx;
background: #fff;
font-size: 12px;
}
.form-container {
margin: 20rpx 0 0 0;
padding: 15rpx;
border-radius: 20rpx;
background: #fff;
}
/* 表单一行两列布局核心样式 - 优化版 */
.form-row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin: 0 10rpx 10rpx 10rpx;
width: 100%;
/* 新增清除flex布局默认的基线对齐避免垂直错位 */
align-items: flex-start;
/* 新增:计算宽度时包含内边距/边框,避免溢出 */
box-sizing: border-box;
}
.form-item {
display: flex;
align-items: center;
width: 48%; /* 保留你的48%宽度留2%间距 */
box-sizing: border-box;
margin: 0 !important; /* 保留清除默认间距 */
/* 新增适配uni-forms-item的关键样式 */
padding: 0; /* 清除组件默认内边距 */
min-height: auto; /* 取消组件默认最小高度,避免高度不一致 */
}
/* 新增适配uni-data-select组件防止下拉框溢出 */
.form-item .uni-data-select {
width: 100%; /* 下拉选择器占满form-item宽度 */
flex: 1; /* 让选择器自适应剩余空间 */
}
/* 新增统一label标签样式避免文字错位 */
.form-item .uni-forms-item__label {
/* 根据你的需求调整label宽度比如固定120rpx */
flex: 0 0 120rpx;
padding: 0; /* 清除默认内边距 */
margin-right: 10rpx; /* label和选择器之间留间距 */
}
/* 新增:统一表单项内容区域样式 */
.form-item .uni-forms-item__content {
flex: 1; /* 内容区域占满剩余宽度 */
padding: 0; /* 清除默认内边距 */
}
/* 核心父容器flex布局通过justify-content: space-between实现左右分布 */
.btn-group {
display: flex;
justify-content: space-between; /* 关键:左右元素分别靠两端 */
align-items: center; /* 垂直居中,保证按钮对齐 */
width: 100%; /* 占满父容器宽度,确保分布效果 */
padding: 20rpx 10rpx 0; /* 左右留间距,避免贴边 */
box-sizing: border-box;
}
/* 左侧按钮容器:横向排列,靠左 */
.btn-left {
display: flex;
align-items: center;
}
/* 右侧按钮容器:横向排列,靠右 */
.btn-right {
display: flex;
align-items: center;
}
/* 表格容器样式 */
.uni-container {
padding: 10px;
box-sizing: border-box;
}
.uni-group {
display: flex;
align-items: center;
justify-content: center;
}
/* 需求详情文字样式 */
.name {
line-height: 18px;
font-size: 11px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 分页区域样式 */
.uni-pagination-box {
text-align: center;
}
/deep/ .uni-forms-item__label {
font-size: 12px !important;
padding: 0 !important;
}
/deep/ .uni-select, /deep/.uni-easyinput__content-input {
font-size: 12px !important;
height: 30px;
}
/* 弹窗样式 */
.dialog-container {
background: #fff;
border-radius: 10rpx;
padding: 20rpx;
width: 600rpx;
}
.dialog-title {
font-size: 16px;
font-weight: bold;
text-align: center;
padding: 10rpx 0;
border-bottom: 1px solid #eee;
margin-bottom: 10rpx;
}
.dialog-btn-group {
display: flex;
justify-content: flex-end;
margin-top: 20rpx;
gap: 10rpx;
}
/* 禁用按钮样式 */
/deep/ button[disabled] {
background-color: #e5e5e5 !important;
color: #999 !important;
border-color: #ddd !important;
}
/deep/ .uni-forms-item {
margin-bottom: 12rpx;
}
/deep/ .uni-table-td {
text-align: center !important;
}
.ui-th {
width: auto;
height: 18px;
line-height: 18px;
font-size: 13px;
}
.ui-th:last-child {
width: 160rpx;
}
/deep/ .uni-table-th {
padding: 16rpx 5rpx;
}
.uni-group button:last-child {
margin-left: 0;
}
</style>

View File

@ -143,7 +143,7 @@
.uni-margin-wrap { .uni-margin-wrap {
width: 690rpx; width: 690rpx;
width: 100%; width: 100%;
;
} }
.swiper { .swiper {

View File

@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path stroke viewBox IE
normalize.css */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@ -0,0 +1,368 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe664;</span>
<div class="name">add</div>
<div class="code-name">&amp;#xe664;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe665;</span>
<div class="name">ashbin</div>
<div class="code-name">&amp;#xe665;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe668;</span>
<div class="name">close</div>
<div class="code-name">&amp;#xe668;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe685;</span>
<div class="name">close-bold</div>
<div class="code-name">&amp;#xe685;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe654;</span>
<div class="name">edit</div>
<div class="code-name">&amp;#xe654;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xea56;</span>
<div class="name">search</div>
<div class="code-name">&amp;#xea56;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe615;</span>
<div class="name">查询</div>
<div class="code-name">&amp;#xe615;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe65b;</span>
<div class="name">蜡烛</div>
<div class="code-name">&amp;#xe65b;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.ttf?t=1768306553008') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-add"></span>
<div class="name">
add
</div>
<div class="code-name">.icon-add
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-ashbin"></span>
<div class="name">
ashbin
</div>
<div class="code-name">.icon-ashbin
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-close"></span>
<div class="name">
close
</div>
<div class="code-name">.icon-close
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-close-bold"></span>
<div class="name">
close-bold
</div>
<div class="code-name">.icon-close-bold
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-edit"></span>
<div class="name">
edit
</div>
<div class="code-name">.icon-edit
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-search"></span>
<div class="name">
search
</div>
<div class="code-name">.icon-search
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-search1"></span>
<div class="name">
查询
</div>
<div class="code-name">.icon-search1
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-lazhu"></span>
<div class="name">
蜡烛
</div>
<div class="code-name">.icon-lazhu
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-add"></use>
</svg>
<div class="name">add</div>
<div class="code-name">#icon-add</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-ashbin"></use>
</svg>
<div class="name">ashbin</div>
<div class="code-name">#icon-ashbin</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-close"></use>
</svg>
<div class="name">close</div>
<div class="code-name">#icon-close</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-close-bold"></use>
</svg>
<div class="name">close-bold</div>
<div class="code-name">#icon-close-bold</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-edit"></use>
</svg>
<div class="name">edit</div>
<div class="code-name">#icon-edit</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-search"></use>
</svg>
<div class="name">search</div>
<div class="code-name">#icon-search</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-search1"></use>
</svg>
<div class="name">查询</div>
<div class="code-name">#icon-search1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-lazhu"></use>
</svg>
<div class="name">蜡烛</div>
<div class="code-name">#icon-lazhu</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>

View File

@ -0,0 +1,45 @@
@font-face {
font-family: "iconfont"; /* Project id */
src: url('@/static/font_ve87r6kfq3/iconfont.ttf?t=1768306553008') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-add:before {
content: "\e664";
}
.icon-ashbin:before {
content: "\e665";
}
.icon-close:before {
content: "\e668";
}
.icon-close-bold:before {
content: "\e685";
}
.icon-edit:before {
content: "\e654";
}
.icon-search:before {
content: "\ea56";
}
.icon-search1:before {
content: "\e615";
}
.icon-lazhu:before {
content: "\e65b";
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,65 @@
{
"id": "",
"name": "",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "15838424",
"name": "add",
"font_class": "add",
"unicode": "e664",
"unicode_decimal": 58980
},
{
"icon_id": "15838430",
"name": "ashbin",
"font_class": "ashbin",
"unicode": "e665",
"unicode_decimal": 58981
},
{
"icon_id": "15838444",
"name": "close",
"font_class": "close",
"unicode": "e668",
"unicode_decimal": 58984
},
{
"icon_id": "15838563",
"name": "close-bold",
"font_class": "close-bold",
"unicode": "e685",
"unicode_decimal": 59013
},
{
"icon_id": "33215995",
"name": "edit",
"font_class": "edit",
"unicode": "e654",
"unicode_decimal": 58964
},
{
"icon_id": "44063702",
"name": "search",
"font_class": "search",
"unicode": "ea56",
"unicode_decimal": 59990
},
{
"icon_id": "44849862",
"name": "查询",
"font_class": "search1",
"unicode": "e615",
"unicode_decimal": 58901
},
{
"icon_id": "46589632",
"name": "蜡烛",
"font_class": "lazhu",
"unicode": "e65b",
"unicode_decimal": 58971
}
]
}

Binary file not shown.

View File

@ -88,3 +88,39 @@
} }
} }
} }
/* 极简版无CSS变量 */
.mini-btn {
margin-left: 10rpx !important;
font-size: 12px !important;
padding: 5rpx 15rpx !important;
border-width: 1px !important;
border-style: solid !important;
}
.btn-left .mini-btn:first-child {
margin-left: 0 !important;
}
/* 默认按钮 */
.mini-btn { color: #fff !important; background: #1890ff !important; border-color: #1890ff !important; }
/* 新增按钮 */
.add-btn { color: #1890ff !important; background: #e8f4ff !important; border-color: #a3d3ff !important; }
/* 删除按钮 */
.del-btn { color: #71e2a3 !important; background: #e7faf0 !important; border-color: #d0f5e0 !important; }
/* 编辑按钮 */
.edit-btn { color: #ff9292 !important; background: #ffeded !important; border-color: #ffdbdb !important; }
.cancel-btn { color: #333 !important; background: #f5f5f5 !important; border-color: #f5f5f5 !important; height: 55rpx; line-height: 55rpx; font-size: 14px !important; }
.confirm-btn { color: #fff !important; background: #1890ff !important; border-color: #1890ff !important; height: 55rpx; line-height: 55rpx; font-size: 14px !important; }
/* hover统一样式 */
.is-hover {
color: #fff !important;
/* 按按钮类型匹配背景色 */
&.add-btn { background: #1890ff !important; border-color: #1890ff !important; }
&.del-btn { background: #71e2a3 !important; border-color: #71e2a3 !important; }
&.edit-btn { background: #ff9292 !important; border-color: #ff9292 !important; }
&:not(.add-btn):not(.del-btn):not(.edit-btn) { background: #1682e6 !important; border-color: #1682e6 !important; }
}

View File

@ -4,3 +4,4 @@
@import "@/static/scss/colorui.css"; @import "@/static/scss/colorui.css";
// iconfont // iconfont
@import "@/static/font/iconfont.css"; @import "@/static/font/iconfont.css";
@import "@/static/font_ve87r6kfq3/iconfont.css";

View File

@ -13,11 +13,11 @@ import mqtt from 'mqtt/dist/mqtt'
// ===================== MQTT配置暂时写死TODO后续从数据字典获取===================== // ===================== MQTT配置暂时写死TODO后续从数据字典获取=====================
const MQTT_CONFIG = { const MQTT_CONFIG = {
server: 'wxs://mq.mj142.cn:443/mqtt', // 替换为你的MQTT服务器地址 server: 'wxs://mq.xiaoces.com:443/mqtt', // 替换为你的MQTT服务器地址
username: 'admin', // 替换为通用账号 username: 'admin', // 替换为通用账号
password: 'Admin#12345678', // 替换为通用密码 password: 'Admin#12345678', // 替换为通用密码
clean: true, clean: true,
host: 'mq.mj142.cn', host: 'mq.xiaoces.com',
port: 443, port: 443,
reconnectPeriod: 5000, // 重连间隔 reconnectPeriod: 5000, // 重连间隔
connectTimeout: 10000, // 连接超时 connectTimeout: 10000, // 连接超时
@ -59,7 +59,7 @@ export function initMqttConfig() {
console.log('MQTT配置初始化成功clientId', mqttState.options.clientId) console.log('MQTT配置初始化成功clientId', mqttState.options.clientId)
return true return true
} catch (err) { } catch (err) {
uni.showToast({ title: '设备连接异常', icon: 'none', duration: 2000 }) uni.showToast({ title: '设备连接异常-设备初始化失败', icon: 'none', duration: 2000 })
console.error('MQTT配置初始化失败', err) console.error('MQTT配置初始化失败', err)
return false return false
} }
@ -72,7 +72,7 @@ export function initMqttConfig() {
export function connectMqtt() { export function connectMqtt() {
// 前置校验:是否已初始化配置 // 前置校验:是否已初始化配置
if (!mqttState.options.clientId) { if (!mqttState.options.clientId) {
uni.showToast({ title: '设备连接异常', icon: 'none' }) uni.showToast({ title: '设备连接失败', icon: 'none' })
console.error('MQTT连接失败请先调用initMqttConfig初始化配置') console.error('MQTT连接失败请先调用initMqttConfig初始化配置')
return false return false
} }
@ -86,7 +86,7 @@ export function connectMqtt() {
try { try {
// 创建客户端实例(同步操作) // 创建客户端实例(同步操作)
// #ifndef MP-WEIXIN // #ifndef MP-WEIXIN
MQTT_CONFIG.server = 'wss://mq.mj142.cn:443/mqtt' MQTT_CONFIG.server = 'wxs://mq.xiaoces.com:443/mqtt'
// #endif // #endif
console.info("mqttState.connect",mqttState) console.info("mqttState.connect",mqttState)
mqttState.client = mqtt.connect(MQTT_CONFIG.server, mqttState.options) mqttState.client = mqtt.connect(MQTT_CONFIG.server, mqttState.options)
@ -156,7 +156,7 @@ export function removeOnMessageCallback() {
*/ */
export function updateSubscribeList(list) { export function updateSubscribeList(list) {
if (!Array.isArray(list)) { if (!Array.isArray(list)) {
uni.showToast({ title: '设备连接异常', icon: 'none' }) uni.showToast({ title: '设备订阅更新异常', icon: 'none' })
console.error('订阅列表必须是数组') console.error('订阅列表必须是数组')
return false return false
} }
@ -183,7 +183,7 @@ function subscribeAllTopics() {
client.subscribe(subscribeList, { qos: 0 }, (err) => { client.subscribe(subscribeList, { qos: 0 }, (err) => {
if (err) { if (err) {
uni.showToast({ title: '设备连接异常', icon: 'none' }) uni.showToast({ title: '设备订阅异常', icon: 'none' })
console.error('MQTT订阅失败', err) console.error('MQTT订阅失败', err)
} else { } else {
console.log(`MQTT成功订阅${subscribeList.join(', ')}`) console.log(`MQTT成功订阅${subscribeList.join(', ')}`)
@ -198,6 +198,7 @@ function subscribeAllTopics() {
* @returns {Boolean} - 是否触发发布成功 * @returns {Boolean} - 是否触发发布成功
*/ */
export function publishMqtt(topic, message) { export function publishMqtt(topic, message) {
if (process.env.NODE_ENV === "production") {
const { isConnected, client } = mqttState const { isConnected, client } = mqttState
if (!isConnected || !client) { if (!isConnected || !client) {
uni.showToast({ title: '控制异常', icon: 'none' }) uni.showToast({ title: '控制异常', icon: 'none' })
@ -226,6 +227,7 @@ export function publishMqtt(topic, message) {
console.error('MQTT发布异常', err) console.error('MQTT发布异常', err)
return false return false
} }
}
} }
/** /**
@ -245,7 +247,7 @@ export function disconnectMqtt() {
console.log('MQTT连接已断开') console.log('MQTT连接已断开')
return true return true
} catch (err) { } catch (err) {
uni.showToast({ title: '设备连接异常', icon: 'none' }) uni.showToast({ title: '设备通信异常', icon: 'none' })
console.error('MQTT断开失败', err) console.error('MQTT断开失败', err)
return false return false
} }
@ -275,6 +277,39 @@ export function getMqttState() {
} }
} }
// utils/mqttOnline.js
let timer = null;
export function startMqttOnlinePing(intervalMs = 20000) {
if (!mqttState.client || !mqttState.options.clientId) return;
if (timer) return;
const topic = `frontend/${mqttState.options.clientId}/online`;
const ping = () => {
try {
if (!mqttState.client.connected) return;
const payload = JSON.stringify({ ts: Date.now() });
// qos=0 足够retain 不要
mqttState.client.publish(topic, payload, { qos: 0, retain: false });
} catch (e) {}
};
ping();
// 每 20000ms20秒执行一次 ping
timer = setInterval(ping, intervalMs);
}
export function stopMqttOnlinePing() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
// 导出所有方法(全局调用) // 导出所有方法(全局调用)
export default { export default {
initMqttConfig, initMqttConfig,