feat(property): 1
This commit is contained in:
@@ -22,7 +22,7 @@ onMounted(() => {
|
||||
try {
|
||||
// 尝试解析消息内容
|
||||
const parsedMessage = JSON.parse(latestMessage);
|
||||
if (parsedMessage.type === 'meter') {
|
||||
if (parsedMessage.type === 'meter' && parsedMessage.meterType === 1) {
|
||||
// 根据消息内容执行相应操作
|
||||
handleSSEMessage(parsedMessage);
|
||||
}
|
||||
|
@@ -1,53 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { handleNode } from '@vben/utils'
|
||||
import { Empty, Skeleton, Tree } from 'ant-design-vue'
|
||||
import { communityTree } from "#/api/property/community"
|
||||
import type { CommunityVO } from "#/api/property/community/model"
|
||||
import type { PropType } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { handleNode } from '@vben/utils';
|
||||
import { Empty, Skeleton, Tree } from 'ant-design-vue';
|
||||
import { queryTree } from '#/api/property/energyManagement/meterInfo';
|
||||
import type { TreeNode } from '#/api/common';
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
defineOptions({ inheritAttrs: false });
|
||||
|
||||
withDefaults(defineProps<{ showSearch?: boolean }>(), { showSearch: true })
|
||||
const props = defineProps({
|
||||
isMeter: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
/**
|
||||
* 点击刷新按钮的事件
|
||||
*/
|
||||
reload: []
|
||||
reload: [];
|
||||
/**
|
||||
* 点击节点的事件
|
||||
* @param selectedKeys 选中的节点keys
|
||||
* @param info 节点信息对象
|
||||
*/
|
||||
select: []
|
||||
}>()
|
||||
select: [selectedKeys: string[], info: any];
|
||||
}>();
|
||||
|
||||
const selectFloorId = defineModel('selectFloorId', {
|
||||
const selectTreeId = defineModel('selectTreeId', {
|
||||
type: Array as PropType<string[]>,
|
||||
})
|
||||
});
|
||||
|
||||
const searchValue = defineModel('searchValue', {
|
||||
type: String,
|
||||
default: '',
|
||||
})
|
||||
});
|
||||
|
||||
type TreeArray = CommunityVO[]
|
||||
const treeArray = ref<TreeArray>([])
|
||||
const treeArray = ref<TreeNode[]>([]);
|
||||
/** 骨架屏加载 */
|
||||
const showTreeSkeleton = ref<boolean>(true)
|
||||
const showTreeSkeleton = ref<boolean>(true);
|
||||
|
||||
async function loadTree() {
|
||||
showTreeSkeleton.value = true
|
||||
searchValue.value = ''
|
||||
selectFloorId.value = []
|
||||
const ret = await communityTree(3)
|
||||
const splitStr = '/'
|
||||
handleNode(ret, 'label', splitStr, function (node: any) {
|
||||
if (node.level != 3) {
|
||||
node.disabled = true
|
||||
showTreeSkeleton.value = true;
|
||||
searchValue.value = '';
|
||||
const ret = await queryTree({ meterType: 2, isMeter: props.isMeter });
|
||||
handleNode(ret, 3);
|
||||
treeArray.value = ret;
|
||||
showTreeSkeleton.value = false;
|
||||
}
|
||||
|
||||
function handleNode(nodes: any[], level: number) {
|
||||
nodes.forEach((node) => {
|
||||
node.key = node.id;
|
||||
if (node.level < level) {
|
||||
node.disabled = true;
|
||||
}
|
||||
})
|
||||
treeArray.value = ret
|
||||
showTreeSkeleton.value = false
|
||||
if (node.children) {
|
||||
handleNode(node.children, level);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(loadTree);
|
||||
@@ -55,23 +67,46 @@ onMounted(loadTree);
|
||||
|
||||
<template>
|
||||
<div :class="$attrs.class">
|
||||
<Skeleton :loading="showTreeSkeleton" :paragraph="{ rows: 8 }" active class="p-[8px] flex-1 min-h-0">
|
||||
<div class="bg-background flex h-full flex-col overflow-y-auto rounded-lg">
|
||||
<Skeleton
|
||||
:loading="showTreeSkeleton"
|
||||
:paragraph="{ rows: 8 }"
|
||||
active
|
||||
class="min-h-0 flex-1 p-[8px]"
|
||||
>
|
||||
<div
|
||||
class="bg-background flex h-full flex-col overflow-y-auto rounded-lg"
|
||||
>
|
||||
<div class="h-full overflow-x-hidden px-[8px]">
|
||||
<Tree v-bind="$attrs" v-if="treeArray.length > 0" v-model:selected-keys="selectFloorId"
|
||||
:field-names="{ title: 'label', key: 'id' }" :show-line="{ showLeafIcon: false }" :tree-data="treeArray"
|
||||
:virtual="false" default-expand-all @select="$emit('select')">
|
||||
<Tree
|
||||
v-bind="$attrs"
|
||||
v-if="treeArray.length > 0"
|
||||
v-model:selected-keys="selectTreeId"
|
||||
:show-line="{ showLeafIcon: false }"
|
||||
:tree-data="treeArray"
|
||||
:virtual="false"
|
||||
default-expand-all
|
||||
@select="
|
||||
(selectedKeys, info) => $emit('select', selectedKeys, info)
|
||||
"
|
||||
>
|
||||
<template #title="{ label }">
|
||||
<span v-if="label.indexOf(searchValue) > -1">
|
||||
{{ label.substring(0, label.indexOf(searchValue)) }}
|
||||
<span style="color: #f50">{{ searchValue }}</span>
|
||||
{{ label.substring(label.indexOf(searchValue) + searchValue.length) }}
|
||||
{{
|
||||
label.substring(
|
||||
label.indexOf(searchValue) + searchValue.length,
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<span v-else>{{ label }}</span>
|
||||
</template>
|
||||
</Tree>
|
||||
<div v-else class="mt-5">
|
||||
<Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" description="暂无数据" />
|
||||
<Empty
|
||||
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
||||
description="暂无数据"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,377 +1,286 @@
|
||||
<script setup lang="ts">
|
||||
import { useNotifyStore } from '#/store';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
import FloorTree from '../components/floor-tree.vue';
|
||||
import { BackTop, message, Spin } from 'ant-design-vue';
|
||||
import { Page, VbenCountToAnimator } from '@vben/common-ui';
|
||||
import { ref, onMounted, onBeforeUnmount, reactive, watch } from 'vue';
|
||||
import { currentReading } from '#/api/property/energyManagement/meterInfo';
|
||||
|
||||
const notifyStore = useNotifyStore();
|
||||
const readingData = ref<any>({});
|
||||
const readingTime = ref('');
|
||||
const readingLoading = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
notifyStore.startListeningMessage();
|
||||
// 监听sseList的变化
|
||||
watch(
|
||||
() => notifyStore.sseList,
|
||||
(val) => {
|
||||
const latestMessage = val[val.length - 1];
|
||||
try {
|
||||
// 尝试解析消息内容
|
||||
const parsedMessage = JSON.parse(latestMessage);
|
||||
if (parsedMessage.type === 'meter' && parsedMessage.meterType === 2) {
|
||||
// 根据消息内容执行相应操作
|
||||
handleSSEMessage(parsedMessage);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('收到非JSON消息:', latestMessage);
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
});
|
||||
|
||||
function handleSSEMessage(data: any) {
|
||||
if (data.data.length === 0) {
|
||||
message.warn('当前楼层暂无水表!');
|
||||
}
|
||||
readingData.value = data.data;
|
||||
readingTime.value = data.readingTime;
|
||||
readingLoading.value = false;
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 关闭页面,发送请求停止心跳
|
||||
currentReading({ meterType: 0, floorId: 0 });
|
||||
});
|
||||
async function handleSelectFloor(selectedKeys, info) {
|
||||
if (typeof selectedKeys[0] === 'undefined') {
|
||||
return;
|
||||
}
|
||||
readingLoading.value = true;
|
||||
await currentReading({
|
||||
meterType: 2,
|
||||
floorId: selectedKeys[0],
|
||||
});
|
||||
}
|
||||
|
||||
function targetFn() {
|
||||
return document.getElementById('right-panel');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<div class="flex h-full gap-[8px]">
|
||||
<FloorTree class="w-[260px]"></FloorTree>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<div class="row">
|
||||
<div class="comparison-section-container">
|
||||
<div class="section-header">
|
||||
<div class="header-title">环比</div>
|
||||
</div>
|
||||
<div class="comparison-grid">
|
||||
<div class="comparison-item">
|
||||
<div class="item-value">{{ chainData.currentMonthEnergy }}</div>
|
||||
<div class="item-title">当月用水(t)</div>
|
||||
<FloorTree
|
||||
:isMeter="false"
|
||||
class="w-[260px]"
|
||||
@select="handleSelectFloor"
|
||||
></FloorTree>
|
||||
<div class="flex-1" id="right-panel">
|
||||
<Spin :spinning="readingLoading" size="large" style="height: 100px">
|
||||
<div class="cards-container">
|
||||
<div v-for="item in readingData" class="meterInfo-card">
|
||||
<h2>
|
||||
{{ item.meterName }}
|
||||
</h2>
|
||||
<div class="meterInfo-reading">
|
||||
<p>
|
||||
<VbenCountToAnimator
|
||||
:end-val="item.initReading"
|
||||
:decimals="2"
|
||||
prefix=""
|
||||
/>
|
||||
</p>
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="comparison-item">
|
||||
<div class="item-value">{{ chainData.lastMonthSamePeriodEnergy }}</div>
|
||||
<div class="item-title">上月同期(t)</div>
|
||||
<div class="meterInfo-list">
|
||||
<ul>
|
||||
<li>
|
||||
<span>采集时间:</span>
|
||||
<span>{{ readingTime }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>设备位置:</span>
|
||||
<span>{{ item.installLocation }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="comparison-item">
|
||||
<div class="item-top">
|
||||
<div class="item-percent">{{ chainData.monthTrendPercentage }}</div>
|
||||
<div>{{ chainData.monthTrendValue }}
|
||||
<span class="item-title">t</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-title">趋势</div>
|
||||
</div>
|
||||
<div class="comparison-item">
|
||||
<div class="item-value">{{ chainData.currentYearEnergy }}</div>
|
||||
<div class="item-title">当年用水(t)</div>
|
||||
|
||||
</div>
|
||||
<div class="comparison-item">
|
||||
<div class="item-value">{{ chainData.lastYearSamePeriodEnergy }}</div>
|
||||
<div class="item-title">去年同期(t)</div>
|
||||
</div>
|
||||
<div class="comparison-item">
|
||||
<div class="item-top">
|
||||
<div class="item-percent">{{ chainData.yearTrendPercentage }}</div>
|
||||
<div>{{ chainData.yearTrendValue }}
|
||||
<span class="item-title">t</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-title">趋势</div>
|
||||
<div class="button-get-plan">
|
||||
<a
|
||||
v-if="item.communicationState !== 0"
|
||||
style="background: #6bb1e3"
|
||||
>
|
||||
<span>在线</span>
|
||||
</a>
|
||||
<a v-else style="background: orange">
|
||||
<span>离线</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="energy-trend-container">
|
||||
<div class="energy-trend-top">
|
||||
<div class="section-header">
|
||||
<div class="header-title">能耗趋势</div>
|
||||
</div>
|
||||
<RadioGroup v-model:value="energyTrendTime" button-style="solid" size="small"
|
||||
@change="buildingEnergyTrendData(energyTrendTime)">
|
||||
<RadioButton value="2">当月</RadioButton>
|
||||
<RadioButton value="3">当年</RadioButton>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div class="energy-chart" ref="energyTrendChart">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="power-curve-container">
|
||||
<div class="section-header">
|
||||
<div class="header-title">当年数据曲线</div>
|
||||
</div>
|
||||
<div class="power-chart" ref="powerCurveChart">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Spin>
|
||||
</div>
|
||||
<BackTop class="back-to-top" :target="targetFn"></BackTop>
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RadioGroup, RadioButton } from 'ant-design-vue'
|
||||
import { Page } from '@vben/common-ui'
|
||||
import { ref, onMounted, onBeforeUnmount, reactive } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import type { ECharts, EChartsOption } from 'echarts'
|
||||
import FloorTree from "../components/floor-tree.vue"
|
||||
|
||||
const chainData = reactive({
|
||||
currentMonthEnergy: '9',
|
||||
lastMonthSamePeriodEnergy: '--',
|
||||
monthTrendPercentage: '--',
|
||||
monthTrendValue: '--',
|
||||
currentYearEnergy: '59',
|
||||
lastYearSamePeriodEnergy: '--',
|
||||
yearTrendPercentage: '--',
|
||||
yearTrendValue: '--',
|
||||
})
|
||||
|
||||
const energyTrendTime = ref('2')
|
||||
|
||||
// 能耗趋势图表容器
|
||||
const energyTrendChart = ref<HTMLElement | null>(null)
|
||||
const energyTrendInstance = ref<ECharts | null>(null)
|
||||
|
||||
const energyTrendOption: EChartsOption = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [],
|
||||
name: '日期',
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: 't',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [],
|
||||
type: 'bar',
|
||||
markPoint: {
|
||||
data: [
|
||||
{ type: 'max', name: 'Max' },
|
||||
{ type: 'min', name: 'Min' }
|
||||
]
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
const initEnergyTrendChart = () => {
|
||||
if (energyTrendChart.value) {
|
||||
// 销毁旧实例
|
||||
energyTrendInstance.value?.dispose()
|
||||
// 创建新实例
|
||||
energyTrendInstance.value = echarts.init(energyTrendChart.value)
|
||||
// 设置配置项
|
||||
energyTrendInstance.value.setOption(energyTrendOption)
|
||||
buildingEnergyTrendData('2')
|
||||
// 可选:添加窗口大小变化监听
|
||||
const resizeHandler = () => {
|
||||
energyTrendInstance.value?.resize()
|
||||
}
|
||||
window.addEventListener('resize', resizeHandler)
|
||||
|
||||
// 在组件卸载前移除监听
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resizeHandler)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function buildingEnergyTrendData(val: string) {
|
||||
const now = new Date()
|
||||
let timeArr = []
|
||||
let valArr = []
|
||||
let name = '日期'
|
||||
if (val == '2') {
|
||||
const day = now.getDate()
|
||||
for (let i = 1; i < day; i++) {
|
||||
timeArr.push(i)
|
||||
valArr.push(parseFloat((Math.random() * 10).toFixed(2)))
|
||||
}
|
||||
} else {
|
||||
const month = now.getMonth() + 1
|
||||
for (let i = 1; i < month; i++) {
|
||||
timeArr.push(i)
|
||||
valArr.push(parseFloat((Math.random() * 80).toFixed(2)))
|
||||
}
|
||||
name = '月份'
|
||||
}
|
||||
|
||||
if (energyTrendInstance.value) {
|
||||
energyTrendInstance.value.setOption({
|
||||
xAxis: { data: timeArr, name },
|
||||
series: [{ data: valArr }],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//日用电率
|
||||
const powerCurveChart = ref<HTMLElement | null>(null)
|
||||
const powerCurveInstance = ref<ECharts | null>(null)
|
||||
|
||||
|
||||
const powerCurveOption: EChartsOption = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
crossStyle: {
|
||||
color: '#999'
|
||||
}
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
magicType: { show: true, type: ['line', 'bar'] },
|
||||
restore: { show: true },
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['今年']
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: [],
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
name: '月份'
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: 't',
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '今年',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: [],
|
||||
markPoint: {
|
||||
data: [
|
||||
{ type: 'max', },
|
||||
{ type: 'min', }
|
||||
]
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const initPowerCurveChart = () => {
|
||||
if (powerCurveChart.value) {
|
||||
// 销毁旧实例
|
||||
powerCurveInstance.value?.dispose()
|
||||
// 创建新实例
|
||||
powerCurveInstance.value = echarts.init(powerCurveChart.value)
|
||||
// 设置配置项
|
||||
powerCurveInstance.value.setOption(powerCurveOption)
|
||||
buildingPowerCurveData()
|
||||
// 可选:添加窗口大小变化监听
|
||||
const resizeHandler = () => {
|
||||
powerCurveInstance.value?.resize()
|
||||
}
|
||||
window.addEventListener('resize', resizeHandler)
|
||||
|
||||
// 在组件卸载前移除监听
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resizeHandler)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function buildingPowerCurveData() {
|
||||
const now = new Date()
|
||||
const month = now.getMonth() + 1
|
||||
let yearData = []
|
||||
let timeDate = []
|
||||
for (let i = 1; i < month; i++) {
|
||||
timeDate.push(i + '月')
|
||||
yearData.push(parseFloat((Math.random() * 60).toFixed(2)))
|
||||
}
|
||||
if (powerCurveInstance.value) {
|
||||
powerCurveInstance.value.setOption({
|
||||
xAxis: { data: timeDate },
|
||||
series: [{ data: yearData }],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载后初始化图表
|
||||
onMounted(() => {
|
||||
initEnergyTrendChart()
|
||||
initPowerCurveChart()
|
||||
})
|
||||
|
||||
// 组件卸载前销毁图表实例
|
||||
onBeforeUnmount(() => {
|
||||
energyTrendInstance.value?.dispose()
|
||||
powerCurveInstance.value?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.row {
|
||||
/* 右侧内容区域样式 */
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
overflow-y: auto; /* 添加垂直滚动条 */
|
||||
padding: 10px;
|
||||
height: 100%; /* 确保高度占满父容器 */
|
||||
box-sizing: border-box; /* 包括padding在高度计算中 */
|
||||
}
|
||||
|
||||
/* 卡片容器样式 */
|
||||
.cards-container {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
gap: 20px; /* 使用gap替代margin控制间距 */
|
||||
}
|
||||
|
||||
.comparison-section-container {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.energy-trend-container {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
.power-curve-container {
|
||||
margin-top: 10px;
|
||||
flex: 5;
|
||||
}
|
||||
|
||||
.energy-trend-top {
|
||||
display: flex;
|
||||
justify-content: space-between
|
||||
}
|
||||
|
||||
.section-header {
|
||||
border-left: 4px solid #3671e8;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.comparison-section-container,
|
||||
.energy-trend-container,
|
||||
.power-curve-container {
|
||||
.meterInfo-card {
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
padding: 1rem;
|
||||
width: 15rem;
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 20px;
|
||||
border-radius: 10px;
|
||||
border-bottom: 4px solid #87ceeb;
|
||||
box-shadow: 0 6px 30px rgba(173, 216, 230, 0.3);
|
||||
font-family: 'Poppins', sans-serif;
|
||||
height: 270px;
|
||||
margin: 0 5px 20px 5px;
|
||||
}
|
||||
|
||||
.comparison-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 5px;
|
||||
.meterInfo-card h2 {
|
||||
margin-bottom: 15px;
|
||||
font-size: 27px;
|
||||
color: #5faee3;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.comparison-item {
|
||||
padding: 15px 10px;
|
||||
border: 1px solid #e0e0e0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
font-size: 22px;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
.meterInfo-card h2 span {
|
||||
display: block;
|
||||
margin-top: -4px;
|
||||
color: #7eb8da;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.item-top {
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
.meterInfo-reading {
|
||||
position: relative;
|
||||
background: #f0f8ff;
|
||||
width: 14.46rem;
|
||||
margin-left: -0.65rem;
|
||||
padding: 0.2rem 1.2rem;
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
|
||||
.item-percent {
|
||||
color: #7fb926;
|
||||
margin-bottom: 5px;
|
||||
border-bottom: 1px solid #666;
|
||||
.meterInfo-reading p {
|
||||
margin: 0;
|
||||
padding-top: 0.4rem;
|
||||
display: flex;
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
color: #4a8cbb;
|
||||
}
|
||||
|
||||
.power-chart {
|
||||
height: 45vh;
|
||||
.meterInfo-reading p:before {
|
||||
content: '';
|
||||
margin-right: 5px;
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.energy-chart {
|
||||
height: 30vh;
|
||||
.meterInfo-reading p:after {
|
||||
content: '/ 立方米';
|
||||
margin-left: 5px;
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.meterInfo-reading div {
|
||||
position: absolute;
|
||||
bottom: -23px;
|
||||
right: 0px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 13px solid #87ceeb;
|
||||
border-bottom: 10px solid transparent;
|
||||
border-right: 13px solid transparent;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.meterInfo-list {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.meterInfo-list ul {
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.meterInfo-list ul li {
|
||||
color: #5a7d9a;
|
||||
list-style: none;
|
||||
margin-bottom: 0.2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.meterInfo-list ul li svg {
|
||||
width: 0.9rem;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.meterInfo-list ul li span {
|
||||
font-weight: 300;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.button-get-plan {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1.2rem;
|
||||
}
|
||||
|
||||
.button-get-plan a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: #fff;
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.05rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.button-get-plan a:hover {
|
||||
transform: translateY(-3%);
|
||||
box-shadow: 0 3px 10px rgba(123, 180, 220, 0.6); /* 浅蓝色阴影 */
|
||||
background: #5fa0d0; /* 悬停时略深的蓝色 */
|
||||
}
|
||||
|
||||
.button-get-plan .svg-rocket {
|
||||
margin-right: 10px;
|
||||
width: 0.9rem;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.back-to-top {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
/* 返回顶部按钮 */
|
||||
.back-to-top:hover {
|
||||
background-color: #6bb1e3;
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
</style>
|
||||
|
@@ -108,6 +108,12 @@ export const columns: VxeGridProps['columns'] = [
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '授权状态',
|
||||
field: 'eeightId',
|
||||
slots: { default: 'eeightId' },
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
field: 'remark',
|
||||
@@ -117,7 +123,7 @@ export const columns: VxeGridProps['columns'] = [
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 200,
|
||||
width: 250,
|
||||
},
|
||||
]
|
||||
|
||||
|
@@ -1,31 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui'
|
||||
import { getVxePopupContainer } from '@vben/utils'
|
||||
|
||||
import { Avatar, Modal, Popconfirm, Space } from 'ant-design-vue'
|
||||
import { useAccess } from '@vben/access';
|
||||
import personModal from './person-modal.vue';
|
||||
import { columns, querySchema } from './data';
|
||||
import personDetail from './person-detail.vue';
|
||||
import { TableSwitch } from '#/components/table';
|
||||
import { getVxePopupContainer } from '@vben/utils';
|
||||
import faceImportModal from './face-import-modal.vue';
|
||||
import personImportModal from './person-import-modal.vue';
|
||||
import { commonDownloadExcel } from '#/utils/file/download';
|
||||
import { Avatar, Modal, Popconfirm, Space, Tag } from 'ant-design-vue';
|
||||
import type { PersonForm } from '#/api/property/resident/person/model';
|
||||
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
|
||||
|
||||
import {
|
||||
useVbenVxeGrid,
|
||||
vxeCheckboxChecked,
|
||||
type VxeGridProps
|
||||
} from '#/adapter/vxe-table'
|
||||
type VxeGridProps,
|
||||
} from '#/adapter/vxe-table';
|
||||
|
||||
import {
|
||||
personExport,
|
||||
personList,
|
||||
personRemove, personUpdate,
|
||||
} from '#/api/property/resident/person'
|
||||
import type { PersonForm } from '#/api/property/resident/person/model'
|
||||
import { commonDownloadExcel } from '#/utils/file/download'
|
||||
|
||||
import personModal from './person-modal.vue'
|
||||
import personDetail from './person-detail.vue'
|
||||
import personImportModal from './person-import-modal.vue'
|
||||
import faceImportModal from './face-import-modal.vue'
|
||||
import { columns, querySchema } from './data'
|
||||
import { useAccess } from "@vben/access"
|
||||
import { TableSwitch } from "#/components/table"
|
||||
personRemove,
|
||||
personUpdate,
|
||||
} from '#/api/property/resident/person';
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
@@ -36,7 +34,7 @@ const formOptions: VbenFormProps = {
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
}
|
||||
};
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
checkboxConfig: {
|
||||
@@ -64,8 +62,6 @@ const gridOptions: VxeGridProps = {
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
console.log(res);
|
||||
|
||||
return res;
|
||||
},
|
||||
},
|
||||
@@ -74,75 +70,80 @@ const gridOptions: VxeGridProps = {
|
||||
keyField: 'id',
|
||||
},
|
||||
// 表格全局唯一表示 保存列配置需要用到
|
||||
id: 'property-person-index'
|
||||
}
|
||||
id: 'property-person-index',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
formOptions,
|
||||
gridOptions,
|
||||
})
|
||||
});
|
||||
|
||||
const [PersonModal, modalApi] = useVbenModal({
|
||||
connectedComponent: personModal,
|
||||
})
|
||||
});
|
||||
const [PersonDetail, personDetailApi] = useVbenModal({
|
||||
connectedComponent: personDetail,
|
||||
})
|
||||
});
|
||||
const [PersonImportModal, personImportModalApi] = useVbenModal({
|
||||
connectedComponent: personImportModal,
|
||||
})
|
||||
});
|
||||
const [FaceImportModal, faceImportModalApi] = useVbenModal({
|
||||
connectedComponent: faceImportModal,
|
||||
})
|
||||
});
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
function handleAdd() {
|
||||
modalApi.setData({})
|
||||
modalApi.open()
|
||||
modalApi.setData({});
|
||||
modalApi.open();
|
||||
}
|
||||
|
||||
function handleImport() {
|
||||
personImportModalApi.open()
|
||||
personImportModalApi.open();
|
||||
}
|
||||
|
||||
function handleFaceImport() {
|
||||
faceImportModalApi.open()
|
||||
faceImportModalApi.open();
|
||||
}
|
||||
|
||||
async function handleEdit(row: Required<PersonForm>) {
|
||||
modalApi.setData({ id: row.id })
|
||||
modalApi.open()
|
||||
modalApi.setData({ id: row.id });
|
||||
modalApi.open();
|
||||
}
|
||||
|
||||
async function handleDelete(row: Required<PersonForm>) {
|
||||
await personRemove(row.id)
|
||||
await tableApi.query()
|
||||
await personRemove(row.id);
|
||||
await tableApi.query();
|
||||
}
|
||||
|
||||
function handleMultiDelete() {
|
||||
const rows = tableApi.grid.getCheckboxRecords()
|
||||
const ids = rows.map((row: Required<PersonForm>) => row.id)
|
||||
const rows = tableApi.grid.getCheckboxRecords();
|
||||
const ids = rows.map((row: Required<PersonForm>) => row.id);
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
okType: 'danger',
|
||||
content: `确认删除选中的${ids.length}条记录吗?`,
|
||||
onOk: async () => {
|
||||
await personRemove(ids)
|
||||
await tableApi.query()
|
||||
await personRemove(ids);
|
||||
await tableApi.query();
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function handleDownloadExcel() {
|
||||
commonDownloadExcel(personExport, '入驻员工数据', tableApi.formApi.form.values, {
|
||||
fieldMappingTime: formOptions.fieldMappingTime,
|
||||
})
|
||||
commonDownloadExcel(
|
||||
personExport,
|
||||
'入驻员工数据',
|
||||
tableApi.formApi.form.values,
|
||||
{
|
||||
fieldMappingTime: formOptions.fieldMappingTime,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function handleInfo(row: Required<PersonForm>) {
|
||||
personDetailApi.setData({ id: row.id })
|
||||
personDetailApi.open()
|
||||
personDetailApi.setData({ id: row.id });
|
||||
personDetailApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -151,20 +152,33 @@ function handleInfo(row: Required<PersonForm>) {
|
||||
<BasicTable table-title="入驻员工列表">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button v-access:code="['property:person:export']" @click="handleDownloadExcel">
|
||||
<a-button
|
||||
v-access:code="['property:person:export']"
|
||||
@click="handleDownloadExcel"
|
||||
>
|
||||
{{ $t('pages.common.export') }}
|
||||
</a-button>
|
||||
<a-button v-access:code="['system:user:import']" @click="handleImport">
|
||||
<a-button
|
||||
v-access:code="['system:user:import']"
|
||||
@click="handleImport"
|
||||
>
|
||||
{{ $t('pages.common.import') }}
|
||||
</a-button>
|
||||
<a-button @click="handleFaceImport">
|
||||
人脸导入
|
||||
</a-button>
|
||||
<a-button :disabled="!vxeCheckboxChecked(tableApi)" danger type="primary"
|
||||
v-access:code="['property:person:remove']" @click="handleMultiDelete">
|
||||
<a-button @click="handleFaceImport"> 人脸导入 </a-button>
|
||||
<a-button
|
||||
:disabled="!vxeCheckboxChecked(tableApi)"
|
||||
danger
|
||||
type="primary"
|
||||
v-access:code="['property:person:remove']"
|
||||
@click="handleMultiDelete"
|
||||
>
|
||||
{{ $t('pages.common.delete') }}
|
||||
</a-button>
|
||||
<a-button type="primary" v-access:code="['property:person:add']" @click="handleAdd">
|
||||
<a-button
|
||||
type="primary"
|
||||
v-access:code="['property:person:add']"
|
||||
@click="handleAdd"
|
||||
>
|
||||
{{ $t('pages.common.add') }}
|
||||
</a-button>
|
||||
</Space>
|
||||
@@ -173,17 +187,38 @@ function handleInfo(row: Required<PersonForm>) {
|
||||
<Avatar :src="row.img" v-if="row.img" />
|
||||
<span v-else>无</span>
|
||||
</template>
|
||||
<template #eeightId="{ row }">
|
||||
<tag v-if="row.eeightId" color="green">成功</tag>
|
||||
<tag v-else color="orange">未授权</tag>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<Space>
|
||||
<ghost-button @click.stop="handleInfo(row)">
|
||||
{{ $t('pages.common.info') }}
|
||||
</ghost-button>
|
||||
<ghost-button v-access:code="['property:person:edit']" @click.stop="handleEdit(row)">
|
||||
<ghost-button
|
||||
v-access:code="['property:person:edit']"
|
||||
@click.stop="handleEdit(row)"
|
||||
>
|
||||
{{ $t('pages.common.edit') }}
|
||||
</ghost-button>
|
||||
<Popconfirm :get-popup-container="getVxePopupContainer" placement="left" title="确认删除?"
|
||||
@confirm="handleDelete(row)">
|
||||
<ghost-button danger v-access:code="['property:person:remove']" @click.stop="">
|
||||
<ghost-button
|
||||
v-if="!row.eeightId"
|
||||
style="color: orange; border-color: orange"
|
||||
>
|
||||
授权
|
||||
</ghost-button>
|
||||
<Popconfirm
|
||||
:get-popup-container="getVxePopupContainer"
|
||||
placement="left"
|
||||
title="确认删除?"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<ghost-button
|
||||
danger
|
||||
v-access:code="['property:person:remove']"
|
||||
@click.stop=""
|
||||
>
|
||||
{{ $t('pages.common.delete') }}
|
||||
</ghost-button>
|
||||
</Popconfirm>
|
||||
|
Reference in New Issue
Block a user