# Conflicts:
#	pages/views/shengchan/ribaoshuju/rbsjLsxq.vue
#	pages/views/shengchan/ribaoshuju/trqRbsj.vue
#	uni_modules/cxc-szcx-dateRangeSelect/components/cxc-szcx-dateRangeSelect/cxc-szcx-dateRangeSelect.vue
This commit is contained in:
廖德云 2025-03-12 21:50:04 +08:00
commit 9556ecb346
8 changed files with 843 additions and 328 deletions

View File

@ -1,4 +1,4 @@
# 开发环境 # 开发环境
# 请求接口地址 # 请求接口地址
VITE_REQUEST_BASE_URL = https://36.112.48.190 VITE_REQUEST_BASE_URL = https://36.112.48.190
#VITE_REQUEST_BASE_URL = http://10.75.15.249:8080 #VITE_REQUEST_BASE_URL = http://10.75.166.6:8080

View File

@ -1,13 +1,15 @@
<template> <template>
<view> <view>
<view class="stats-container"> <view class="stats-container">
<uni-title :title="name.unit + '历史数据---单位(万方)'" color="blue" type="h2"></uni-title> <uni-title v-if="type === 'trq'" :title="name.unit + '天然气---单位(万方)'" color="blue" type="h2"></uni-title>
<uni-title v-if="type === 'yy'" :title="name.dw + '原油---单位(吨)'" color="blue" type="h2"></uni-title>
</view> </view>
<view class="dateSelect"> <view class="dateSelect">
<cxc-szcx-dateRangeSelect v-model="dateRange"></cxc-szcx-dateRangeSelect> <cxc-szcx-dateRangeSelect :mode="'range'" v-model="dateRange"></cxc-szcx-dateRangeSelect>
</view> </view>
<cxc-szcx-lineChart v-if="type === 'trq'" :dataList="dataList" x-field="rqDate" y-field="rq" legend-field="unit" :reference-value="0"></cxc-szcx-lineChart>
<cxc-szcx-lineChart :dataList="dataList" x-field="rqDate" y-field="rq" legend-field="unit" :reference-value="0"></cxc-szcx-lineChart> <cxc-szcx-lineChart v-if="type === 'yy'" :dataList="dataList" x-field="scrq" y-field="rcwy" legend-field="dw" :reference-value="0"></cxc-szcx-lineChart>
<view style="margin: 0 15px"> <view style="margin: 0 15px">
<view class="stats-container"> <view class="stats-container">
<view class="stats-item">最大值: {{ dataStats.max }}</view> <view class="stats-item">最大值: {{ dataStats.max }}</view>
@ -15,7 +17,7 @@
<view class="stats-item">平均值: {{ dataStats.average }}</view> <view class="stats-item">平均值: {{ dataStats.average }}</view>
</view> </view>
<view class="table"> <view v-if="type === 'trq'" class="table-container">
<!-- 表头 --> <!-- 表头 -->
<view class="tr header"> <view class="tr header">
<view class="th1">序号</view> <view class="th1">序号</view>
@ -23,7 +25,7 @@
<view class="th">日期</view> <view class="th">日期</view>
<view class="th">日气量</view> <view class="th">日气量</view>
</view> </view>
<scroll-view scroll-X="true" scroll-Y="true" class="table-container"> <scroll-view scroll-Y="true" class="scroll-wrapper">
<!-- 表格内容 --> <!-- 表格内容 -->
<view class="tr" v-for="(item, index) in dataList" :key="index" :class="{ even: index % 2 === 0 }"> <view class="tr" v-for="(item, index) in dataList" :key="index" :class="{ even: index % 2 === 0 }">
<view class="td1">{{ index + 1 }}</view> <view class="td1">{{ index + 1 }}</view>
@ -38,6 +40,30 @@
<view v-if="!dataList.length" class="empty">暂无相关数据</view> <view v-if="!dataList.length" class="empty">暂无相关数据</view>
</scroll-view> </scroll-view>
</view> </view>
<view v-if="type === 'yy'" class="table-container">
<!-- 表头 -->
<view class="tr header">
<view class="th1">序号</view>
<view class="th">名称</view>
<view class="th">日期</view>
<view class="th">日油量</view>
</view>
<scroll-view scroll-Y="true" class="scroll-wrapper">
<!-- 表格内容 -->
<view class="tr" v-for="(item, index) in dataList" :key="index" :class="{ even: index % 2 === 0 }">
<view class="td1">{{ index + 1 }}</view>
<view class="td">{{ item.dw }}</view>
<view class="td">{{ item.scrq }}</view>
<view class="td" :class="{ negative: item.rcwy < 0 }">
{{ item.rcwy }}
</view>
</view>
<!-- 空数据提示 -->
<view v-if="!dataList.length" class="empty">暂无相关数据</view>
</scroll-view>
</view>
</view> </view>
</view> </view>
</template> </template>
@ -53,7 +79,11 @@ const dateRange = ref([]);
const dataList = ref([]); const dataList = ref([]);
const endDate = ref(''); const endDate = ref('');
const startDate = ref(''); const startDate = ref('');
const dataStats = ref({ min: 0, max: 0, avg: 0 }); const dataStats = ref({
min: 0,
max: 0,
avg: 0
});
const getJinriShengchansj = (tempDateRange) => { const getJinriShengchansj = (tempDateRange) => {
// console.log(tempDateRange); // console.log(tempDateRange);
// //
@ -99,7 +129,7 @@ const getJinriYuanyouShengchansj = (tempDateRange) => {
queryParms.pageSize = 500; queryParms.pageSize = 500;
// //
if (!queryParms.rqDate_begin || !queryParms.rqDate_end) { if (!queryParms.scrq_begin || !queryParms.scrq_end) {
console.error('参数格式化失败:', queryParms); console.error('参数格式化失败:', queryParms);
return; return;
} }
@ -108,7 +138,7 @@ const getJinriYuanyouShengchansj = (tempDateRange) => {
if (res.success) { if (res.success) {
console.log(res); console.log(res);
dataList.value = res.result.records; dataList.value = res.result.records;
dataStats.value = calculateStats(dataList.value, 'rcwy'); dataStats.value = calculateStats(dataList.value, 'rcwy').reverse();
} }
}); });
}; };
@ -151,6 +181,7 @@ function calculateStats(data, field) {
average: average average: average
}; };
} }
function convertToDate(str) { function convertToDate(str) {
try { try {
const date = new Date(str); const date = new Date(str);
@ -187,7 +218,10 @@ watch(
console.warn('未知类型:', newType); console.warn('未知类型:', newType);
} }
}, },
{ immediate: true, deep: true } {
immediate: true,
deep: true
}
); );
onMounted(() => { onMounted(() => {
// nextTick(); // nextTick();
@ -219,67 +253,64 @@ onLoad((options) => {
overflow: hidden; overflow: hidden;
} }
.table { .scroll-wrapper {
min-width: 100%; width: 100%;
border: 2rpx solid #e8e8e8; height: 35vh;
}
.tr { .tr {
display: flex; display: flex;
border-bottom: 2rpx solid #e8e8e8; min-height: 14px;
font-size: 12px;
border-bottom: 1px solid #e8e8e8;
}
&.header { .th,
background-color: #fafafa; .td {
font-weight: 600;
}
&.even {
background-color: #f8f8f8;
}
}
.th,
.td {
flex: 1; flex: 1;
min-width: 80rpx; min-width: 80px;
padding: 10rpx; padding: 10px;
font-size: 22rpx; font-size: 12px;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
height: 30rpx; height: 18px;
vertical-align: middle; vertical-align: middle;
} overflow: hidden;
text-overflow: ellipsis;
}
.th1, .th1,
.td1 { .td1 {
flex: 1; flex: 1;
max-width: 40rpx; max-width: 40px;
padding: 10rpx; padding: 10px;
font-size: 22rpx; font-size: 12px;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
height: 30rpx; height: 14px;
vertical-align: middle; vertical-align: middle;
} }
.th { .th {
background-color: #f0f0f0; background-color: #f0f0f0;
} }
.td.negative { .td.negative {
color: #ff4444; color: #ff4444;
font-weight: 500; font-weight: 500;
}
} }
.empty { .empty {
padding: 40rpx; padding: 40px;
text-align: center; text-align: center;
color: #888; color: #888;
font-size: 16rpx; font-size: 16px;
} }
.stats-container { .stats-container {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
@ -291,6 +322,7 @@ onLoad((options) => {
padding: 10px; padding: 10px;
margin: 15 15px; margin: 15 15px;
} }
.dateSelect { .dateSelect {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
@ -302,6 +334,7 @@ onLoad((options) => {
padding: 5px; padding: 5px;
margin: 10px; margin: 10px;
} }
.stats-item { .stats-item {
font-size: 12px; font-size: 12px;
color: #00aa00; color: #00aa00;

View File

@ -1,13 +1,23 @@
<template> <template>
<view :class="{ gray: store.isgray == 1 }"> <view :class="{ gray: store.isgray == 1 }">
<view class="progress-bar"> <view style="padding: 0 10px">
<view class="progress-bartime">
<!-- 动态设置宽度和颜色 --> <!-- 动态设置宽度和颜色 -->
<view class="progressTime" :style="{ width: `${timePercent}%`, 'background-color': '#00aaff' }"></view> <view class="progressTime" :style="{ width: `${timePercent}%`, 'background-color': '#00aaff' }"></view>
<!-- 显示带符号的百分比 --> <!-- 显示带符号的百分比 -->
<text class="progress-text">全年时间进度:{{ timePercent }}%</text> <text class="progress-text">全年时间进度:{{ timePercent }}%</text>
</view> </view>
</view>
<view class="content">
<!-- 标题行 -->
<view class="header-row">
<view class="title">天然气产量</view>
<view class="more" @click="selectMore">更多 </view>
</view>
</view>
<view class="container"> <view class="container">
<view v-for="(item, index) in shishiArr" :key="index" class="card-item" @click="handleCardClick(item.gas)"> <view v-for="(item, index) in shishiArrSelect" :key="index" class="card-item" @click="handleCardClick(item.gas)">
<view class="card"> <view class="card">
<text class="title">{{ item.gas }}</text> <text class="title">{{ item.gas }}</text>
<view class="content"> <view class="content">
@ -27,6 +37,20 @@
</view> </view>
</view> </view>
</view> </view>
<uni-popup ref="popupSelect" type="top" background-color="#fff">
<view style="margin-top: 50px">
<view class="titlepopup">选择显示更多生产数据</view>
<uni-data-checkbox
style="font-size: 14px"
@change="onselectionchange"
:localdata="shishiArr"
v-model="shishiArrDisplay"
multiple
:map="{ value: 'gas', text: 'gas' }"
></uni-data-checkbox>
</view>
</uni-popup>
<!-- 数据弹窗 --> <!-- 数据弹窗 -->
<uni-popup ref="popup" type="bottom" background-color="#fff"> <uni-popup ref="popup" type="bottom" background-color="#fff">
<view class="popup-content"> <view class="popup-content">
@ -79,29 +103,51 @@ import { getYearProgress } from '@/utils/dateTime.js';
const store = useStore(); const store = useStore();
const shishiArr = ref([]); const shishiArr = ref([]);
const shishiArrSelect = ref([]);
const shishiArrDisplay = ref(['气井气', '站线综合输差']);
const dataJinriUnit = ref([]); const dataJinriUnit = ref([]);
const selectedGas = ref(''); const selectedGas = ref('');
const filteredData = ref([]); const filteredData = ref([]);
const popup = ref(null); const popup = ref(null);
const popupSelect = ref(null);
const strDate = ref(''); const strDate = ref('');
const timePercent = ref(0); const timePercent = ref(0);
const dataJinri = ref([]);
const dataJinriSum = ref([]);
const dataJinriSumUnit = ref([]);
// //
// const handleCardClick = (gas) => {
// selectedGas.value = gas;
// let queryParms = {};
// filteredData.value = [];
// queryParms.day = strDate.value;
// queryParms.gas = gas;
// queryParms.unit = '23,';
// console.log(queryParms);
// queryJinriTrqShengchansj(queryParms).then((res) => {
// if (res.success) {
// console.log(res);
// filteredData.value = res.result;
// }
// });
// popup.value.open();
// };
function selectMore() {
popupSelect.value.open();
}
const onselectionchange = () => {
shishiArrSelect.value = [];
shishiArrDisplay.value.forEach((item) => {
shishiArrSelect.value.push(shishiArr.value.filter((item1) => item1.gas === item)[0]);
});
};
const handleCardClick = (gas) => { const handleCardClick = (gas) => {
selectedGas.value = gas; selectedGas.value = gas;
let queryParms = {}; filteredData.value = dataJinriSumUnit.value.filter((item) => item.gas === gas);
filteredData.value = [];
queryParms.day = strDate.value;
queryParms.gas = gas;
queryParms.unit = '文23气田,户部寨气田';
console.log(queryParms);
queryJinriTrqShengchansj(queryParms).then((res) => {
if (res.success) {
console.log(res);
filteredData.value = res.result;
}
});
popup.value.open(); popup.value.open();
}; };
@ -110,8 +156,9 @@ const closePopup = () => {
popup.value.close(); popup.value.close();
}; };
onMounted(() => { onMounted(() => {
getJinriShengchansj(); getJinriTrqShengchansj();
timePercent.value = getYearProgress(); timePercent.value = getYearProgress();
getJinriShengchansj();
}); });
// //
@ -127,7 +174,7 @@ const formatNumber = (num) => {
}; };
// //
watchEffect(() => { function calcZhsc(tempArray) {
let totalJinqi = { let totalJinqi = {
gas: '总进气', gas: '总进气',
dailyVolume: 0, dailyVolume: 0,
@ -156,7 +203,7 @@ watchEffect(() => {
yearPlan: '100', yearPlan: '100',
yearPerCent: 0 yearPerCent: 0
}; // }; //
shishiArr.value.forEach((item) => { tempArray.forEach((item) => {
if (item.gas === '站输差' || item.gas === '线输差') { if (item.gas === '站输差' || item.gas === '线输差') {
compositeZx.dailyVolume += parseFloat(item.dailyVolume) || 0; compositeZx.dailyVolume += parseFloat(item.dailyVolume) || 0;
compositeZx.yearVolume += parseFloat(item.yearVolume) || 0; compositeZx.yearVolume += parseFloat(item.yearVolume) || 0;
@ -176,13 +223,16 @@ watchEffect(() => {
compositeJc.dailyVolume = (-totalJinqi.dailyVolume + totalChuqi.dailyVolume).toFixed(4); compositeJc.dailyVolume = (-totalJinqi.dailyVolume + totalChuqi.dailyVolume).toFixed(4);
compositeJc.yearVolume = (-totalJinqi.yearVolume + totalChuqi.yearVolume).toFixed(4); compositeJc.yearVolume = (-totalJinqi.yearVolume + totalChuqi.yearVolume).toFixed(4);
compositeJc.yearPerCent = calcPercent(compositeJc.yearPlan, compositeJc.yearVolume); compositeJc.yearPerCent = calcPercent(compositeJc.yearPlan, compositeJc.yearVolume);
shishiArr.value.push(compositeZx);
shishiArr.value.push(compositeJc); tempArray.push(compositeZx);
// tempArray.push(compositeJc);
return tempArray;
// console.log(composite); // console.log(composite);
}); }
const getJinriShengchansj = () => { const getJinriTrqShengchansj = () => {
const now = new Date(); const now = new Date();
if (now.getHours() < 11) { if (now.getHours() < 11) {
strDate.value = formatDate(getDateAfterDays(now, -1)).toString(); //11 strDate.value = formatDate(getDateAfterDays(now, -1)).toString(); //11
@ -196,7 +246,7 @@ const getJinriShengchansj = () => {
// // console.log(queryParms); // // console.log(queryParms);
queryJinriTrqShengchansj(queryParms).then((res) => { queryJinriTrqShengchansj(queryParms).then((res) => {
if (res.success) { if (res.success) {
console.log(res); // console.log(1, res);
let temp = res.result; let temp = res.result;
temp.forEach((item) => { temp.forEach((item) => {
@ -205,15 +255,22 @@ const getJinriShengchansj = () => {
item.yearPerCent = calcPercent(item.yearPlan, item.yearVolume); item.yearPerCent = calcPercent(item.yearPlan, item.yearVolume);
} }
}); });
shishiArr.value = temp; // console.log(2, temp);
shishiArr.value = calcZhsc(temp);
nextTick();
onselectionchange();
console.log(3, shishiArr.value);
console.log(4, shishiArrSelect.value);
} }
}); });
}; };
function goHistory(val) { function goHistory(val) {
uni.navigateTo({ uni.navigateTo({
url: '/pages/views/shengchan/ribaoshuju/rbsjLsxq?data=' + JSON.stringify(val) + '&type=trq' url: '/pages/views/shengchan/ribaoshuju/rbsjLsxq?data=' + JSON.stringify(val) + '&type=trq'
}); });
} }
function calcPercent(yearJihua, yearShiji) { function calcPercent(yearJihua, yearShiji) {
// 0 // 0
// 100 // 100
@ -227,9 +284,180 @@ function calcPercent(yearJihua, yearShiji) {
} }
return parseFloat(percent.toFixed(2)); // return parseFloat(percent.toFixed(2)); //
} }
const getJinriShengchansj = () => {
const now = new Date();
if (now.getHours() < 11) {
strDate.value = formatDate(getDateAfterDays(now, -1)).toString(); //11
} else {
strDate.value = formatDate(now).toString();
}
let queryParms = {};
dataJinri.value = [];
dataJinriSum.value = [];
dataJinriSumUnit.value = [];
queryParms.rqDate = strDate.value;
queryParms.pageSize = 100;
// console.log(queryParms);
queryJinriShengchansj(queryParms).then((res) => {
if (res.success) {
// console.log(res);
dataJinri.value = res.result.records;
dataJinriSumUnit.value = sumByUnit(dataJinri.value); //gas unit rq cq totalGas
// console.log(dataJinriSumUnit.value);
// 使 nextTick DOM
nextTick();
getYearShengchansj(); //
}
});
};
const getYearShengchansj = () => {
const now = new Date();
let year = formatDate(now).split('-')[0];
let queryParms = {};
queryParms.yearStart = year;
queryParms.yearEnd = year;
// // console.log(2, queryParms.value);
queryYearShengchansj(queryParms).then((res) => {
if (res.success) {
try {
// // console.log(res.result);
let yearData = res.result[year];
// console.log(dataJinriSumUnit.value.length, dataJinriSumUnit.value, yearData.length,
// yearData);
dataJinriSumUnit.value.forEach((item) => {
yearData.forEach((itemYear) => {
// // console.log(item, itemYear);
if (item.unit === itemYear.unit) {
item.yearVolume = itemYear.yearSum || 0;
}
});
});
dataJinriSum.value = sumByGas(dataJinriSumUnit.value);
// console.log(dataJinriSum.value);
shishiArr.value.forEach((item) => {
dataJinriSum.value.forEach((itemjinri) => {
if (item.gas === itemjinri.gas) {
item.dailyVolume = itemjinri.rq.toFixed(4);
item.yearVolume = itemjinri.yearVolume.toFixed(4);
}
});
});
} catch (error) {
// console.log(error);
}
}
});
};
function sumByGas(records) {
// console.log(records);
const summaryMap = {};
try {
records.forEach((record) => {
const gas = record.gas;
if (!summaryMap[gas]) {
// gas
summaryMap[gas] = {
gas: gas,
rq: 0,
sq: 0,
totalGas: 0,
yearVolume: 0
};
}
// gas
summaryMap[gas].rq += record.rq || 0;
summaryMap[gas].sq += record.sq || 0;
summaryMap[gas].totalGas += record.totalGas || 0;
summaryMap[gas].yearVolume += record.yearVolume || 0;
});
return Object.values(summaryMap);
} catch (error) {
//TODO handle the exception
// console.log(error);
}
}
function sumByUnit(records) {
// console.log(records);
const summaryMap = {};
try {
records.forEach((record) => {
const unit = record.unit;
if (!unit.includes('区')) {
if (!summaryMap[unit]) {
// gas
summaryMap[unit] = {
unit: unit,
gas: record.gas,
rq: 0,
sq: 0,
totalGas: 0,
yearVolume: 0
};
}
// unit
summaryMap[unit].rq += record.rq || 0;
summaryMap[unit].sq += record.sq || 0;
summaryMap[unit].totalGas += record.totalGas || 0;
summaryMap[unit].yearVolume += record.yearVolume || 0;
}
});
return Object.values(summaryMap);
} catch (error) {
//TODO handle the exception
// console.log(error);
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.header-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10 10rpx;
border-bottom: 1rpx solid #eee;
}
.title {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.titlepopup {
font-size: 30rpx;
font-weight: bold;
color: #333;
text-align: center;
margin-top: 20px;
margin-top: 20px;
}
.more {
font-size: 26rpx;
color: #666;
cursor: pointer;
}
.more::after {
content: '';
width: 8rpx;
height: 8rpx;
border-top: 2rpx solid #666;
border-right: 2rpx solid #666;
transform: rotate(45deg);
margin-left: 8rpx;
vertical-align: middle;
}
/* 鼠标悬停效果 */
.more:hover {
color: #007aff;
}
.container { .container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -337,10 +565,10 @@ function calcPercent(yearJihua, yearShiji) {
} }
.card { .card {
background: #ffffff; background: #ececec;
border-radius: 16rpx; border-radius: 16rpx;
padding: 10rpx; padding: 15rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); box-shadow: 0 4rpx 12rpx rgba(179, 179, 179, 0.1);
.title { .title {
display: block; display: block;
@ -384,14 +612,23 @@ function calcPercent(yearJihua, yearShiji) {
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
} }
.progress-bartime {
position: relative;
height: 20px;
background: #f0f0f0;
border-radius: 10px;
overflow: hidden;
margin-bottom: 30rpx;
}
.progress { .progress {
height: 100%; height: 100%;
transition: all 0.3s; transition: all 0.3s;
} }
.progressTime { .progressTime {
height: 100%; height: 100%;
transition: all 0.3s; transition: all 0.3s;
padding: 0 20px;
} }
.progress-text { .progress-text {
@ -399,9 +636,11 @@ function calcPercent(yearJihua, yearShiji) {
left: 50%; left: 50%;
top: 50%; top: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
color: red; /* 保持红色 */ color: red;
/* 保持红色 */
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); /* 提升可读性 */ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
/* 提升可读性 */
} }
</style> </style>

View File

@ -2,10 +2,18 @@
<view :class="{ gray: store.isgray == 1 }"> <view :class="{ gray: store.isgray == 1 }">
<!-- <view style="margin-left: 20rpx;"> <uni-title :title="strDate + ':生产数据'" type="h1" color="red" /> --> <!-- <view style="margin-left: 20rpx;"> <uni-title :title="strDate + ':生产数据'" type="h1" color="red" /> -->
<!-- </view> --> <!-- </view> -->
<view class="content">
<!-- 标题行 -->
<view class="header-row">
<view class="title">原油产量</view>
<view class="more"></view>
</view>
</view>
<view class="container"> <view class="container">
<view v-for="(item, index) in shishiArr" :key="index" class="card-item" @click="handleCardClick(item.gas)"> <view v-for="(item, index) in shishiArr" :key="index" class="card-item" @click="handleCardClick(item.gas)">
<view class="card"> <view class="card">
<text class="title">{{ item.gas }}</text> <!-- <text class="title">{{ item.gas }}</text> -->
<view class="content"> <view class="content">
<text class="label">日油量</text> <text class="label">日油量</text>
<text class="value">{{ item.rcwy || '-' }}</text> <text class="value">{{ item.rcwy || '-' }}</text>
@ -200,6 +208,41 @@ function sumByOil(records) {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.header-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15rpx 0;
border-bottom: 1rpx solid #eee;
}
.title {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.more {
font-size: 26rpx;
color: #666;
cursor: pointer;
}
.more::after {
content: '';
width: 8rpx;
height: 8rpx;
border-top: 2rpx solid #666;
border-right: 2rpx solid #666;
transform: rotate(45deg);
margin-left: 8rpx;
vertical-align: middle;
}
/* 鼠标悬停效果 */
.more:hover {
color: #007aff;
}
.container { .container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -304,10 +347,10 @@ function sumByOil(records) {
} }
.card { .card {
background: #ffffff; background: #ececec;
border-radius: 16rpx; border-radius: 16rpx;
padding: 10rpx; padding: 15rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); box-shadow: 0 4rpx 12rpx rgba(197, 197, 197, 0.1);
.title { .title {
display: block; display: block;

View File

@ -5,44 +5,41 @@
<view class="compact-input" @click="openPicker"> <view class="compact-input" @click="openPicker">
{{ dateRange[0] || '开始日期' }} {{ dateRange[0] || '开始日期' }}
</view> </view>
<text class="separator"></text> <text v-if="mode==='range'" class="separator"></text>
<view class="compact-input" @click="openPicker"> <view v-if="mode==='range'" class="compact-input" @click="openPicker">
{{ dateRange[1] || '结束日期' }} {{ dateRange[1] || '结束日期' }}
</view> </view>
</view> </view>
<wu-calendar <wu-calendar ref="calendar" :mode="mode" :date="dateRange" @confirm="calendarConfirm"
ref="calendar" slideSwitchMode="horizontal" type="month" :fold="false" :insert="false" :rangeSameDay="true" :lunar="true"
mode="range" :monthShowCurrentMonth="false" :range-end-repick="true" :item-height="45"
:date="dateRange" :rangeEndRepick="true"></wu-calendar>
@confirm="calendarConfirm"
slideSwitchMode="horizontal"
type="month"
:fold="false"
:insert="false"
:rangeSameDay="true"
:lunar="true"
:monthShowCurrentMonth="false"
:range-end-repick="true"
:item-height="45"
:rangeEndRepick="true"
></wu-calendar>
</view> </view>
</template> </template>
<script> <script>
import { formatDate, getDateAfterDays, getDateAfterMonths } from '@/utils/dateTime.js'; import {
export default { formatDate,
getDateAfterDays,
getDateAfterMonths
} from '@/utils/dateTime.js';
export default {
props: { props: {
modelValue: { modelValue: {
type: Array, type: Array,
default: () => [null, null] default: () => [null, null]
} },
//
mode: {
type: String,
default: 'single'
},
}, },
data() { data() {
return { return {
dateRange: [] dateRange: null
}; };
}, },
@ -50,9 +47,28 @@ export default {
modelValue: { modelValue: {
immediate: true, immediate: true,
handler(newVal) { handler(newVal) {
console.log(44, JSON.stringify(newVal))
let dateType = Object.prototype.toString.call(newVal);
if (this.mode == 'single' && dateType != '[object String]') {
return console.error(`类型错误mode=${this.mode}date=String`);
} else if (this.mode != 'single' && dateType != '[object Array]') {
return console.error(`类型错误mode=${this.mode}date=Array`);
}
if (this.mode == 'single') {
this.dateRange = ''
this.dateRange = newVal;
}
//
if (this.mode == 'multiple') {
this.dateRange = []
this.dateRange = newVal;
} else if (this.mode == 'range') {
this.dateRange = []
this.dateRange.push(formatDate(new Date(newVal[0]))); this.dateRange.push(formatDate(new Date(newVal[0])));
this.dateRange.push(formatDate(new Date(newVal[1]))); this.dateRange.push(formatDate(new Date(newVal[1])));
} }
}
} }
}, },
@ -72,17 +88,18 @@ export default {
this.$emit('update:modelValue', this.dateRange); this.$emit('update:modelValue', this.dateRange);
} }
} }
}; };
</script> </script>
<style scoped> <style scoped>
.input-container { .input-container {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
} }
/* 紧凑型输入框 */
.compact-input { /* 紧凑型输入框 */
.compact-input {
flex: 1; flex: 1;
padding: 6px 8px; padding: 6px 8px;
font-size: 16px; font-size: 16px;
@ -92,10 +109,10 @@ export default {
border-radius: 4px; border-radius: 4px;
text-align: center; text-align: center;
width: 120px; width: 120px;
} }
.compact-input.active { .compact-input.active {
border-color: #409eff; border-color: #409eff;
background-color: #f5f7ff; background-color: #f5f7ff;
} }
</style> </style>

View File

@ -1,139 +1,162 @@
# cxc-szcx-dateRangeSelect
# # 紧凑型日期时间选择器组件说明书
## 一、组件概述 ### 紧凑型日期时间选择器组件说明
`compact-datetime-picker` 是一个基于 Vue 开发的紧凑型日期时间选择器组件,用于在页面中选择日期范围。组件提供了自然周期和相对周期两种选择模式,并且支持快捷选择本周、本月、本季、本年、近一周、近一月、近一季、近一年等常见时间范围。
## 二、组件依赖
- **Vue**:组件基于 Vue 框架开发。
- **uni-popup**:来自 `uni-app` 的弹窗组件,用于显示日期选择器的弹出层。
## 三、组件使用方法 #### 一、组件功能
该组件是基于 UniApp 开发的紧凑型日期选择器,支持以下功能:
1. **三种选择模式**
- 单选single
- 范围选择range
- 多选multiple
2. **弹出式日历选择**
- 支持月份滑动切换
- 显示农历日期
- 支持同天范围选择
3. **数据双向绑定**
- 通过 `modelValue` 实现父子组件数据同步
4. **自定义样式**
- 紧凑型输入框设计
- 支持自定义主题颜色
- 响应式布局适配
### 1. 引入组件
在需要使用该组件的 Vue 文件中,引入 `compact-datetime-picker` 组件。 #### 二、组件Props
| 参数名 | 类型 | 默认值 | 说明 |
|--------------|------------|----------|--------------------------|
| `modelValue` | Array/Date | `[null]` | 双向绑定的值(根据模式变化) |
| `mode` | String | `single` | 选择模式single/range/multiple |
#### 三、组件数据
| 名称 | 类型 | 说明 |
|------------|--------|--------------------------|
| `dateRange` | Array | 当前选中的日期范围(内部状态) |
#### 四、组件方法
| 方法名 | 参数 | 说明 |
|----------------|------------|--------------------------|
| `openPicker` | 无 | 打开日历选择器 |
| `calendarConfirm` | `e` | 日历确认回调(处理选中数据) |
#### 五、事件说明
| 事件名 | 说明 | 返回值 |
|--------------|--------------------------|----------------------|
| `update:modelValue` | 数据更新时触发 | 当前选中的日期数组 |
#### 六、样式说明
```vue
<style scoped>
.input-container {
display: flex;
align-items: center;
gap: 10px;
}
.compact-input {
flex: 1;
padding: 6px 8px;
font-size: 16px;
height: 20px;
line-height: 20px;
border: 1px solid #dcdfe6;
border-radius: 4px;
text-align: center;
width: 120px;
}
.compact-input.active {
border-color: #409eff;
background-color: #f5f7ff;
}
</style>
```
**关键样式点**
1. 输入框宽度固定为 120px
2. 紧凑型设计height: 20px
3. 活动状态样式增强
4. 水平排列布局
#### 七、使用示例
```vue ```vue
<template> <template>
<view> <view>
<compact-datetime-picker v-model="dateRange" /> <!-- 单选模式 -->
<compact-datetime-picker
v-model="singleDate"
mode="single"
/>
<!-- 范围选择模式 -->
<compact-datetime-picker
v-model="rangeDate"
mode="range"
/>
<!-- 多选模式 -->
<compact-datetime-picker
v-model="multipleDate"
mode="multiple"
/>
</view> </view>
</template> </template>
<script> <script>
import CompactDateTimePicker from '@/components/compact-datetime-picker.vue';
export default { export default {
components: {
CompactDateTimePicker
},
data() { data() {
return { return {
dateRange: [null, null] singleDate: null,
rangeDate: [null, null],
multipleDate: []
}; };
} }
}; };
</script> </script>
``` ```
### 2. 组件属性
| 属性名 | 类型 | 默认值 | 描述 |
| ---- | ---- | ---- | ---- |
| `modelValue` | `Array` | `[null, null]` | 双向绑定的日期范围数组,第一个元素为开始日期,第二个元素为结束日期。 |
## 四、组件内部实现 #### 八、注意事项
1. **日期格式要求**
### 1. 模板部分 - 单选模式:`YYYY-MM-DD` 字符串格式
- **输入框区域**:显示开始日期和结束日期的输入框,点击输入框可打开日期选择弹窗。 - 范围模式:`[startDate, endDate]` 数组格式
- **日期选择弹窗** - 多选模式:`[date1, date2, ...]` 数组格式
- **快捷按钮区域**:包含自然周期和相对周期的切换按钮,以及不同模式下的快捷操作按钮。 2. **依赖组件**
- **日历选择区域**:显示当前月份的日历,可选择日期。 - 需安装 `wu-calendar` 组件(第三方库)
- **操作按钮**:包含取消和确定按钮,用于关闭弹窗和确认选择的日期范围。 3. **异常处理**
```javascript
### 2. 脚本部分 calendarConfirm(e) {
this.dateRange = [];
#### 2.1 数据属性 try {
```javascript this.dateRange.push(formatDate(new Date(e.range.before)));
data() { this.dateRange.push(formatDate(new Date(e.range.after)));
return { } catch (error) {
isNatural: true, // 是否为自然周期模式 return; // 异常处理
activeType: 'start', // 当前活跃的日期选择类型(开始或结束)
currentMonth: new Date(), // 当前显示的月份
tempStart: null, // 临时开始日期
tempEnd: null, // 临时结束日期
weekDays: ['日', '一', '二', '三', '四', '五', '六'], // 星期几的显示文本
quickButtonszr: [
{ label: '本周', type: 'week' },
{ label: '本月', type: 'month' },
{ label: '本季', type: 'quarter' },
{ label: '本年', type: 'year' }
], // 自然周期模式下的快捷按钮
quickButtonsxd: [
{ label: '近一周', type: 'week' },
{ label: '近一月', type: 'month' },
{ label: '近一季', type: 'quarter' },
{ label: '近一年', type: 'year' }
] // 相对周期模式下的快捷按钮
};
}
```
#### 2.2 计算属性
- `formattedStart`:格式化后的开始日期。
- `formattedEnd`:格式化后的结束日期。
- `calendarDays`:生成当前月份的日历数据。
- `monthText`:当前月份的显示文本。
#### 2.3 监听属性
```javascript
watch: {
modelValue: {
immediate: true,
handler(newVal) {
this.tempStart = newVal[0];
this.tempEnd = newVal[1];
} }
this.$emit('update:modelValue', this.dateRange);
} }
} ```
``` 4. **性能优化**
监听 `modelValue` 的变化,初始化临时日期。 - 建议使用 `:range-end-repick="true"` 开启范围重新选择
- 通过 `:item-height="45"` 控制日历行高
#### 2.4 方法
- **打开弹窗并初始化临时值**`openPicker(type)`,打开日期选择弹窗,并根据活跃类型初始化临时日期。
- **日期点击处理**`handleDayClick(day)`,处理日期点击事件,更新临时开始或结束日期。
- **确认选择**`confirmSelection()`,确认选择的日期范围,触发 `update:modelValue` 事件并关闭弹窗。
- **关闭弹窗**`closePicker()`,关闭日期选择弹窗,并重置临时日期。
- **重置临时日期**`resetTempDates()`,将临时日期重置为当前的 `modelValue`
- **生成日历数据**`generateCalendar(date)`,根据给定的月份生成日历数据。
- **日期选择处理**`selectDate(date)`,处理日期选择,更新 `modelValue`
- **切换月份**`changeMonth(offset)`,切换当前显示的月份。
- **判断日期状态**
- `isSelected(date)`:判断日期是否被选中。
- `isEnd(date)`:判断日期是否为结束日期。
- `isToday(date)`:判断日期是否为今天。
- `isStart(date)`:判断日期是否为开始日期。
- `isInRange(date)`:判断日期是否在选择的范围内。
- `isSameDay(d1, d2)`:判断两个日期是否为同一天。
- **快捷范围设置**`setQuickRange(type)`,根据快捷按钮的类型设置日期范围,并关闭弹窗。
- **自然周期计算**`calcNaturalRange(type, currentDate)`,计算自然周期模式下的日期范围。
- **相对周期计算**`calcRelativeRange(type, currentDate)`,计算相对周期模式下的日期范围。
- **周计算(周一为起点)**
- `getWeekStart(date)`:获取一周的开始日期。
- `getWeekEnd(startDate)`:获取一周的结束日期。
- **月末处理**`lastDayOfMonth(date)`,获取给定月份的最后一天。
- **月末日调整**`adjustMonthEnd(start, end)`,调整开始日期到合适的月末日期。
- **闰年判断**`isLeapYear(year)`,判断给定年份是否为闰年。
- **模式切换**`toggleMode(isNatural)`,切换自然周期和相对周期模式。
- **日期格式化**`formatDate(date)`,将日期格式化为 `YYYY-MM-DD` 的字符串。
### 3. 样式部分 #### 九、扩展功能建议
- **输入框区域**:设置输入框的样式,包括边框、背景色、字体大小等。 1. 添加时间选择功能
- **紧凑弹窗**:设置弹窗的整体样式,包括背景色、边框半径等。 2. 支持自定义日期禁用范围
- **紧凑头部**:设置月份标题和导航箭头的样式。 3. 增加国际化支持
- **紧凑日期网格**:设置星期几和日期的显示样式,以及不同状态下的日期背景色和字体颜色。 4. 添加动画过渡效果
- **快捷按钮区域**:设置模式切换按钮和快捷操作按钮的样式。 5. 支持预设常用日期范围
- **底部操作**:设置取消和确定按钮的样式。
## 五、注意事项 如果需要进一步定制化,可以修改以下部分:
- 组件使用了 `uni-popup` 组件,确保项目中已正确引入和配置。 1. 日历组件配置(`wu-calendar` 参数)
- 日期范围的选择逻辑较为复杂,特别是在自然周期和相对周期的切换以及快捷范围设置时,需注意日期的计算和显示。 2. 日期格式化函数(`formatDate`
- 组件的样式是基于 `scoped` 作用域的,可根据项目需求进行调整和扩展。 3. 输入框样式和交互逻辑
4. 异常处理机制

View File

@ -1 +1,90 @@
# cxc-szcx-dictSelect # cxc-szcx-dictSelect
### 组件说明:`uni-data-checkbox` 多选字典项选择组件
#### 一、组件概述
该组件基于 Vue 3 和 `uni-data-checkbox` 实现了一个多选的字典项选择器。它通过传入字典编码(`dictCode`)从后端接口获取字典项数据,并将其展示为多选框列表。组件支持双向数据绑定,可将选中的值同步给父组件,同时在选中值发生变化时触发 `change` 事件。
#### 二、组件使用的技术栈
- **框架**Vue 3使用组合式 API
- **UI 组件**`uni-data-checkbox`(用于展示多选框列表)
- **请求库**:假设使用了自定义的 `getDictItemsApi` 函数进行后端数据请求
#### 三、组件输入Props
| 属性名 | 类型 | 是否必填 | 默认值 | 说明 |
| ---- | ---- | ---- | ---- | ---- |
| `dictCode` | String | 是 | 无 | 用于从后端获取字典项数据的字典编码 |
| `modelValue` | String | 否 | '' | 双向绑定的选中值,多个值以逗号分隔 |
#### 四、组件输出Emits
| 事件名 | 说明 | 参数 |
| ---- | ---- | ---- |
| `update:modelValue` | 当选中值发生变化时,用于更新父组件的 `modelValue` | 选中值的字符串,多个值以逗号分隔 |
| `change` | 当选中值发生变化时触发 | 选中值的字符串,多个值以逗号分隔 |
#### 五、组件内部数据
| 变量名 | 类型 | 说明 |
| ---- | ---- | ---- |
| `dictItems` | Ref<Array> | 存储从后端获取的字典项数据 |
| `selectedValuesArray` | Ref<Array> | 存储当前选中的值,以数组形式存储 |
#### 六、组件方法
##### 1. `loadDictItems`
- **功能**:异步从后端接口获取字典项数据,并将其赋值给 `dictItems`
- **实现逻辑**
- 调用 `getDictItemsApi` 函数,传入 `props.dictCode` 进行数据请求。
- 若请求成功,将响应数据的 `result` 字段赋值给 `dictItems.value`
- 若请求失败,在控制台输出错误信息。
##### 2. 监听 `selectedValuesArray` 的变化
- **功能**:当 `selectedValuesArray` 发生变化时,将选中的值转换为字符串,并触发 `update:modelValue``change` 事件通知父组件。
- **实现逻辑**
- 使用 `watch` 函数监听 `selectedValuesArray` 的变化。
- 当变化发生时,将新的选中值数组使用 `join(',')` 方法转换为字符串。
- 触发 `update:modelValue``change` 事件,将转换后的字符串作为参数传递。
##### 3. 监听 `props.modelValue` 的变化
- **功能**:当父组件传递的 `modelValue` 发生变化时,更新 `selectedValuesArray`
- **实现逻辑**
- 使用 `watch` 函数监听 `props.modelValue` 的变化。
- 当变化发生时,将新的 `modelValue` 使用 `split(',')` 方法转换为数组,并赋值给 `selectedValuesArray.value`
- `immediate: true` 表示在组件初始化时就会执行一次监听回调,确保初始值能正确同步。
#### 七、组件生命周期钩子
##### `onMounted`
- **功能**:在组件挂载后调用 `loadDictItems` 函数,从后端获取字典项数据。
#### 八、使用示例
```vue
<template>
<div>
<MyDictCheckbox
:dictCode="myDictCode"
v-model="selectedValues"
@change="handleChange"
/>
<p>当前选中的值: {{ selectedValues }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
import MyDictCheckbox from './MyDictCheckbox.vue';
const myDictCode = 'exampleDictCode';
const selectedValues = ref('');
const handleChange = (newValue) => {
console.log('选中值发生变化:', newValue);
};
</script>
```
#### 九、注意事项
- 确保 `getDictItemsApi` 函数能正确获取后端数据,且响应数据的格式符合 `{ result: [...] }`
- 若 `modelValue` 初始值为空字符串,`selectedValuesArray` 会初始化为空数组。
- 由于使用了 `watch``deep: true` 选项,在 `selectedValuesArray` 内部元素发生变化时也会触发监听回调,可能会影响性能,需注意。

View File

@ -1,35 +1,106 @@
# trq-depart-select # trq-depart-select
# 1.0.1 ### 组件说明:单位选择组件
更新,添加返回值,将整个机构对象返回父组件
```javascript #### 一、组件概述
const onpopupclosed = ((e) => { 此组件为基于 UniApp 和 Vue 3 构建的单位选择组件,借助 `uni-data-picker` 组件实现单位的选择功能。组件支持依据传入的 `returnCodeOrID` 属性,返回单位的 `orgCode` 或者 `id`。组件会从后端获取单位列表数据,并在用户选择单位时触发 `change` 事件通知父组件。
selectDepartID.value = tempSelectDepartID.value
$emit('change', selectDepartID.value, departInfo) #### 二、组件使用的技术栈
}) - **框架**Vue 3使用组合式 API
- **状态管理**`@/store` (推测使用了 Vuex 或 Pinia 进行状态管理)
- **UI 组件**`uni-data-picker` (用于展示单位选择器)
- **请求库**:自定义的 API 请求函数(如 `queryMyDeptTreeListApi`
#### 三、组件输入Props
| 属性名 | 类型 | 是否必填 | 默认值 | 说明 |
| ---- | ---- | ---- | ---- | ---- |
| `returnCodeOrID` | String | 否 | "orgCode" | 决定返回单位的 `orgCode` 还是 `id` |
#### 四、组件输出Emits
| 事件名 | 说明 | 参数 |
| ---- | ---- | ---- |
| `change` | 当用户选择单位时触发,用于通知父组件选择结果 | 选中单位的 `orgCode``id`,以及单位的全部信息对象 |
#### 五、组件内部数据
| 变量名 | 类型 | 说明 |
| ---- | ---- | ---- |
| `departList` | Ref<Array> | 存储从后端获取的单位列表数据 |
| `selectDepartID` | Ref<String> | 当前选中的单位 ID |
| `selectDepartIDS` | Ref<Array> | 选中的级联单位 ID 数组 |
| `tempSelectDepartID` | Ref<String> | 临时选择的单位 ID |
| `depart` | Ref<Object> | 对 `uni-data-picker` 组件的引用 |
| `departInfo` | Ref<Object> | 选中单位的全部信息 |
#### 六、组件方法
##### 1. `getDepartList`
- **功能**:异步从后端获取单位列表数据,并将其赋值给 `departList`
- **实现逻辑**
- 调用 `queryMyDeptTreeListApi` 函数进行数据请求。
- 若请求成功,将响应数据的 `result` 字段赋值给 `departList.value`
- 若请求失败,在控制台输出错误信息。
##### 2. `onnodeclick`
- **功能**:处理用户点击单位节点的事件,更新 `departInfo``tempSelectDepartID`
- **实现逻辑**
- 将点击的单位信息赋值给 `departInfo.value`
- 根据 `props.returnCodeOrID` 的值,将点击单位的 `orgCode``value` 赋值给 `tempSelectDepartID.value`
##### 3. `onchange`
- **功能**:处理 `uni-data-picker``change` 事件,更新 `selectDepartID`
- **实现逻辑**:将选择的单位 ID 赋值给 `selectDepartID.value`
##### 4. `onpopupclosed`
- **功能**:处理 `uni-data-picker` 弹出框关闭的事件,将 `tempSelectDepartID` 的值赋给 `selectDepartID`
- **实现逻辑**:将 `tempSelectDepartID.value` 赋值给 `selectDepartID.value`
##### 5. 监听 `tempSelectDepartID` 的变化
- **功能**:当 `tempSelectDepartID` 发生变化时,触发 `change` 事件通知父组件。
- **实现逻辑**
- 使用 `watch` 函数监听 `tempSelectDepartID` 的变化。
- 当变化发生时,触发 `change` 事件,将 `tempSelectDepartID` 的新值和 `departInfo` 作为参数传递。
- `immediate: true` 表示在组件初始化时就会执行一次监听回调。
- `deep: true` 表示深度监听,确保 `tempSelectDepartID` 内部属性变化也能被捕获。
#### 七、组件生命周期钩子
##### `onLoad`
- **功能**:在页面加载时调用 `getDepartList` 函数,从后端获取单位列表数据。
#### 八、样式说明
```css
.no-wrap-picker::v-deep .uni-data-picker-item {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
``` ```
增加选择到最后层级触发的change时间返回数据到父组件的功能 - 此样式规则用于强制 `uni-data-picker` 的选项不换行,超出部分隐藏并显示省略号。
```javascript #### 九、使用示例
const onchange = ((e) => { ```vue
$emit('change', e.detail.value[e.detail.value.length - 1].value, {}) <template>
}) <div>
<UnitSelector
:returnCodeOrID="'id'"
@change="handleUnitChange"
/>
</div>
</template>
<script setup>
import UnitSelector from './UnitSelector.vue';
const handleUnitChange = (selectedId, unitInfo) => {
console.log('选中的单位 ID:', selectedId);
console.log('选中单位的信息:', unitInfo);
};
</script>
``` ```
#### 十、注意事项
- 确保 `queryMyDeptTreeListApi` 函数能正确获取后端数据,且响应数据的格式符合 `{ success: true, result: [...] }`
# 1.0 - 由于使用了 `watch``deep: true` 选项,在 `tempSelectDepartID` 内部元素发生变化时也会触发监听回调,可能会影响性能,需注意。
- 代码中注释掉的部分(如 `onnodeclick``onpopupclosed` 中触发 `change` 事件的代码)可能是之前的实现逻辑,若需要可根据实际情况恢复使用。
属性 returnCodeOrID 默认值 orgCode 组件返回单位的orgCode 不设置属性或设置为其他,组件返回 单位ID
事件change 选择内容发生变化时发生通过emit 返回给父组件 选择单位的ID或code
由于uni-data-picker 无法选择任意节点数据,通过关闭组件事件返回当前选择的节点数据。
```javascript
const onpopupclosed = ((e) => {
selectDepartID.value = tempSelectDepartID.value
$emit('change', selectDepartID.value)
})
```