温度接入数据库

master
lld 2025-12-28 21:32:15 +08:00
parent 04e19f22df
commit f5b9b4687f
15 changed files with 2355 additions and 85 deletions

12
api/system/data.js Normal file
View File

@ -0,0 +1,12 @@
import request from '@/utils/request'
// 查询DTU温湿度上报数据列表
export function findDtuDataByInfo(query) {
return request({
url: '/system/data/findDtuDataByInfo',
method: 'get',
params: query
})
}

View File

@ -0,0 +1,77 @@
<template>
<view class="container" :style="{padding: linePadding}">
<!-- 带文字的分割线 -->
<view class="divider" :style="{margin: lineMargin}">
<view class="line" :style="{height: lineHeight,backgroundColor: lineColor}"></view>
<text :style="{padding: textPadding,fontSize: fontSize,color: fontColor,fontWeight: fontWeight}">{{ lineText }}</text>
<view class="line" :style="{height: lineHeight,backgroundColor: lineColor,}"></view>
</view>
</view>
</template>
<script>
export default {
name: "DividerText",
props: {
// 线
linePadding: {
type: String,
default: '10rpx 0'
},
//线
lineMargin: {
type: String,
default: '30rpx 0'
},
// 线
lineHeight: {
type: String,
default: '1rpx'
},
// 线
lineColor: {
type: String,
default: '#e5e5e5'
},
// 线
textPadding: {
type: String,
default: '0 20rpx'
},
//
fontSize: {
type: String,
default: '28rpx'
},
//
fontColor: {
type: String,
default: '#999'
},
//
fontWeight: {
type: String,
default: 'normal'
},
// 线
lineText: {
type: String,
default: '',
required: true
}
}
};
</script>
<style scoped>
.divider {
display: flex;
align-items: center;
width: 100%;
}
.line {
flex: 1
}
</style>

View File

@ -0,0 +1,54 @@
<template>
<view class="divider" :style="{ margin: margin }">
<view
:style="{
backgroundColor: color,
height: thickness + 'rpx',
width: hasMargin ? 'calc(100% - ' + sideMargin + 'rpx)' : '100%'
}"
></view>
</view>
</template>
<script>
export default {
name: "Divider",
props: {
// 线
color: {
type: String,
default: "#e5e5e5"
},
// 线
thickness: {
type: Number,
default: 1
},
// "20rpx 0"
margin: {
type: String,
default: "20rpx 0"
},
//
hasMargin: {
type: Boolean,
default: false
},
//
sideMargin: {
type: Number,
default: 40
}
}
};
</script>
<style scoped>
.divider {
width: 100%;
display: flex;
justify-content: center;
}
</style>

View File

