18 changed files with 784 additions and 125 deletions
-
1src/assets/styles/variable.scss
-
8src/components/common/BTButton/index.vue
-
290src/components/common/CascadingSelectModal/index.vue
-
4src/components/formula/FormulaConfig.vue
-
1src/components/home/HomeFormula.vue
-
1src/components/home/HomeOperation.vue
-
154src/components/home/HomeSetting.vue
-
123src/components/home/LineChart.vue
-
72src/components/home/chart.vue
-
4src/layouts/default.vue
-
11src/libs/constant.ts
-
2src/router/routes.ts
-
1src/stores/formulaStore.ts
-
26src/stores/homeStore.ts
-
12src/types/home.d.ts
-
1src/views/formula/index.vue
-
190src/views/home/chart.vue
-
8src/views/home/index.vue
@ -0,0 +1,290 @@ |
|||
<script lang="ts" setup> |
|||
import { defineEmits, defineProps, onBeforeMount, ref, toRefs } from 'vue' |
|||
|
|||
const props = defineProps({ |
|||
optionsLeft: { |
|||
type: Array as () => System.Option[], |
|||
required: true, |
|||
}, |
|||
options: { |
|||
type: Array as () => System.Option[], |
|||
required: true, |
|||
}, |
|||
selectedValue: { |
|||
type: [String, Number, Boolean, Object], |
|||
default: null, |
|||
}, |
|||
placeholder: { |
|||
type: String, |
|||
default: '请选择', |
|||
}, |
|||
searchable: { |
|||
type: Boolean, |
|||
default: true, |
|||
}, |
|||
}) |
|||
|
|||
const emits = defineEmits(['confirm', 'cancel']) |
|||
|
|||
const { optionsLeft, options } = toRefs(props) |
|||
|
|||
const tempSelectedLeftValue = ref('positivePressure') |
|||
|
|||
const tempSelectedRightValue = ref() |
|||
|
|||
const tempSelectedValue = ref<string[]>(['positivePressure', '10%']) |
|||
|
|||
const filteredOptionsLeft = ref<System.Option[]>([]) |
|||
|
|||
const filteredOptionsRight = ref<System.Option[]>([]) |
|||
|
|||
onBeforeMount(() => { |
|||
filteredOptionsLeft.value = optionsLeft.value.filter(item => item.value) |
|||
filteredOptionsRight.value = options.value.filter(item => item.value) |
|||
}) |
|||
|
|||
const selectOptionLeft = (option: System.Option) => { |
|||
if (option.value === 'constantPressure') { |
|||
tempSelectedValue.value = [option.value] |
|||
filteredOptionsRight.value = [] |
|||
} |
|||
else { |
|||
tempSelectedValue.value[0] = option.value |
|||
filteredOptionsRight.value = options.value.filter(item => item.value) |
|||
} |
|||
tempSelectedLeftValue.value = option.value |
|||
} |
|||
|
|||
const selectOption = (option: System.Option) => { |
|||
tempSelectedRightValue.value = option.value |
|||
tempSelectedValue.value[1] = option.value |
|||
} |
|||
|
|||
const confirmSelection = () => { |
|||
emits('confirm', tempSelectedValue.value) |
|||
} |
|||
|
|||
const handleCancel = () => { |
|||
emits('cancel') |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="modal-overlay" @click.self="handleCancel"> |
|||
<div class="modal-container"> |
|||
<div> |
|||
<div class="modal-header"> |
|||
<h3>{{ placeholder }}</h3> |
|||
<button class="close-btn" @click="handleCancel"> |
|||
<i class="fa fa-times"></i> |
|||
</button> |
|||
</div> |
|||
<div class="modal-main"> |
|||
<div class="modal-content"> |
|||
<ul class="options-list"> |
|||
<li |
|||
v-for="(option, index) in filteredOptionsLeft" |
|||
:key="option.value || index" |
|||
:class="{ selected: option.value === tempSelectedLeftValue }" |
|||
@click="selectOptionLeft(option)" |
|||
> |
|||
{{ option.label }} |
|||
</li> |
|||
</ul> |
|||
<div v-if="!filteredOptionsLeft.length" class="no-results"> |
|||
没有找到匹配项 |
|||
</div> |
|||
</div> |
|||
<div class="modal-content"> |
|||
<ul class="options-list"> |
|||
<li |
|||
v-for="(option, index) in filteredOptionsRight" |
|||
:key="option.value || index" |
|||
:class="{ selected: option.value === tempSelectedRightValue }" |
|||
@click="selectOption(option)" |
|||
> |
|||
{{ option.label }} |
|||
</li> |
|||
</ul> |
|||
<div v-if="!filteredOptionsRight.length" class="no-results"> |
|||
没有找到匹配项 |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="modal-footer"> |
|||
<button class="cancel-btn" @click="handleCancel">取消</button> |
|||
<button class="confirm-btn" @click="confirmSelection">确定</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
.modal-overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
z-index: 200; |
|||
} |
|||
|
|||
.modal-container { |
|||
background-color: white; |
|||
border-radius: 12px; |
|||
width: 90%; |
|||
max-width: 400px; |
|||
max-height: 80vh; |
|||
display: flex; |
|||
flex-direction: column; |
|||
overflow: hidden; |
|||
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); |
|||
animation: fadeIn 0.2s ease-out; |
|||
} |
|||
|
|||
.modal-header { |
|||
padding: 16px 20px; |
|||
border-bottom: 1px solid #e2e8f0; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.modal-header h3 { |
|||
margin: 0; |
|||
font-size: 18px; |
|||
font-weight: 500; |
|||
color: #1e293b; |
|||
} |
|||
|
|||
.close-btn { |
|||
background: none; |
|||
border: none; |
|||
cursor: pointer; |
|||
font-size: 18px; |
|||
color: #94a3b8; |
|||
transition: color 0.2s ease; |
|||
} |
|||
|
|||
.close-btn:hover { |
|||
color: #64748b; |
|||
} |
|||
|
|||
.modal-content { |
|||
flex: 1; |
|||
overflow-y: auto; |
|||
padding: 10px 0; |
|||
max-height: 15vw; |
|||
} |
|||
|
|||
.search-box { |
|||
position: relative; |
|||
padding: 8px 16px; |
|||
} |
|||
|
|||
.search-box input { |
|||
width: 100%; |
|||
padding: 10px 32px 10px 12px; |
|||
border: 1px solid #e2e8f0; |
|||
border-radius: 8px; |
|||
font-size: 14px; |
|||
outline: none; |
|||
transition: border-color 0.2s ease; |
|||
} |
|||
|
|||
.search-box input:focus { |
|||
border-color: #3b82f6; |
|||
} |
|||
|
|||
.search-box i { |
|||
position: absolute; |
|||
right: 28px; |
|||
top: 50%; |
|||
transform: translateY(-50%); |
|||
color: #94a3b8; |
|||
} |
|||
|
|||
.options-list { |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
.options-list li { |
|||
padding: 14px 20px; |
|||
font-size: 16px; |
|||
color: #334155; |
|||
cursor: pointer; |
|||
transition: background-color 0.2s ease; |
|||
text-align: center; |
|||
} |
|||
|
|||
.options-list li:hover { |
|||
background-color: #f1f5f9; |
|||
} |
|||
|
|||
.options-list li.selected { |
|||
background-color: #a4c4f1; |
|||
color: #0284c7; |
|||
font-weight: 500; |
|||
text-align: center; |
|||
} |
|||
|
|||
.no-results { |
|||
padding: 16px 20px; |
|||
font-size: 14px; |
|||
color: #94a3b8; |
|||
text-align: center; |
|||
} |
|||
|
|||
.modal-footer { |
|||
padding: 12px 16px; |
|||
border-top: 1px solid #e2e8f0; |
|||
display: flex; |
|||
gap: 12px; |
|||
} |
|||
|
|||
.modal-footer button { |
|||
flex: 1; |
|||
padding: 10px 16px; |
|||
border-radius: 8px; |
|||
font-size: 16px; |
|||
cursor: pointer; |
|||
transition: all 0.2s ease; |
|||
} |
|||
|
|||
.cancel-btn { |
|||
background-color: #ffffff; |
|||
border: 1px solid #e2e8f0; |
|||
color: #64748b; |
|||
} |
|||
|
|||
.cancel-btn:hover { |
|||
background-color: #f8fafc; |
|||
} |
|||
|
|||
.confirm-btn { |
|||
background-color: #3b82f6; |
|||
border: none; |
|||
color: white; |
|||
} |
|||
|
|||
.confirm-btn:hover { |
|||
background-color: #2563eb; |
|||
} |
|||
|
|||
.modal-main { |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr; |
|||
} |
|||
|
|||
@keyframes fadeIn { |
|||
from { opacity: 0; transform: scale(0.95); } |
|||
to { opacity: 1; transform: scale(1); } |
|||
} |
|||
</style> |
@ -0,0 +1,123 @@ |
|||
<script lang="ts" setup> |
|||
import * as echarts from 'echarts' |
|||
import { onMounted } from 'vue' |
|||
|
|||
const props = defineProps({ |
|||
title: { |
|||
type: String, |
|||
default: '仓内', |
|||
}, |
|||
chartId: { |
|||
type: String, |
|||
default: 'inside', |
|||
}, |
|||
}) |
|||
|
|||
onMounted(() => { |
|||
const chartDom = document.getElementById(props.chartId) |
|||
if (chartDom) { |
|||
const myChart = echarts.init(chartDom) |
|||
|
|||
// mock生成随机数据 |
|||
const generateRandomData = (maxValue: number) => { |
|||
return Math.floor(Math.random() * maxValue) |
|||
} |
|||
|
|||
const temperatureData: number[] = [0] |
|||
const humidityData: number[] = [0] |
|||
const h2o2ConcentrationData: number[] = [0] |
|||
const h2o2SaturationData: number[] = [0] |
|||
|
|||
const dataLength = 20 |
|||
for (let i = 0; i < dataLength; i++) { |
|||
temperatureData.push(generateRandomData(400)) |
|||
humidityData.push(generateRandomData(200)) |
|||
h2o2ConcentrationData.push(generateRandomData(100)) |
|||
h2o2SaturationData.push(generateRandomData(500)) |
|||
} |
|||
|
|||
const option = { |
|||
title: { |
|||
text: props.title, |
|||
}, |
|||
tooltip: { |
|||
trigger: 'axis', |
|||
}, |
|||
legend: { |
|||
// data: ['温度', '湿度', 'H2O2浓度', 'H2O2饱和度'], |
|||
data: ['温度', '湿度', 'H2O2浓度', 'H2O2饱和度'], |
|||
orient: 'horizontal', // 水平布局 |
|||
itemWidth: 10, // 图例图形宽度 |
|||
itemHeight: 10, // 图例图形高度 |
|||
columnWidth: 80, // 每列宽度,可按需调整 |
|||
top: '5%', // 距离容器顶部距离 |
|||
left: 'center', // 水平居中 |
|||
}, |
|||
xAxis: { |
|||
type: 'category', |
|||
boundaryGap: false, |
|||
data: Array.from({ length: dataLength }, (_, i) => `点${i + 1}`), |
|||
}, |
|||
yAxis: { |
|||
type: 'value', |
|||
}, |
|||
series: [ |
|||
{ |
|||
name: '温度', |
|||
type: 'line', |
|||
data: temperatureData.sort((a, b) => a - b), |
|||
symbol: 'circle', |
|||
symbolSize: 2, |
|||
lineStyle: { |
|||
color: '#1e90ff', |
|||
width: 1, |
|||
}, |
|||
}, |
|||
{ |
|||
name: '湿度', |
|||
type: 'line', |
|||
data: humidityData.sort((a, b) => a - b), |
|||
symbol: 'circle', |
|||
symbolSize: 2, |
|||
lineStyle: { |
|||
color: '#32cd32', |
|||
width: 1, |
|||
}, |
|||
}, |
|||
{ |
|||
name: 'H2O2浓度', |
|||
type: 'line', |
|||
data: h2o2ConcentrationData.sort((a, b) => a - b), |
|||
symbol: 'circle', |
|||
symbolSize: 2, |
|||
lineStyle: { |
|||
color: '#ffd700', |
|||
width: 1, |
|||
}, |
|||
}, |
|||
{ |
|||
name: 'H2O2饱和度', |
|||
type: 'line', |
|||
data: h2o2SaturationData.sort((a, b) => a - b), |
|||
symbol: 'circle', |
|||
symbolSize: 2, |
|||
lineStyle: { |
|||
color: '#ff0000', |
|||
width: 1, |
|||
}, |
|||
}, |
|||
], |
|||
} |
|||
myChart.setOption(option) |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="line-chart"> |
|||
<div :id="chartId" style="width: 32vw; height: 50vh;" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
</style> |
@ -1,72 +0,0 @@ |
|||
<script lang="ts" setup> |
|||
import * as echarts from 'echarts' |
|||
import { onMounted } from 'vue' |
|||
import { useRouter } from 'vue-router' |
|||
|
|||
const router = useRouter() |
|||
onMounted(() => { |
|||
// 基于准备好的dom,初始化echarts实例 |
|||
const myChart = echarts.init(document.getElementById('line-chart') as HTMLElement) |
|||
|
|||
// 假设的数据,你可以根据实际情况替换 |
|||
const xData = [1950, 1953, 1956, 1959, 1962, 1965, 1968, 1971, 1974, 1977, 1980, 1983, 1986, 1989, 1992, 1995, 1998, 2001, 2004, 2007, 2010, 2013] |
|||
const yData1 = [7000, 12000, 14000, 16000, 18000, 20000, 22000, 24000, 26000, 28000, 30000, 32000, 34000, 36000, 38000, 40000, 42000, 40000, 41000, 43000, 45000, 44000] |
|||
const yData2 = [6000, 9000, 11000, 13000, 15000, 17000, 19000, 21000, 23000, 25000, 27000, 29000, 31000, 33000, 35000, 37000, 39000, 36000, 37000, 38000, 36000, 37000] |
|||
|
|||
// 配置项 |
|||
const option: echarts.EChartsOption = { |
|||
xAxis: { |
|||
type: 'category', |
|||
data: xData, |
|||
}, |
|||
yAxis: { |
|||
type: 'value', |
|||
name: 'Income', |
|||
}, |
|||
series: [ |
|||
{ |
|||
name: 'Series 1', |
|||
type: 'line', |
|||
data: yData1, |
|||
}, |
|||
{ |
|||
name: 'Series 2', |
|||
type: 'line', |
|||
data: yData2, |
|||
}, |
|||
], |
|||
} |
|||
// 使用刚指定的配置项和数据显示图表 |
|||
myChart.setOption(option) |
|||
}) |
|||
const goHome = () => { |
|||
router.push('/home') |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="chart-main"> |
|||
<div id="line-chart" style="width: 800px; height: 400px;"></div> |
|||
<div class="chart-home"> |
|||
<bt-button |
|||
button-text="返回" |
|||
@click="goHome" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.chart-main{ |
|||
height: 100%; |
|||
overflow: hidden; |
|||
padding: 10px; |
|||
background: $gradient-color; |
|||
height: 83vh; |
|||
} |
|||
.chart-home{ |
|||
position: absolute; |
|||
bottom: 5rem; |
|||
margin-left: 45vw; |
|||
} |
|||
</style> |
@ -0,0 +1,190 @@ |
|||
<script lang="ts" setup> |
|||
import { useFormulaStore } from '@/stores/formulaStore' |
|||
import { useHomeStore } from '@/stores/homeStore' |
|||
import homeFinish from 'assets/images/home/home-finish.svg' |
|||
import homeSettingSvg from 'assets/images/home/home-setting.svg' |
|||
import HomeFormula from 'components/home/HomeFormula.vue' |
|||
import LineChart from 'components/home/LineChart.vue' |
|||
import { ref, watchEffect } from 'vue' |
|||
import { useRouter } from 'vue-router' |
|||
|
|||
const router = useRouter() |
|||
const formulaStore = useFormulaStore() |
|||
const homeStore = useHomeStore() |
|||
const formulaInfo = ref() |
|||
const disinfectionState = ref(homeStore.disinfectionState) |
|||
const curStateRemainTime = ref(homeStore.curStateRemainTime) |
|||
|
|||
watchEffect(() => { |
|||
formulaInfo.value = formulaStore.currentSelectedFormulaInfo |
|||
disinfectionState.value = homeStore.disinfectionState |
|||
curStateRemainTime.value = homeStore.curStateRemainTime |
|||
}) |
|||
const onDisinfectConfig = () => { |
|||
|
|||
} |
|||
// 结束消毒 |
|||
const onFinishDisinfect = () => { |
|||
// countdownTimer.value && countdownTimer.value.stopTimer() |
|||
homeStore.updateHomeDisinfectionState({ |
|||
...disinfectionState.value, |
|||
state: 'idle', |
|||
}) |
|||
} |
|||
|
|||
const goHome = () => { |
|||
router.back() |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<main class="main-content"> |
|||
<div class="line-chart-title"> |
|||
<div v-if="formulaInfo && formulaInfo.name" class="line-chart-formula"> |
|||
<HomeFormula /> |
|||
</div> |
|||
<div class="line-chart-set"> |
|||
<bt-button |
|||
button-text="消毒机数据" |
|||
text-size="14px" |
|||
border-radius="5px" |
|||
height="3.5rem" |
|||
text-color="#1989fa" |
|||
padding="0.8vw" |
|||
@click="onDisinfectConfig" |
|||
> |
|||
<template #icon> |
|||
<el-icon style="font-weight: bold;"><Operation /></el-icon> |
|||
</template> |
|||
</bt-button> |
|||
<bt-button |
|||
button-text="消毒设置" |
|||
text-size="14px" |
|||
border-radius="5px" |
|||
height="3.5rem" |
|||
text-color="#1989fa" |
|||
padding="0.8vw" |
|||
@click="onDisinfectConfig" |
|||
> |
|||
<template #icon> |
|||
<img :src="homeSettingSvg" width="15" alt=""> |
|||
</template> |
|||
</bt-button> |
|||
</div> |
|||
</div> |
|||
<div class="line-chart-content"> |
|||
<div> |
|||
<LineChart key="1" title="仓内" chart-id="inside" /> |
|||
</div> |
|||
<div> |
|||
<LineChart key="2" title="探头1" chart-id="env1" /> |
|||
</div> |
|||
<div> |
|||
<LineChart key="3" title="探头2" chart-id="env2" /> |
|||
</div> |
|||
</div> |
|||
<div class="line-chart-bottom"> |
|||
<div class="home-chart-time"> |
|||
<div class="home-remain-time"> |
|||
<div class="home-chart-label"> |
|||
预计剩余时间: |
|||
</div> |
|||
<div v-if="curStateRemainTime" class="home-chart-value"> |
|||
{{ curStateRemainTime }} |
|||
</div> |
|||
<div v-else class="home-chart-value"> |
|||
空闲 |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="home-chart-btn"> |
|||
<bt-button |
|||
button-text="结束消毒" |
|||
bg-color="#FF6767" |
|||
text-color="#FFFFFF" |
|||
width="15vw" |
|||
height="7vh" |
|||
text-size="24px" |
|||
border-radius="12px" |
|||
@click="onFinishDisinfect" |
|||
> |
|||
<template #icon> |
|||
<img :src="homeFinish" alt=""> |
|||
</template> |
|||
</bt-button> |
|||
<bt-button |
|||
button-text="返回" |
|||
width="10vw" |
|||
height="7vh" |
|||
text-color="#1989fa" |
|||
text-size="24px" |
|||
border-radius="12px" |
|||
@click="goHome" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</main> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.main-content{ |
|||
overflow: hidden; |
|||
background: $gradient-color; |
|||
height: $main-container; |
|||
.line-chart-formula{ |
|||
width: 25vw; |
|||
background: #E0F0FF; |
|||
height: 3.5rem; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
border: 1px solid #E0F0FF; |
|||
border-radius: 10px; |
|||
margin-left: 2rem; |
|||
} |
|||
.line-chart-set{ |
|||
display: flex; |
|||
justify-content: end; |
|||
align-items: center; |
|||
width: 65vw; |
|||
} |
|||
.line-chart-title{ |
|||
height: 15%; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
.line-chart-content{ |
|||
display: grid; |
|||
grid-template-columns: repeat(3,1fr); |
|||
height: 65%; |
|||
} |
|||
.line-chart-bottom{ |
|||
height: 15%; |
|||
display: flex; |
|||
padding-right: 20px; |
|||
.home-chart-btn{ |
|||
display: flex; |
|||
justify-content: end; |
|||
width: 65%; |
|||
} |
|||
.home-chart-time{ |
|||
width: 35%; |
|||
.home-remain-time{ |
|||
background: #F6FAFE; |
|||
width: 27vw; |
|||
height: 8vh; |
|||
border-radius: 12px; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-size: 24px; |
|||
gap: 10px; |
|||
margin-left: 1rem; |
|||
.home-chart-value{ |
|||
color: #2892F3; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue