This commit is contained in:
2025-01-07 21:30:10 +08:00
commit 10caf6b7fd
26 changed files with 13679 additions and 0 deletions

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2025 monjack
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

24
README.md Normal file
View File

@ -0,0 +1,24 @@
# imas
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
babel.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

19
jsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

12412
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

58
package.json Normal file
View File

@ -0,0 +1,58 @@
{
"name": "imas",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^1.7.9",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3",
"bootstrap-timepicker": "^0.5.2",
"bootstrap-vue": "^2.23.1",
"core-js": "^3.8.3",
"flatpickr": "^4.6.13",
"lodash": "^4.17.21",
"pinia": "^2.3.0",
"toastify-js": "^1.12.0",
"vue": "^3.2.13",
"vue-json-viewer": "^3.0.4",
"vue-router": "^4.5.0",
"vue3-timepicker": "^1.0.0-beta.2"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@popperjs/core": "^2.11.8",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"bootstrap": "^5.3.3",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"jquery": "^3.7.1"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

17
public/index.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

105
src/App.vue Normal file
View File

@ -0,0 +1,105 @@
<template>
<div class="Main-title h1 mt-0 ms-2 mb-3 ps-3 border-start border-5 border-dark-subtle" @click="clickTitle" >控制中心</div>
<router-view></router-view>
<footer class="mt-5 text-center border-top border-1">Copyright © 2024-2025 <a href="https://blog.monjack.cn" class="btn-link" style="text-decoration: none">王仁杰</a></footer>
</template>
<script>
export default {
name: 'App',
components: {
},
methods: {
clickTitle() {
this.$router.push('/HomeView');
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&family=ZCOOL+KuaiLe&display=swap');
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 20px;
}
.roboto-thin {
font-family: "Roboto", serif;
font-weight: 100;
font-style: normal;
}
.roboto-light {
font-family: "Roboto", serif;
font-weight: 300;
font-style: normal;
}
.roboto-regular {
font-family: "Roboto", serif;
font-weight: 400;
font-style: normal;
}
.roboto-medium {
font-family: "Roboto", serif;
font-weight: 500;
font-style: normal;
}
.roboto-bold {
font-family: "Roboto", serif;
font-weight: 700;
font-style: normal;
}
.roboto-black {
font-family: "Roboto", serif;
font-weight: 900;
font-style: normal;
}
.roboto-thin-italic {
font-family: "Roboto", serif;
font-weight: 100;
font-style: italic;
}
.roboto-light-italic {
font-family: "Roboto", serif;
font-weight: 300;
font-style: italic;
}
.roboto-regular-italic {
font-family: "Roboto", serif;
font-weight: 400;
font-style: italic;
}
.roboto-medium-italic {
font-family: "Roboto", serif;
font-weight: 500;
font-style: italic;
}
.roboto-bold-italic {
font-family: "Roboto", serif;
font-weight: 700;
font-style: italic;
}
.roboto-black-italic {
font-family: "Roboto", serif;
font-weight: 900;
font-style: italic;
}
.Main-title:hover {
color: #007bff;
cursor: pointer;
}
</style>

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

View File

@ -0,0 +1,59 @@
<script>
import {getWeather} from '@/services/weatherService'
import TitleBlock from "@/components/TitleBlock.vue";
export default {
name: "APIView",
components: {
TitleBlock
},
data(){
return(
{
res: '',
weather: '',
temperature: '',
winddirection: '',
windpower: '',
reporttime: ''
}
)
},
async created() {
try {
const weatherData = await getWeather();
this.res = weatherData;
this.weather = weatherData.weather;
this.winddirection = weatherData.winddirection;
this.windpower = weatherData.windpower;
this.temperature = weatherData.temperature;
this.reporttime = weatherData.reporttime;
} catch (error) {
console.error('获取天气数据失败:', error);
}
}
}
</script>
<template>
<TitleBlock :title="'查看API数据'" />
<div class="ms-4 p-2 border rounded-3">
<div>天气 : {{ weather }}</div>
<div>温度 : {{temperature}}</div>
<div>风向 : {{winddirection}}</div>
<div>风力 : {{windpower}}</div>
<div>更新时间 : {{reporttime}}</div>
</div>
<div class="ms-4 mt-3 p-2 border rounded-3">
<json-viewer
:value="res"
:expand-depth=5
copyable
boxed
sort></json-viewer>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,37 @@
<script>
import Toastify from 'toastify-js';
export default {
name: "ButtonArea",
methods: {
showToast() {
Toastify({
text: "请以管理员身份登录",
duration: 3000,
close: true
}).showToast();
}
}
}
</script>
<template>
<div class="mt-4 text-center">
<router-link to="/APIView">
<button class="btn btn-secondary w-25 shadow-sm m-2 fs-4"><a>查看API</a></button>
</router-link>
<router-link to="/SettingsView">
<button class="btn btn-secondary w-25 shadow-sm m-2 fs-4"><a>设置</a></button>
</router-link>
<router-link to="/DebugView">
<button class="btn btn-secondary w-25 shadow-sm m-2 fs-4">调试</button>
</router-link>
</div>
</template>
<style scoped>
template{
display: flex;
}
</style>

View File

@ -0,0 +1,198 @@
<script>
import TitleBlock from "@/components/TitleBlock.vue";
import $ from "jquery";
export default {
name: "DebugView",
components: {TitleBlock},
created() {
this.getStatusData();
},
data() {
return {
Label_button1: "待机",
Label_button2: "重启",
Label_button3: "启动加湿",
Label_button4: "关闭屏幕",
button1: false,
button2: false,
button3: false,
button4: false,
data: {
standby: 0,
reboot: 0,
humidifier: 0,
screen: 1,
}
};
},
methods: {
button1_click() {
console.log("待机");
this.button1 = true;
setTimeout(() => {
this.button1 = false;
}, 5000);
if (this.Label_button1 === "待机") {
this.Label_button1 = "工作";
this.button2 = true;
this.button3 = true;
this.button4 = true;
this.data.standby = 1;
} else {
this.Label_button1 = "待机";
this.button2 = false;
this.button3 = false;
this.button4 = false;
this.data.standby = 0;
}
this.updateData();
},
button2_click() {
console.log("重启");
this.button1 = true;
this.button2 = true;
this.button3 = true;
this.button4 = true;
setTimeout(() => {
this.button1 = false;
this.button2 = false;
this.button3 = false;
this.button4 = false;
}, 5000);
this.data.reboot = 1;
this.updateData();
this.data.reboot = 0;
},
button3_click() {
console.log("启动加湿");
this.button3 = true;
setTimeout(() => {
this.button3 = false;
}, 1000);
if (this.Label_button3 === "启动加湿") {
this.Label_button3 = "停止加湿";
this.data.humidifier = 1;
} else {
this.Label_button3 = "启动加湿";
this.data.humidifier = 0;
}
this.updateData();
},
button4_click() {
console.log("关闭屏幕");
this.button4 = true;
setTimeout(() => {
this.button4 = false;
}, 1000);
if (this.Label_button4 === "关闭屏幕") {
this.Label_button4 = "打开屏幕";
this.data.screen = 0;
} else {
this.Label_button4 = "关闭屏幕";
this.data.screen = 1;
}
this.updateData();
},
updateData() {
$.ajax({
url: "http://localhost/debug.php",
type: "POST",
contentType: "application/json",
data: JSON.stringify(this.data),
success: (response) => {
console.log(response);
},
error: (xhr, status, error) => {
console.log(error);
console.log(xhr.responseText);
}
}
)
},
getStatusData(){
$.ajax({
url: "http://localhost/debug.php",
type: "GET",
contentType: "application/json",
success: (response) => {
console.log(response);
this.data.standby = parseInt(JSON.parse(response).standby);
this.data.reboot = parseInt(JSON.parse(response).reboot);
this.data.humidifier = parseInt(JSON.parse(response).humidifier);
this.data.screen = parseInt(JSON.parse(response).screen);
if (this.data.standby) {
this.Label_button1 = "工作";
} else {
this.Label_button1 = "待机";
}
if (this.data.reboot) {
this.Label_button2 = "重启中";
} else {
this.Label_button2 = "重启";
}
if (this.data.humidifier) {
this.Label_button3 = "停止加湿";
} else {
this.Label_button3 = "启动加湿";
}
if (this.data.screen) {
this.Label_button4 = "关闭屏幕";
} else {
this.Label_button4 = "打开屏幕";
}
},
error: (xhr, status, error) => {
console.log(error);
console.log(xhr.responseText);
}
}
)
}
},
computed: {
}
}
</script>
<template>
<TitleBlock class="mb-4" title="调试" /><br>
<div class="text-center me-1 pe-5 ps-5">
<table class="table table-sm w-25">
<thead>
<tr>
<th>功能</th>
<th>状态</th>
</tr>
</thead>
<tbody class="table-group-divider">
<tr>
<td>在线状态</td>
<td>{{ data.standby? "离线" : data.reboot? "重启中": "在线" }}</td>
</tr>
<tr>
<td>启动加湿</td>
<td>{{ data.humidifier? "启动中" : "已停止" }}</td>
</tr>
<tr>
<td>关闭屏幕</td>
<td>{{ data.screen? "打开" : "关闭" }}</td>
</tr>
</tbody>
</table>
</div>
<div class="ms-5 mt-4 btn-group">
<button class="btn btn-outline-secondary" v-bind:disabled="button1" @click="button1_click" hidden>{{ Label_button1 }}</button>
<button class="btn btn-outline-secondary" v-bind:disabled="button2" @click="button2_click">{{ Label_button2 }}</button>
<button class="btn btn-outline-secondary" v-bind:disabled="button3" @click="button3_click">{{ Label_button3 }}</button>
<button class="btn btn-outline-secondary" v-bind:disabled="button4" @click="button4_click">{{ Label_button4 }}</button>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,20 @@
<script>
import ButtonArea from "@/components/ButtonArea.vue";
import WeatherCard from "@/components/WeatherCard.vue";
import RoomCard from "@/components/RoomCard.vue";
export default {
name: "HomeView",
components: {RoomCard, WeatherCard, ButtonArea}
}
</script>
<template>
<WeatherCard style=""></WeatherCard>
<RoomCard></RoomCard>
<ButtonArea></ButtonArea>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,68 @@
<script>
import {getLocalData} from "@/services/weatherService";
export default {
name: "RoomCard",
data() {
return {
RoomTemp: 20,
RoomHumidity: 40,
LastUpdateTime: "未获取",
RoomNumber: "101",
}
},
methods: {
TempMap(temp) {
if (temp < 18) {
return "冷";
} else if (temp < 25) {
return "适中";
} else if (temp >34) {
return "热";
} else{
return "暖";
}
},
HumidityMap(humidity) {
if (humidity < 30) {
return "较干燥";
} else if (humidity < 60) {
return "正常";
} else {
return "较湿";
}
},
},
async created() {
const localData = await getLocalData();
console.log(localData);
this.RoomTemp = localData[0].temperature;
console.log(this.RoomTemp);
this.RoomHumidity = localData[0].humidity;
console.log(this.RoomHumidity);
this.LastUpdateTime = localData[0].time;
},
}
</script>
<template>
<div class="border border-0 border-dark-subtle m-3 p-4 rounded-4 shadow">
<div class="h1 ms-2 mb-2 fw-semibold roboto-bold">房间</div>
<div class="fs-3 ms-3 text-black-75">
<div class="fs-2 mt-3 mb-2 border-bottom border-2">
<span class=""></span>
</div>
<i class="bi bi-thermometer-half"></i>
{{ TempMap(RoomTemp) }} {{ RoomTemp }}
<div class="mt-4 ms-1 fs-4">
<i class="bi bi-moisture"></i> <span class="ms-1 me-4">{{HumidityMap(RoomHumidity)}}</span><span>{{ RoomHumidity }}%</span>
</div>
</div>
<div class="text-end fs-6 fw-light mt-3"><span class="me-4">更新时间</span><span>{{ LastUpdateTime }}</span></div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,155 @@
<script>
import TitleBlock from "@/components/TitleBlock.vue";
import TimePicker from "@/components/TimePicker.vue";
import ThresholdPicker from "@/components/ThresholdPicker.vue";
import axios from "axios";
export default {
name: "SettingsView",
components: {TimePicker, TitleBlock, ThresholdPicker},
created() {
this.getSettings();
},
data() {
return {
timeSettings:{
times_Power: {
times:{
PowerOn: "12:00",
PowerOff: "12:30"
},
enable_boot: false,
},
times_Humidity: {
times:{
HumidityOn: "12:00",
HumidityOff: "12:30",
},
enable_boot: false,
},
},
thresholdSettings:{
Threshold_Humidity:{
thresholds: {
min: 20,
max: 80,
},
enable_boot: false,
},
Threshold_Temperature:{
thresholds: {
min: 20,
max: 26,
},
enable_boot: false,
},
},
loaded: true
}
},
methods: {
save() {
this.postSettings();
},
cancel() {
window.location.reload();
},
getSettings() {
axios.get("http://locahost/getSetting.php").then(response => {
console.log(response.data);
this.timeSettings.times_Power = {
times: {
PowerOn: response.data[0].boot,
PowerOff: response.data[0].shutdown,
},
enable_boot: response.data[0].enable_boot == 1,
}
console.log(this.timeSettings.times_Power);
this.timeSettings.times_Humidity = {
times: {
HumidityOn: response.data[0].start,
HumidityOff: response.data[0].end,
},
enable_boot: response.data[0].enable_humidify == 1,
}
this.thresholdSettings.Threshold_Humidity = {
thresholds: {
min: response.data[0].min_humidity,
max: response.data[0].max_humidity,
},
enable_boot: response.data[0].enable_humi_threshold == 1,
}
this.thresholdSettings.Threshold_Temperature = {
thresholds: {
min: response.data[0].min_temperature,
max: response.data[0].max_temperature,
},
enable_boot: response.data[0].enable_temp_threshold == 1,
}
this.loaded = true;
}).catch(error => {
console.log(error);
});
},
postSettings() {
axios.post("http://locahost/postSetting.php", {
boot: this.timeSettings.times_Power.times.PowerOn,
shutdown: this.timeSettings.times_Power.times.PowerOff,
start: this.timeSettings.times_Humidity.times.HumidityOn,
end: this.timeSettings.times_Humidity.times.HumidityOff,
min_humidity: this.thresholdSettings.Threshold_Humidity.thresholds.min,
max_humidity: this.thresholdSettings.Threshold_Humidity.thresholds.max,
min_temperature: this.thresholdSettings.Threshold_Temperature.thresholds.min,
max_temperature: this.thresholdSettings.Threshold_Temperature.thresholds.max,
enable_boot: this.timeSettings.times_Power.enable_boot? 1 : 0,
enable_humidify: this.timeSettings.times_Humidity.enable_boot? 1 : 0,
enable_humi_threshold: this.thresholdSettings.Threshold_Humidity.enable_boot? 1 : 0,
enable_temp_threshold: this.thresholdSettings.Threshold_Temperature.enable_boot? 1 : 0,
}).then(response => {
console.log(response);
this.loaded = false;
}).catch(error => {
console.log(error);
this.loaded = true;
});
}
}
}
</script>
<template>
<div v-if="loaded">
<TitleBlock title="设置" />
<span hidden>{{timeSettings.times_Power.times.PowerOn}}</span>
<span hidden>{{timeSettings.times_Power.times.PowerOff}}</span>
<span hidden>{{timeSettings.times_Humidity.times.HumidityOn}}</span>
<span hidden>{{timeSettings.times_Humidity.times.HumidityOff}}</span>
<span hidden>{{thresholdSettings.Threshold_Humidity.thresholds.min}}</span>
<span hidden>{{thresholdSettings.Threshold_Humidity.thresholds.max}}</span>
<span hidden>{{thresholdSettings.Threshold_Temperature.thresholds.min}}</span>
<span hidden>{{thresholdSettings.Threshold_Temperature.thresholds.max}}</span>
<div class="ms-5 mt-2 fs-5 ">
<TimePicker :pickers="timeSettings.times_Power.times" v-model="timeSettings.times_Power" :title="'定时开关机'" :enable="timeSettings.times_Power.enable_boot" />
</div>
<div class="ms-5 mt-2 fs-5 ">
<TimePicker v-model="timeSettings.times_Humidity" :title="'定时加湿'" :enable="timeSettings.times_Humidity.enable_boot" :pickers=timeSettings.times_Humidity.times />
</div>
<div class="ms-5 mt-2 fs-5 ">
<ThresholdPicker v-model="thresholdSettings.Threshold_Temperature" :title="'温度阈值'" :enable="thresholdSettings.Threshold_Temperature.enable_boot" :Thresholds=thresholdSettings.Threshold_Temperature.thresholds />
</div>
<div class="ms-5 mt-2 fs-5 ">
<ThresholdPicker v-model="thresholdSettings.Threshold_Humidity" :title="'湿度阈值'" :enable="thresholdSettings.Threshold_Humidity.enable_boot" :Thresholds=thresholdSettings.Threshold_Humidity.thresholds />
</div>
<div class="mt-5">
<button class="btn btn-secondary ms-5 mt-2 fs-5" @click="save">保存</button>
<button class="btn btn-secondary ms-5 mt-2 fs-5" @click="cancel">重置</button>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,85 @@
<script>
export default {
name: "ThresholdPicker",
components: {},
mounted() {
this.LocalThresholds = this.Thresholds
this.showThresholdPicker = this.enable
},
methods: {
updateThresholds() {
this.$emit("update:Thresholds", this.LocalThresholds)
}
},
data() {
return {
showThresholdPicker: false,
LocalThresholds: {
min: "",
max: "",
},
unit: "%"
}
},
props: {
title: {
type: String,
default: ""
},
Thresholds: {
type: Object,
default: () => ({
min: "",
max: "",
})
},
enable: {
type: Boolean,
default: true
}
}
}
</script>
<template>
<div class="d-flex align-items-center flex-wrap">
<div class="form-check form-switch">
<input
type="checkbox"
class="form-check-input"
role="switch"
id="showThresholdPicker"
v-model="showThresholdPicker"
/>
<label class="form-check-label me-4" for="showThresholdPicker">{{ title }}</label>
</div>
<div v-if="showThresholdPicker">
<input
v-for="(threshold, index) in LocalThresholds"
:key="index"
:placeholder="index ==='min'? '输入最小阈值' : '输入最大阈值'"
@input="updateThresholds"
v-model="LocalThresholds[index]"
type="text"
class="picker-input fs-5" />
</div>
</div>
</template>
<style scoped>
.picker-input {
width: 200px;
margin-left: 9px;
padding: 10px 10px 10px;
font-size: 14px;
border: 1px solid #ccc;
outline: none;
}
.picker-input:focus {
border: 2px solid #101010;
border-radius: 5px;
}
</style>

View File

@ -0,0 +1,72 @@
<template>
<div class="d-flex align-items-center flex-wrap ">
<!-- Checkbox to toggle timepicker visibility -->
<div class="form-check form-switch">
<input
type="checkbox"
class="form-check-input"
role="switch"
id="showTimepicker"
v-model="showTimepicker"
/>
<label class="form-check-label me-4" for="showTimepicker">{{ title }}</label>
</div>
<div v-if="showTimepicker">
<VueTimepicker
v-for="(picker, index) in pickers"
:key="index"
:picker="picker"
class="mt-2 ms-2"
:placeholder="index"
v-model="time[index]"
@change="updateTime(index)"
>
<template v-slot:icon>
<i class="bi bi-clock"></i>
</template>
</VueTimepicker>
</div>
</div>
</template>
<script>
import VueTimepicker from "vue3-timepicker";
export default {
name: "TimePicker",
props: {
title: String,
pickers: {
type: Object,
required: true,
default: () => ({
time: "00:00",
}),
},
enable: {
type: Boolean,
default: true,
},
},
data() {
return {
showTimepicker: false,
time: {}, // 本地时间数据
};
},
components: {
VueTimepicker,
},
mounted() {
this.showTimepicker = this.enable;
this.time = { ...this.pickers }; // 初始化 time 数据
},
methods: {
updateTime() {
this.$emit("update:pickers", Object.assign({}, this.time, this.showTimepicker)); // 确保事件名称和父组件一致
},
},
};
</script>
<style>
</style>

View File

@ -0,0 +1,24 @@
<script>
export default {
name: "TitleBlock",
props: {
title: {
type: String,
required: true
}
}
}
</script>
<template>
<div class="title ms-4 ps-1 pt-2 pe-1 mb-3 mt-1 border-bottom border-3 ">
<h2>{{ title }}</h2>
</div>
</template>
<style scoped>
.title {
display: inline-block; /* 使宽度适应内容 */
user-select: none; /* 防止选中文本 */
}
</style>

View File

@ -0,0 +1,152 @@
<script>
import {getWeather, getIPInfo} from "@/services/weatherService";
import TitleBlock from "@/components/TitleBlock.vue";
export default {
name: "WeatherCard",
components: {
TitleBlock
},
data() {
return {
weather: '',
temperature: '',
winddirection: '',
windpower: '',
reporttime: '',
clothingIndex: '',
windspeedMap: {
'0': 0.5,
'1': 0.9,
'2': 2.5,
'3': 4.4,
'4': 6.7,
'5': 9.3,
'6': 12.5,
'7': 15.5,
'8': 19.0,
'9': 22.6,
'10': 26.45,
'11': 30.55,
'12': 32
},
CloudMap: {
'晴': 0,
'阴': 9,
'多云': 6,
'雨': 10,
},
ip: '',
address: '',
}
},
methods: {
GetClothingIndex(temp, windSpeed) {
const tFelt = calculateWindChill(temp, windSpeed);
function calculateWindChill(temp, windSpeed) {
return (
13.12 +
0.6215 * temp -
11.37 * Math.pow(windSpeed, 0.16) +
0.3965 * temp * Math.pow(windSpeed, 0.16)
);
}
if (tFelt > 26) {
return "短袖或薄长袖";
} else if (tFelt >= 20 && tFelt <= 26) {
return "薄外套、长袖";
} else if (tFelt >= 10 && tFelt < 20) {
return "厚外套";
} else if (tFelt >= 0 && tFelt < 10) {
return "棉衣或羽绒服";
} else {
return "加厚羽绒服,注意保暖";
}
}
},
// GetClothingIndex() {
// let Ic, Ts, Tv, Tr, Ta, H, Rat, Ia, V, Nt, Nl;
// Nl = 0;
// Ts = 33;
// V = this.windspeedMap[this.windpower.match(/\d+/g)];
// Nt = this.weather in this.CloudMap? this.CloudMap[this.weather] : 5;
// Ta = 20;
// Ia = 0.40 / Math.pow(V, 0.406);
// Tv = 0.0246 * Math.pow(Math.log10(7.23 * V), 3) - 0.4525 * Math.pow(Math.log10(7.23 * V), 2) + 3.2398 * Math.log10(7.23 * V);
// Tr = 0.42 * (1- 0.45 * (Nt + Nl)) * 0.5 * Ia;
// H = 0.93244 * 4.1841 * (0.104 * Ta*Ta - 5.1403 * Ta + 117.13);
// Ta >= 25?
// Rat = 0.0775 + 0.001 * Ta * (0.1 * Ta * (0.01 * Ta * (1.235 * Ta - 54.8752) + 10.1044) - 3.2813)
// : Rat = 0.24;
// Ic = (Ts - Ta - Tv + Tr)/(H * (1 - Rat) * 0.043) - Ia;
// console.log(Ic);
// return Ic;
//
// }
async created() {
try {
const weatherData = await getWeather();
const ipInfo = await getIPInfo();
this.address = ipInfo.province + ipInfo.city;
this.weather = weatherData.weather;
this.winddirection = weatherData.winddirection;
this.windpower = weatherData.windpower;
this.temperature = weatherData.temperature;
this.reporttime = weatherData.reporttime;
} catch (error) {
console.error('获取天气数据失败:', error);
}
this.clothingIndex = this.GetClothingIndex(this.temperature, this.windspeedMap[this.windpower.match(/\d+/g)]);
},
computed: {
iconClass() {
switch (this.weather) {
case '晴':
return 'bi-sun';
case '阴':
return 'bi-cloud-drizzle';
case '多云':
return 'bi-cloud-fog';
case '雨':
return 'bi-cloud-rain';
case '雪':
return 'bi-cloud-snow';
default:
return 'bi-cloud';
}
}
}
}
</script>
<template>
<TitleBlock title="概览"></TitleBlock>
<div class="border border-0 border-dark-subtle m-3 p-4 rounded-4 shadow">
<div class="h1 ms-2 mb-2 fw-semibold roboto-bold">天气</div>
<div class="fs-3 ms-3 text-black-75">
<div class="fs-2 mt-3 mb-2 border-bottom border-2">
<span class=""></span>
</div>
<div class="fs-4 mb-3 mt-3">{{ address }}</div>
<i :class="iconClass"></i>
{{ weather }} {{ temperature }}
<div class="mt-2 fs-4">
<i class="bi bi-wind"></i> <span class="me-4">{{ winddirection }}</span><span>{{ windpower }}</span>
</div>
<div class="mt-2 fs-4">
<span class="me-4">适宜衣物</span><span>{{ clothingIndex }}</span>
</div>
<div class="text-end fs-6 fw-light mt-3">
点击查看未来天气
</div>
</div>
<div class="text-end fs-6 fw-light mt-1"><span class="me-4">更新时间</span><span>{{reporttime}}</span></div>
</div>
</template>
<style scoped>
</style>

28
src/main.js Normal file
View File

@ -0,0 +1,28 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import 'jquery/dist/jquery.min.js'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.min.js'
import 'bootstrap-icons/font/bootstrap-icons.css'
import "toastify-js/src/toastify.css"
import Toastify from 'toastify-js'
import 'vue3-timepicker/dist/VueTimepicker.css'
import VueTimepicker from 'vue3-timepicker'
import JsonViewer from 'vue-json-viewer'
import router from './router'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App);
app.use(router);
app.use(JsonViewer);
app.use(VueTimepicker);
app.use(Toastify);
app.use(pinia);
app.mount('#app');

52
src/router/index.js Normal file
View File

@ -0,0 +1,52 @@
import {createRouter, createWebHistory} from "vue-router";
import WeatherCard from "@/components/WeatherCard.vue";
import RoomCard from "@/components/RoomCard.vue";
import HomeView from "@/components/HomeView.vue";
import DebugView from "@/components/DebugView.vue";
import APIView from "@/components/APIView.vue";
import SettingsView from "@/components/SettingsView.vue";
const routes = [
{
path: '/WeatherCard',
name: 'WeatherCard',
component: WeatherCard
},
{
path: '/RoomCard',
name: 'RoomCard',
component: RoomCard
},
{
path: '/DebugView',
name: 'DebugView',
component: DebugView
},
{
path: '/HomeView',
name: 'HomeView',
component: HomeView
},
{
path: '/',
redirect: '/HomeView',
},
{
path: '/APIView',
name: 'APIView',
component: APIView
},
{
path: '/SettingsView',
name: 'SettingsView',
component: SettingsView
}
]
const router = createRouter({
history: createWebHistory(),
routes
});
export default router

View File

@ -0,0 +1,35 @@
// weatherService.js
import axios from "axios";
export async function getWeather() {
try {
const response = await axios.get("http://monjack.cn/weatherInfo.php");
const weatherData = response.data.lives[0];
return weatherData;
// 将 API 数据更新到 Pinia Store
} catch (error) {
console.error("Failed to fetch weather data:", error);
throw error; // 确保将错误抛出,以便调用者可以捕获
}
}
export async function getIPInfo() {
try {
const response = await axios.get("https://restapi.amap.com/v3/ip?key=12085a54026b8e80ed3f69ec9c328e3e");
return response.data;
// 将 API 数据更新到 Pinia Store
} catch (error) {
console.error("Failed to fetch IP info:", error);
}
}
export async function getLocalData() {
try {
const response = await axios.get("http://localhost/getLocalData.php");
return response.data;
// 将 API 数据更新到 Pinia Store
} catch (error) {
console.error("Failed to fetch local data:", error);
}
}

22
vue.config.js Normal file
View File

@ -0,0 +1,22 @@
const { defineConfig } = require('@vue/cli-service')
// module.exports = defineConfig({
// transpileDependencies: true
// })
// defineConfig 这里是vue3 的默认代码
const webpack = require("webpack")
module.exports = defineConfig({
// 配置插件参数
configureWebpack: {
plugins: [
// 配置 jQuery 插件的参数
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
Popper: ['popper.js', 'default']
})
]
}
})