@ -1,8 +1,8 @@
// 应用全局配置 // 应用全局配置
module.exports = { module.exports = {
// baseUrl: 'https://vue.ruoyi.vip/prod-api', // baseUrl: 'https://vue.ruoyi.vip/prod-api',
// baseUrl: 'http://localhost:8088', baseUrl: 'http://172.14.24.109:8088',
baseUrl: 'http://1.94.254.176:8088', // baseUrl: 'http://1.94.254.176:8088',
// 应用信息 // 应用信息
appInfo: { appInfo: {
// 应用名称 // 应用名称

View File

@ -5,6 +5,8 @@ import store from './store' // store
import plugins from './plugins' // plugins import plugins from './plugins' // plugins
import './permission' // permission import './permission' // permission
import { getDicts } from "@/api/system/dict/data" import { getDicts } from "@/api/system/dict/data"
import "./utils/uni.css";
Vue.use(plugins) Vue.use(plugins)

View File

@ -2,16 +2,63 @@
<view class="container"> <view class="container">
<!-- 控制设置标题 --> <!-- 控制设置标题 -->
<view class="control-title">控制设置</view> <view class="control-title">控制设置</view>
<uni-section title="请选择大棚:" titleFontSize="16px" type="line"> <uni-section title="请选择大棚:" 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="title" titleFontSize="16px" type="line" v-if="value!== 1">
<template v-slot:right> <uni-section title="实时温湿度" titleFontSize="16px" type="line" v-if="value!== 1">
<template v-slot:right >
{{ temp }} {{ temp }}
</template> </template>
<view>
<view class="uni-flex_control uni-row" >
<view class="text uni-flex_control uni-view">
<text class="data" :style="fontStyle">{{ liveData.temp1 }}</text>
<text class="data">温度1</text>
</view>
<view class="text uni-flex_control uni-view">
<text class="data" :style="fontStyle">{{ liveData.temp2 }}</text>
<text class="data">温度2</text>
</view>
<view class="text uni-flex_control uni-view">
<text class="data" :style="fontStyle">{{ liveData.temp3 }}</text>
<text class="data">温度3</text>
</view>
<view class="text uni-flex_control uni-view">
<text class="data" :style="fontStyle">{{ liveData.temp4 }}</text>
<text class="data">温度4</text>
</view>
</view>
<view class="uni-flex_control uni-row" >
<view class="text uni-flex_control uni-view">
<text class="data" :style="fontStyle">{{ liveData.humi1 }}</text>
<text class="data">湿度1</text>
</view>
<view class="text uni-flex_control uni-view">
<text class="data" :style="fontStyle">{{ liveData.humi2 }}</text>
<text class="data">湿度2</text>
</view>
<view class="text uni-flex_control uni-view">
<text class="data" :style="fontStyle">{{ liveData.humi3 }}</text>
<text class="data">湿度3</text>
</view>
<view class="text uni-flex_control uni-view">
<text class="data" :style="fontStyle">{{ liveData.humi4 }}</text>
<text class="data">湿度4</text>
</view>
</view>
</view>
</uni-section>
<uni-section title="设备控制" titleFontSize="16px" type="line" v-if="value!== 1">
<!-- 卷膜/卷被卡片容器2列栅格布局 --> <!-- 卷膜/卷被卡片容器2列栅格布局 -->
<view class="card-grid"> <view class="card-grid">
<!-- 卷被开卡片 --> <!-- 卷被开卡片 -->
@ -107,8 +154,15 @@
<script> <script>
import mqtt from 'mqtt' import mqtt from 'mqtt'
import UniDatetimePicker
from "../../uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue";
import { findDtuDataByInfo } from "@/api/system/data";
export default { export default {
dicts: ['sys_data_map'],
components: {
UniDatetimePicker
},
data() { data() {
return { return {
temp: "", temp: "",
@ -136,6 +190,16 @@ export default {
title:'', title:'',
message: {}, message: {},
connected:false, connected:false,
liveData: {
temp1: '数据加载中...',
temp2: '数据加载中...',
temp3: '数据加载中...',
temp4: '数据加载中...',
humi1: '数据加载中...',
humi2: '数据加载中...',
humi3: '数据加载中...',
humi4: '数据加载中...'
},
// //
show: { show: {
jbk: "暂停", jbk: "暂停",
@ -157,7 +221,8 @@ export default {
jm2g: 0, jm2g: 0,
jm3k: 0, jm3k: 0,
jm3g: 0 jm3g: 0
} },
fontStyle: ''
}; };
}, },
onLoad() { onLoad() {
@ -203,59 +268,87 @@ export default {
if (selectedItem) { if (selectedItem) {
this.selectedText = selectedItem.text; // this.selectedText = selectedItem.text; //
this.title= this.selectedText; this.title= this.selectedText;
var queryParams = {
imei: this.imei
}
findDtuDataByInfo(queryParams).then(response => {
this.liveData = {
temp1: response.data.temp1 || '已离线...',
temp2: response.data.temp2 || '已离线..',
temp3: response.data.temp3 || '已离线..',
temp4: response.data.temp4 || '已离线..',
humi1: response.data.humi1 || '已离线..',
humi2: response.data.humi2 || '已离线..',
humi3: response.data.humi3 || '已离线..',
humi4: response.data.humi4 || '已离线..'
}
this.temp = "最后更新时间:"+response.data.time;
this.fontStyle = 'font-size:16px'
})
} else { } else {
this.selectedText = ''; // this.selectedText = ''; //
this.title=''; this.title='';
this.value=1; this.value=1;
} }
this.reset(); this.reset();
this.style="";
// this.disconnectMqtt(); // this.disconnectMqtt();
}, },
reset() { reset() {
this.show= { this.show = {
jbk: "暂停", jbk: "暂停",
jbg: "暂停", jbg: "暂停",
jm1k: "暂停", jm1k: "暂停",
jm1g: "暂停", jm1g: "暂停",
jm2k: "暂停", jm2k: "暂停",
jm2g: "暂停", jm2g: "暂停",
jm3k: "暂停", jm3k: "暂停",
jm3g: "暂停" jm3g: "暂停"
} }
this.deviceType= ''; this.deviceType = '';
this.status= { this.status = {
jbk: 0, jbk: 0,
jbg: 0, jbg: 0,
jm1k: 0, jm1k: 0,
jm1g: 0, jm1g: 0,
jm2k: 0, jm2k: 0,
jm2g: 0, jm2g: 0,
jm3k: 0, jm3k: 0,
jm3g: 0 jm3g: 0
}; };
this.message={}; this.message = {};
this.temp=''; this.temp = '';
this.liveData = {
temp1: '数据加载中...',
temp2: '数据加载中...',
temp3: '数据加载中...',
temp4: '数据加载中...',
humi1: '数据加载中...',
humi2: '数据加载中...',
humi3: '数据加载中...',
humi4: '数据加载中...'
}
}, },
// //
handleCardClick(status, type) { handleCardClick(status, type) {
// //
// //
const tipMap = { const tipMap = {
'jbk': { opposite: 'jbg', name: '卷被关', op: '卷被开' }, 'jbk': {opposite: 'jbg', name: '卷被关', op: '卷被开'},
'jbg': { opposite: 'jbk', name: '卷被开', op: '卷被关' }, 'jbg': {opposite: 'jbk', name: '卷被开', op: '卷被关'},
'jm1k': { opposite: 'jm1g', name: '卷膜1关', op: '卷膜1开' }, 'jm1k': {opposite: 'jm1g', name: '卷膜1关', op: '卷膜1开'},
'jm1g': { opposite: 'jm1k', name: '卷膜1开', op: '卷膜1关' }, 'jm1g': {opposite: 'jm1k', name: '卷膜1开', op: '卷膜1关'},
'jm2k': { opposite: 'jm2g', name: '卷膜2关', op: '卷膜2开' }, 'jm2k': {opposite: 'jm2g', name: '卷膜2关', op: '卷膜2开'},
'jm2g': { opposite: 'jm2k', name: '卷膜2开', op: '卷膜2关' }, 'jm2g': {opposite: 'jm2k', name: '卷膜2开', op: '卷膜2关'},
'jm3k': { opposite: 'jm3g', name: '卷膜3关', op: '卷膜3开' }, 'jm3k': {opposite: 'jm3g', name: '卷膜3关', op: '卷膜3开'},
'jm3g': { opposite: 'jm3k', name: '卷膜3开', op: '卷膜3关' } 'jm3g': {opposite: 'jm3k', name: '卷膜3开', op: '卷膜3关'}
}; };
// case // case
if (!tipMap[type]) return; if (!tipMap[type]) return;
const { opposite, name, op } = tipMap[type]; const {opposite, name, op} = tipMap[type];
// //
if (status === 1 && this.status[opposite] === 1) { if (status === 1 && this.status[opposite] === 1) {
this.$modal.msgError(`${this.selectedText}${name}在运行状态,不能运行${op}操作!`); this.$modal.msgError(`${this.selectedText}${name}在运行状态,不能运行${op}操作!`);
@ -267,17 +360,17 @@ export default {
// mqtt // mqtt
this.connectMqtt() this.connectMqtt()
} }
if (this.value===1) { if (this.value === 1) {
this.$modal.msgError("设备控制失败!"); this.$modal.msgError("设备控制失败!");
console.info("大棚选取失败!") console.info("大棚选取失败!")
return; return;
} }
uni.showModal({ uni.showModal({
title: '操作提示:', title: '操作提示:',
content: '确定'+(status===1?"运行":"暂停")+'【'+this.selectedText+'】设备?', content: '确定' + (status === 1 ? "运行" : "暂停") + '【' + this.selectedText + '】设备?',
cancelText: '取消', cancelText: '取消',
confirmText: '确定', confirmText: '确定',
success: (res) =>{ success: (res) => {
if (res.confirm) { if (res.confirm) {
// console.info(""+type+""+ status) // console.info(""+type+""+ status)
@ -287,7 +380,7 @@ export default {
// //
this.publishMessage(); this.publishMessage();
// //
this.deviceType=type; this.deviceType = type;
//todo //todo
// this.status[type] = this.status[type] === 0 ? 1 : 0; // this.status[type] = this.status[type] === 0 ? 1 : 0;
@ -314,11 +407,11 @@ export default {
this.client.on('connect', () => { this.client.on('connect', () => {
this.connected = true this.connected = true
this.client.subscribe('dtu/+/up', { qos: 0 }) this.client.subscribe('dtu/+/up', {qos: 0})
this.addMessage('已连接到MQTT服务器') this.addMessage('已连接到MQTT服务器')
}) })
this.client.on("message",this.ackMessage); this.client.on("message", this.ackMessage);
this.client.on('error', (err) => { this.client.on('error', (err) => {
this.addMessage(`控制失败: ${err.message}`) this.addMessage(`控制失败: ${err.message}`)
@ -368,35 +461,35 @@ export default {
ackMessage(topic, payload) { ackMessage(topic, payload) {
// console.info(""+topic,payload) // console.info(""+topic,payload)
// 1. dtu/xxx/up // 1. dtu/xxx/up
if (topic !== this.mqttConfig.subscribeTopic) return; if (topic !== this.mqttConfig.subscribeTopic) return;
// 2. // 2.
let msgData = {}; let msgData = {};
try { try {
msgData = JSON.parse(payload); msgData = JSON.parse(payload);
} catch (e) { } catch (e) {
console.error("消息解析失败:", e); console.error("消息解析失败:", e);
return; return;
} }
// console.info(""+msgData) // console.info(""+msgData)
// 3. // 3.
if (msgData.prop && "suc" in msgData) { if (msgData.prop && "suc" in msgData) {
// console.info("") // console.info("")
// 👉 // 👉
this.handleCommandAck(msgData,this.deviceType); this.handleCommandAck(msgData, this.deviceType);
} else { } else {
this.handleOtherContent(msgData) this.handleOtherContent(msgData,payload)
} }
}, },
addMessage(content) { addMessage(content) {
console.info("提示消息:"+content) console.info("提示消息:" + content)
}, },
// //
handleCommandAck(ackData,type) { handleCommandAck(ackData, type) {
// console.info("11111"+ackData) // console.info("11111"+ackData)
// jm2ksuc // jm2ksuc
const commandField = Object.keys(ackData.prop)[0]; // "jm2k" const commandField = Object.keys(ackData.prop)[0]; // "jm2k"
@ -407,7 +500,7 @@ export default {
this.status[type] = this.status[type] === 0 ? 1 : 0; this.status[type] = this.status[type] === 0 ? 1 : 0;
this.show[type] = this.status[type] === 0 ? "运行" : "暂停"; this.show[type] = this.status[type] === 0 ? "运行" : "暂停";
} }
this.deviceType=''; this.deviceType = '';
this.$modal.msgSuccess("设备操作成功!") this.$modal.msgSuccess("设备操作成功!")
// / // /
@ -416,42 +509,64 @@ export default {
}, },
// //
handleOtherContent(msgData) { handleOtherContent(msgData,payload) {
// //
// console.log("", msgData); // 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"]
const allKeysNumeric = Object.keys(msgData).some(key => arr.includes(key)); const allKeysNumeric = Object.keys(msgData).some(key => arr.includes(key));
if (allKeysNumeric) { if (allKeysNumeric) {
// console.info(msgData) // console.info(msgData)
this.status={...msgData} this.status = {...msgData}
// console.info("imei: "+this.publishTopic+"copy: ",this.status) // console.info("imei: "+this.publishTopic+"copy: ",this.status)
} }
//
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) {
var avgResult = this.calculateAvg(msgData).averageFixed;
this.temp = "当前棚内平均温度:"+avgResult+"℃"
}
}
// 101~104湿201~204/10湿/10
const div10 = (v) => (v == null ? null : Math.round((Number(v)/10)*10)/10)
this.liveData = {
temp1: msgData["201"]==null ? "已离线...":div10(msgData["201"])+"℃",
humi1: msgData["101"]==null ? "已离线...":div10(msgData["101"])+"%RH",
temp2: msgData["202"]==null ? "已离线...":div10(msgData["202"])+"℃",
humi2: msgData["102"]==null ? "已离线...":div10(msgData["102"])+"%RH",
temp3: msgData["203"]==null ? "已离线...":div10(msgData["203"])+"℃",
humi3: msgData["103"]==null ? "已离线...":div10(msgData["103"])+"%RH",
temp4: msgData["204"]==null ? "已离线...":div10(msgData["204"])+"℃",
humi4: msgData["104"]==null ? "已离线...":div10(msgData["104"])+"%RH"
}
//
this.temp = "最新更新时间:" + this.getCurrentTime();
this.fontStyle = 'font-size:16px'
}
}
},
/**
* 获取格式化后的当前时间
* @returns {string} 格式为 YYYY-MM-DD HH:mm:ss 的当前时间
*/
getCurrentTime() {
const now = new Date();
//
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // 0 +1
const day = String(now.getDate()).padStart(2, '0');
//
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
//
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}, },
calculateAvg(dataObj) {
const targetKeys = ['201', '202', '203', '204'];
const values = targetKeys.map(key => dataObj[key]).filter(val => typeof val === 'number' && !isNaN(val));
if (values.length === 0) return { average: 0, averageFixed: '0.00' };
const sum = values.reduce((total, num) => total + num, 0);
const avg = sum/10 / values.length;
return {
average: avg,
averageFixed: avg.toFixed(2)
};
}
}, },
onHide() { onHide() {
this.disconnectMqtt(); this.disconnectMqtt();
@ -541,4 +656,45 @@ export default {
.uni-pb-5 { .uni-pb-5 {
padding-bottom: 40rpx; padding-bottom: 40rpx;
} }
.text {
width: 50rpx;
margin: 10rpx 10rpx 8rpx 0;
padding: 0;
height: 70rpx;
line-height: 70rpx;
text-align: center;
background:#fff;
font-size: 26rpx;
box-shadow: 0 2rpx 8rpx #bfbec1
}
.text:first-child {
margin-left: 10rpx;
}
.uni-view {
-webkit-flex: 1;
flex: 1;
height: 150rpx;
-webkit-justify-content: center;
justify-content: center;
-webkit-align-items: center;
align-items: center;
}
.data {
font-size: 13px;
display: inline-block;
line-height: 23px;
color: #3a3a3a;
}
.data:nth-child(even) {
font-size: 12px;
margin-top: 3px;
color: #9c9c9c;
}
/deep/ .uni-section-header__slot-right {
color: green;
}
</style> </style>

50
store/modules/dict.js Normal file
View File

@ -0,0 +1,50 @@
const state = {
dict: new Array()
}
const mutations = {
SET_DICT: (state, { key, value }) => {
if (key !== null && key !== "") {
state.dict.push({
key: key,
value: value
})
}
},
REMOVE_DICT: (state, key) => {
try {
for (let i = 0; i < state.dict.length; i++) {
if (state.dict[i].key == key) {
state.dict.splice(i, 1)
return true
}
}
} catch (e) {
}
},
CLEAN_DICT: (state) => {
state.dict = new Array()
}
}
const actions = {
// 设置字典
setDict({ commit }, data) {
commit('SET_DICT', data)
},
// 删除字典
removeDict({ commit }, key) {
commit('REMOVE_DICT', key)
},
// 清空字典
cleanDict({ commit }) {
commit('CLEAN_DICT')
}
}
export default {
namespaced: true,
state,
mutations,
actions
}

228
utils/agri.js Normal file
View File

@ -0,0 +1,228 @@
/**
* 通用js方法封装处理
* Copyright (c) 2019 agri
*/
// 日期格式化
export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) {
return null
}
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
time = parseInt(time)
} else if (typeof time === 'string') {
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '')
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
// 表单重置
export function resetForm(refName) {
if (this.$refs[refName]) {
this.$refs[refName].resetFields()
}
}
// 添加日期范围
export function addDateRange(params, dateRange, propName) {
let search = params
search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}
dateRange = Array.isArray(dateRange) ? dateRange : []
if (typeof (propName) === 'undefined') {
search.params['beginTime'] = dateRange[0]
search.params['endTime'] = dateRange[1]
} else {
search.params['begin' + propName] = dateRange[0]
search.params['end' + propName] = dateRange[1]
}
return search
}
// 回显数据字典
export function selectDictLabel(datas, value) {
if (value === undefined) {
return ""
}
var actions = []
Object.keys(datas).some((key) => {
if (datas[key].value == ('' + value)) {
actions.push(datas[key].label)
return true
}
})
if (actions.length === 0) {
actions.push(value)
}
return actions.join('')
}
// 回显数据字典(字符串、数组)
export function selectDictLabels(datas, value, separator) {
if (value === undefined || value.length ===0) {
return ""
}
if (Array.isArray(value)) {
value = value.join(",")
}
var actions = []
var currentSeparator = undefined === separator ? "," : separator
var temp = value.split(currentSeparator)
Object.keys(value.split(currentSeparator)).some((val) => {
var match = false
Object.keys(datas).some((key) => {
if (datas[key].value == ('' + temp[val])) {
actions.push(datas[key].label + currentSeparator)
match = true
}
})
if (!match) {
actions.push(temp[val] + currentSeparator)
}
})
return actions.join('').substring(0, actions.join('').length - 1)
}
// 字符串格式化(%s )
export function sprintf(str) {
var args = arguments, flag = true, i = 1
str = str.replace(/%s/g, function () {
var arg = args[i++]
if (typeof arg === 'undefined') {
flag = false
return ''
}
return arg
})
return flag ? str : ''
}
// 转换字符串undefined,null等转化为""
export function parseStrEmpty(str) {
if (!str || str == "undefined" || str == "null") {
return ""
}
return str
}
// 数据合并
export function mergeRecursive(source, target) {
for (var p in target) {
try {
if (target[p].constructor == Object) {
source[p] = mergeRecursive(source[p], target[p])
} else {
source[p] = target[p]
}
} catch (e) {
source[p] = target[p]
}
}
return source
}
/**
* 构造树型结构数据
* @param {*} data 数据源
* @param {*} id id字段 默认 'id'
* @param {*} parentId 父节点字段 默认 'parentId'
* @param {*} children 孩子节点字段 默认 'children'
*/
export function handleTree(data, id, parentId, children) {
let config = {
id: id || 'id',
parentId: parentId || 'parentId',
childrenList: children || 'children'
}
var childrenListMap = {}
var tree = []
for (let d of data) {
let id = d[config.id]
childrenListMap[id] = d
if (!d[config.childrenList]) {
d[config.childrenList] = []
}
}
for (let d of data) {
let parentId = d[config.parentId]
let parentObj = childrenListMap[parentId]
if (!parentObj) {
tree.push(d)
} else {
parentObj[config.childrenList].push(d)
}
}
return tree
}
/**
* 参数处理
* @param {*} params 参数
*/
export function tansParams(params) {
let result = ''
for (const propName of Object.keys(params)) {
const value = params[propName]
var part = encodeURIComponent(propName) + "="
if (value !== null && value !== "" && typeof (value) !== "undefined") {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
let params = propName + '[' + key + ']'
var subPart = encodeURIComponent(params) + "="
result += subPart + encodeURIComponent(value[key]) + "&"
}
}
} else {
result += part + encodeURIComponent(value) + "&"
}
}
}
return result
}
// 返回项目路径
export function getNormalPath(p) {
if (p.length === 0 || !p || p == 'undefined') {
return p
}
let res = p.replace('//', '/')
if (res[res.length - 1] === '/') {
return res.slice(0, res.length - 1)
}
return res
}
// 验证是否为blob格式
export function blobValidate(data) {
return data.type !== 'application/json'
}

82
utils/dict/Dict.js Normal file
View File

@ -0,0 +1,82 @@
import Vue from 'vue'
import { mergeRecursive } from "@/utils/agri"
import DictMeta from './DictMeta'
import DictData from './DictData'
const DEFAULT_DICT_OPTIONS = {
types: [],
}
/**
* @classdesc 字典
* @property {Object} label 标签对象内部属性名为字典类型名称
* @property {Object} dict 字段数组内部属性名为字典类型名称
* @property {Array.<DictMeta>} _dictMetas 字典元数据数组
*/
export default class Dict {
constructor() {
this.owner = null
this.label = {}
this.type = {}
}
init(options) {
if (options instanceof Array) {
options = { types: options }
}
const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options)
if (opts.types === undefined) {
throw new Error('need dict types')
}
const ps = []
this._dictMetas = opts.types.map(t => DictMeta.parse(t))
this._dictMetas.forEach(dictMeta => {
const type = dictMeta.type
Vue.set(this.label, type, {})
Vue.set(this.type, type, [])
if (dictMeta.lazy) {
return
}
ps.push(loadDict(this, dictMeta))
})
return Promise.all(ps)
}
/**
* 重新加载字典
* @param {String} type 字典类型
*/
reloadDict(type) {
const dictMeta = this._dictMetas.find(e => e.type === type)
if (dictMeta === undefined) {
return Promise.reject(`the dict meta of ${type} was not found`)
}
return loadDict(this, dictMeta)
}
}
/**
* 加载字典
* @param {Dict} dict 字典
* @param {DictMeta} dictMeta 字典元数据
* @returns {Promise}
*/
function loadDict(dict, dictMeta) {
return dictMeta.request(dictMeta)
.then(response => {
const type = dictMeta.type
let dicts = dictMeta.responseConverter(response, dictMeta)
if (!(dicts instanceof Array)) {
console.error('the return of responseConverter must be Array.<DictData>')
dicts = []
} else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) {
console.error('the type of elements in dicts must be DictData')
dicts = []
}
dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts)
dicts.forEach(d => {
Vue.set(dict.label[type], d.value, d.label)
})
return dicts
})
}

View File

@ -0,0 +1,17 @@
import DictOptions from './DictOptions'
import DictData from './DictData'
export default function(dict, dictMeta) {
const label = determineDictField(dict, dictMeta.labelField, ...DictOptions.DEFAULT_LABEL_FIELDS)
const value = determineDictField(dict, dictMeta.valueField, ...DictOptions.DEFAULT_VALUE_FIELDS)
return new DictData(dict[label], dict[value], dict)
}
/**
* 确定字典字段
* @param {DictData} dict
* @param {...String} fields
*/
function determineDictField(dict, ...fields) {
return fields.find(f => Object.prototype.hasOwnProperty.call(dict, f))
}

13
utils/dict/DictData.js Normal file
View File

@ -0,0 +1,13 @@
/**
* @classdesc 字典数据
* @property {String} label 标签
* @property {*} value 标签
* @property {Object} raw 原始数据
*/
export default class DictData {
constructor(label, value, raw) {
this.label = label
this.value = value
this.raw = raw
}
}

38
utils/dict/DictMeta.js Normal file
View File

@ -0,0 +1,38 @@
import { mergeRecursive } from "@/utils/agri"
import DictOptions from './DictOptions'
/**
* @classdesc 字典元数据
* @property {String} type 类型
* @property {Function} request 请求
* @property {String} label 标签字段
* @property {String} value 值字段
*/
export default class DictMeta {
constructor(options) {
this.type = options.type
this.request = options.request
this.responseConverter = options.responseConverter
this.labelField = options.labelField
this.valueField = options.valueField
this.lazy = options.lazy === true
}
}
/**
* 解析字典元数据
* @param {Object} options
* @returns {DictMeta}
*/
DictMeta.parse= function(options) {
let opts = null
if (typeof options === 'string') {
opts = DictOptions.metas[options] || {}
opts.type = options
} else if (typeof options === 'object') {
opts = options
}
opts = mergeRecursive(DictOptions.metas['*'], opts)
return new DictMeta(opts)
}

51
utils/dict/DictOptions.js Normal file
View File

@ -0,0 +1,51 @@
import { mergeRecursive } from "@/utils/agri"
import dictConverter from './DictConverter'
export const options = {
metas: {
'*': {
/**
* 字典请求方法签名为function(dictMeta: DictMeta): Promise
*/
request: (dictMeta) => {
console.log(`load dict ${dictMeta.type}`)
return Promise.resolve([])
},
/**
* 字典响应数据转换器方法签名为function(response: Object, dictMeta: DictMeta): DictData
*/
responseConverter,
labelField: 'label',
valueField: 'value',
},
},
/**
* 默认标签字段
*/
DEFAULT_LABEL_FIELDS: ['label', 'name', 'title'],
/**
* 默认值字段
*/
DEFAULT_VALUE_FIELDS: ['value', 'id', 'uid', 'key'],
}
/**
* 映射字典
* @param {Object} response 字典数据
* @param {DictMeta} dictMeta 字典元数据
* @returns {DictData}
*/
function responseConverter(response, dictMeta) {
const dicts = response.content instanceof Array ? response.content : response
if (dicts === undefined) {
console.warn(`no dict data of "${dictMeta.type}" found in the response`)
return []
}
return dicts.map(d => dictConverter(d, dictMeta))
}
export function mergeOptions(src) {
mergeRecursive(options, src)
}
export default options

33
utils/dict/index.js Normal file
View File

@ -0,0 +1,33 @@
import Dict from './Dict'
import { mergeOptions } from './DictOptions'
export default function(Vue, options) {
mergeOptions(options)
Vue.mixin({
data() {
if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) {
return {}
}
const dict = new Dict()
dict.owner = this
return {
dict
}
},
created() {
if (!(this.dict instanceof Dict)) {
return
}
options.onCreated && options.onCreated(this.dict)
this.dict.init(this.$options.dicts).then(() => {
options.onReady && options.onReady(this.dict)
this.$nextTick(() => {
this.$emit('dictReady', this.dict)
if (this.$options.methods && this.$options.methods.onDictReady instanceof Function) {
this.$options.methods.onDictReady.call(this, this.dict)
}
})
})
},
})
}

1457
utils/uni.css Normal file

File diff suppressed because it is too large Load Diff