This commit is contained in:
FLL
2025-09-15 10:17:21 +08:00
55 changed files with 3203 additions and 1080 deletions

View File

@@ -13,52 +13,63 @@ export function getIndexCount() {
export function getStatisticsCurrDay() {
return requestClient.get<any>('/sis/alarmEvents/query/statistics/currDay');
}
// 所有预警信息分类统计
export function getStatistics() {
return requestClient.get<any>('/sis/alarmEvents/query/statistics');
}
// 工单
export function getworkOrder() {
return requestClient.get<any>('/property/screen/typeWorkOrderHistogram');
return requestClient.get<any>(
'/property/cockpit/screen/typeWorkOrderHistogram',
);
}
// 会议室
export function getTodayMeetCount() {
return requestClient.get<any>('/property/screen/todayMeetCount');
return requestClient.get<any>('/property/cockpit/screen/todayMeetCount');
}
// 水电
export function getHydropower() {
return requestClient.get<any>('/property/meterInfo/statusCount');
}
// 门禁
export function getAccessControl() {
return requestClient.get<any>('/sis/e8/door/online');
}
// 摄像头状态
export function getCamera() {
return requestClient.get<any>('/sis/deviceManage/online');
}
// 访客
export function getVisitorCount() {
return requestClient.get<any>('/property/screen/todayVisitorCount');
return requestClient.get<any>('/property/cockpit/screen/todayVisitorCount');
}
//车流
export async function queryTwentyfourRunningDatasByPlNos() {
let params = {
"orgId": "10012",
"plNos": [
"PFN000000012",
"PFN000000025"
]
orgId: '10012',
plNos: ['PFN000000012', 'PFN000000025'],
};
const response = await fetch('https://server.cqnctc.com:6081/web/thirdParty/queryTwentyfourRunningDatasByPlNos', {
method: 'POST', // 指定请求方法为POST
headers: {
'Content-Type': 'application/json', // 设置内容类型为JSON
const response = await fetch(
'https://server.cqnctc.com:6081/web/thirdParty/queryTwentyfourRunningDatasByPlNos',
{
method: 'POST', // 指定请求方法为POST
headers: {
'Content-Type': 'application/json', // 设置内容类型为JSON
},
body: JSON.stringify(params), // 将参数对象转换为JSON字符串并作为请求体发送
},
body: JSON.stringify(params), // 将参数对象转换为JSON字符串并作为请求体发送
});
);
return response.json();
}
// /**
// * 导出资产管理列表
// * @param params

View File

@@ -65,6 +65,18 @@ export interface OrderChargeVO {
* 收费状态
*/
chargeStatus: string | number;
/**
* 单位
*/
residentUnitId: string;
/**
* 订单号
*/
orderNo?: string | number;
/**
* 订单内容
*/
rentalOrder?: any;
}
export interface OrderChargeForm extends BaseEntity {
@@ -196,8 +208,8 @@ export interface OrderChargeQuery extends PageQuery {
chargeStatus?: string | number;
/**
* 日期范围参数
*/
* 日期范围参数
*/
params?: any;
}
@@ -266,4 +278,8 @@ export interface orderChargeDetailForm extends BaseEntity {
* 收费状态
*/
chargeStatus?: string | number;
/**
* 租赁单位
*/
residentUnitText?: string;
}

View File

@@ -7,12 +7,15 @@ import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
/**
* 查询保洁订单列表
* @param params
* @returns 保洁订单列表
*/
* 查询保洁订单列表
* @param params
* @returns 保洁订单列表
*/
export function clean_orderList(params?: Clean_orderQuery) {
return requestClient.get<PageResult<Clean_orderVO>>('/property/clean_order/list', { params });
return requestClient.get<PageResult<Clean_orderVO>>(
'/property/clean_order/list',
{ params },
);
}
/**
@@ -59,3 +62,14 @@ export function clean_orderUpdate(data: Clean_orderForm) {
export function clean_orderRemove(id: ID | IDS) {
return requestClient.deleteWithMsg<void>(`/property/clean_order/${id}`);
}
/**
* 查询单位的房间列表
* @param params
* @returns 保洁订单列表
*/
export function getRoomsByresidentUnitId(residentUnitId?: string | number) {
return requestClient.get<any>(
`/property/clean_order/residentUnitId/${residentUnitId}`,
);
}

View File

@@ -70,6 +70,8 @@ export interface CostItemSettingVO {
* 搜索值
*/
searchValue: string;
chargeNo: string;
unit: string;
}

View File

@@ -54,6 +54,14 @@ export function houseChargeRefund(data: HouseChargeForm) {
export function houseChargeUpdate(data: HouseChargeForm) {
return requestClient.putWithMsg<void>('/property/houseCharge', data);
}
/**
* 更正房屋收费
* @param data
* @returns void
*/
export function houseChargeAddFee(data: HouseChargeForm) {
return requestClient.postWithMsg<void>('/property/houseCharge/addFee', data);
}
/**
* 删除房屋收费

View File

@@ -43,7 +43,7 @@ export interface HouseChargeVO {
*/
remark: string;
chargeTime: any[];
chargeTime: string;
/**
* 缴费周期
*/

View File

@@ -7,14 +7,22 @@ import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
/**
* 查询绿植租赁-订单管理列表
* @param params
* @returns 绿植租赁-订单管理列表
*/
* 查询绿植租赁-订单管理列表
* @param params
* @returns 绿植租赁-订单管理列表
*/
export function rentalOrderList(params?: RentalOrderQuery) {
return requestClient.get<PageResult<RentalOrderVO>>('/property/rentalOrder/list', { params });
return requestClient.get<PageResult<RentalOrderVO>>(
'/property/rentalOrder/list',
{ params },
);
}
export function rentalNotSelectList(params?: RentalOrderQuery) {
return requestClient.get<PageResult<RentalOrderVO>>(
'/property/rentalOrder/notSelectList',
{ params },
);
}
/**
* 导出绿植租赁-订单管理列表
* @param params

View File

@@ -86,6 +86,10 @@ export interface RentalOrderVO {
*/
signTime: string;
/**
* 单位名称
*/
residentUnitText: string;
}
export interface RentalOrderForm extends BaseEntity {
@@ -173,7 +177,6 @@ export interface RentalOrderForm extends BaseEntity {
* 签署时间
*/
signTime?: string;
}
export interface RentalOrderQuery extends PageQuery {
@@ -257,7 +260,7 @@ export interface RentalOrderQuery extends PageQuery {
signTime?: string;
/**
* 日期范围参数
*/
* 日期范围参数
*/
params?: any;
}

View File

@@ -246,4 +246,9 @@ export interface Unit extends BaseEntity {
*/
area?: string;
/**
* 房间数量
*/
roomNumber: number;
}

View File

@@ -2,9 +2,18 @@
import type { EchartsUIType } from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { onMounted, ref, defineExpose } from 'vue';
import { onMounted, ref, defineExpose, watch } from 'vue';
import { meterRecordTrend } from '#/api/property/energyManagement/meterRecord';
import dayjs from 'dayjs';
const props = defineProps({
meterType: {
type: Number,
default: 1,
},
});
const meterType = ref<Number>(1);
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);
// 添加日期选择相关逻辑
@@ -16,12 +25,12 @@ const getDaysInMonth = (date: any) => {
const daysInMonth = new Date(year, month, 0).getDate();
return Array.from({ length: daysInMonth }, (_, i) => i + 1);
};
const getMeterRecordTrend = async (selectedDate: any) => {
const getMeterRecordTrend = async (selectedDate: any, meterType: any) => {
const res = await meterRecordTrend({
day: dayjs().format('YYYY-MM-DD'),
month: selectedDate.value,
year: dayjs().format('YYYY'),
meterType: 1,
meterType: meterType.value,
day: null,
year: null,
meterId: null,
floorId: null,
});
@@ -109,7 +118,7 @@ const getMeterRecordTrend = async (selectedDate: any) => {
});
};
onMounted(async () => {
getMeterRecordTrend(selectedDate);
getMeterRecordTrend(selectedDate, meterType);
});
// 暴露方法给父组件调用
defineExpose({

View File

@@ -12,7 +12,7 @@ import AnalyticsVisitsData from './analytics-visits-data.vue';
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
import AnalyticsVisitsSource from './analytics-visits-source.vue';
import { ref, onMounted } from 'vue';
import { DatePicker } from 'ant-design-vue';
import { DatePicker, message } from 'ant-design-vue';
import { Radio } from 'ant-design-vue';
import {
getIndexCount,
@@ -52,7 +52,7 @@ const overviewItems = ref<any[]>([
]);
//tab选择
const timeUnit = ref(1);
const meterType = ref<Number>(1);
// 月份选择
const selectedDate = ref<any>(null);
const paramDate = ref(null);
@@ -65,12 +65,28 @@ const analyticsTrendsRef = ref<InstanceType<typeof AnalyticsTrends> | null>(
);
const handleDateChange = (date: any) => {
paramDate.value = date.format('YYYY-MM');
console.log('meterType', meterType);
// 调用子组件的方法
if (
analyticsTrendsRef.value &&
analyticsTrendsRef.value.getMeterRecordTrend
) {
analyticsTrendsRef.value.getMeterRecordTrend(paramDate);
analyticsTrendsRef.value.getMeterRecordTrend(paramDate, meterType);
}
};
const handleMeterTypeChange = (e: Event) => {
if (!selectedDate.value) {
message.warning('请选择月份');
return;
}
paramDate.value = selectedDate.value.format('YYYY-MM');
// 调用子组件的方法
if (
analyticsTrendsRef.value &&
analyticsTrendsRef.value.getMeterRecordTrend
) {
analyticsTrendsRef.value.getMeterRecordTrend(paramDate, meterType);
}
};
@@ -199,8 +215,13 @@ onMounted(() => {
class="flex items-center justify-between p-5"
style="width: 100%; height: 100px"
>
<Radio.Group v-model:value="timeUnit" size="large">
<Radio.Group
size="large"
v-model:value="meterType"
@change="handleMeterTypeChange"
>
<Radio.Button value="1">电量</Radio.Button>
<Radio.Button value="2">水量</Radio.Button>
</Radio.Group>
<DatePicker
v-model:value="selectedDate"
@@ -210,15 +231,15 @@ onMounted(() => {
@change="handleDateChange"
/>
</div>
<div v-if="timeUnit == 1">
<div>
<AnalyticsTrends
:meterType="meterType"
ref="analyticsTrendsRef"
:selected-date="selectedDate"
/>
</div>
</div>
<div class="mt-5 w-full md:flex">
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="预警类型">
<AnalyticsVisitsData :statisticsData="statisticsList" />

View File

@@ -27,7 +27,7 @@ const [BasicModal, modalApi] = useVbenModal({
},
});
const orderDetail = shallowRef<null | WorkOrdersVO>(null);
const orderDetail = ref<null | WorkOrdersVO>(null);
const handleRecords = ref<HandleRecords[]>([])
async function handleOpenChange(open: boolean) {

View File

@@ -5,26 +5,28 @@ import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { cleanList } from '#/api/property/clean';
import { getRoomsByresidentUnitId } from '#/api/property/clean_order';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
const emit = defineEmits<{ reload: [data: any], editReload: [data: any] }>();
const emit = defineEmits<{ reload: [data: any]; editReload: [data: any] }>();
const isUpdate = ref(false);
const isAdd = ref(false);
const isView = ref(false);
const currentUnitId = ref<string | number>('');
const title = computed(() => {
if(isAdd.value){
if (isAdd.value) {
return $t('pages.common.add');
}else if(isView.value){
} else if (isView.value) {
return '查看';
}else{
} else {
return $t('pages.common.edit');
}
});
// 缓存清洁服务数据
let cleanListData: any[] = [];
const detailIndex = ref<number>();//传index对应详情的某条数据,对该条数据进行编辑修改
const detailIndex = ref<number>(); //传index对应详情的某条数据,对该条数据进行编辑修改
const detailSchema = [
{
label: '劳务名称',
@@ -33,7 +35,7 @@ const detailSchema = [
componentProps: {
disabled: isView,
api: async () => {
const res = await cleanList({stater:1});
const res = await cleanList({ stater: 1 });
cleanListData = res.rows || [];
return res;
},
@@ -42,7 +44,7 @@ const detailSchema = [
valueField: 'id',
onChange: async (value: string) => {
// 找到选中的服务数据
const selectedService = cleanListData.find(item => item.id === value);
const selectedService = cleanListData.find((item) => item.id === value);
if (selectedService) {
// 自动填充其他字段
await formApi.setValues({
@@ -60,22 +62,49 @@ const detailSchema = [
rules: 'required',
},
{
label: '保洁面积',
fieldName: 'area',
component: 'InputNumber',
label: '房间',
fieldName: 'roomId',
component: 'ApiSelect',
rules: 'required',
componentProps: {
disabled: isView,
onChange: async (value: number) => {
api: async () => {
const res = await getRoomsByresidentUnitId(currentUnitId.value);
return res;
},
resultField: 'rows',
labelField: 'roomNumber',
valueField: 'id',
mode: 'multiple',
onChange: async (value: any, option: any) => {
let totalArea: any = 0;
totalArea = option.reduce((sum: any, item: any) => {
// 假设每个选项有一个area字段根据实际情况调整
return sum + (parseFloat(item.area) || 0);
}, 0);
await formApi.setValues({
area: totalArea, // 房间总面积
// 其他需要自动填充的字段
});
const formValues = await formApi.getValues();
if (formValues.peices && value) {
if (formValues.peices && totalArea) {
await formApi.setValues({
sumPeices: Number((formValues.peices * value).toFixed(2)),
sumPeices: Number((formValues.peices * totalArea).toFixed(2)),
});
}
},
},
},
{
label: '房间面积(㎡)',
fieldName: 'area',
component: 'Input',
componentProps: {
disabled: true,
},
rules: 'required',
},
{
label: '计量单位',
fieldName: 'measure',
@@ -180,18 +209,25 @@ const [BasicModal, modalApi] = useVbenModal({
}
modalApi.modalLoading(true);
const data = modalApi.getData();
currentUnitId.value = modalApi.getData().unitId;
detailIndex.value = modalApi.getData().index;
if(!data || Object.keys(data).length === 0){
//modalApi.getData()为空时表示添加
if (data.update) {
//有index表示编辑
isUpdate.value = true;
data.roomId = data.roomId
.split(',')
.filter((id: string) => id.trim() !== '');
} else if (data.readonly) {
isView.value = true;
data.roomId = data.roomId
.split(',')
.filter((id: string) => id.trim() !== '');
// data.area = data.sumPeices / data.peices;
} else {
isAdd.value = true;
}else if(data.readonly){
isView.value = true;
}else{
//表示编辑
isUpdate.value = true;
}
// TODO: 获取详情数据
await formApi.setValues(modalApi.getData());
await formApi.setValues(data);
await markInitialized();
modalApi.modalLoading(false);
},
@@ -206,18 +242,19 @@ async function handleConfirm() {
}
let data = cloneDeep(await formApi.getValues());
// 获取选中的服务名称
const selectedService = cleanListData.find(item => item.id === data.name);
const selectedService = cleanListData.find((item) => item.id === data.name);
if (selectedService) {
data.name = selectedService.name;
data.id = selectedService.id
data.id = selectedService.id;
}
data.roomId = data.roomId.join(',');
if (isUpdate.value) {
data.index = detailIndex.value;
emit('editReload', data);
}else if(isAdd.value){
} else if (isAdd.value) {
emit('reload', data);
}
handleClosed()
handleClosed();
await markInitialized();
modalApi.close();
} catch (error) {
@@ -238,8 +275,7 @@ async function handleClosed() {
<template>
<BasicModal :title="title">
<BasicForm >
</BasicForm>
<BasicForm> </BasicForm>
</BasicModal>
</template>
<style scoped>

View File

@@ -26,10 +26,11 @@ import cleanDetailModal from './clean-detail-modal.vue';
// import { modalSchema } from './data';
import { communityTree } from '#/api/property/community';
import { getDictOptions } from '#/utils/dict';
import type {Clean_orderForm} from "#/api/property/clean_order/model";
import type { Clean_orderForm } from '#/api/property/clean_order/model';
import { reactive } from 'vue';
const emit = defineEmits<{ reload: [] }>();
let currentUnitId = ref<string | number>(''); //当前表单单位id
// 计算合计费用
const totalSumPeices = computed(() => {
return detailTable.value
@@ -73,12 +74,28 @@ const modalSchema = [
},
},
{
label: '服务地址(房间号)',
component: 'TreeSelect',
defaultValue: undefined,
fieldName: 'location',
label: '申请单位',
fieldName: 'unitId',
component: 'ApiSelect',
componentProps: {
api: resident_unitList,
resultField: 'rows',
labelField: 'name',
valueField: 'id',
placeholder: '请选择单位',
onChange: (value: any) => {
currentUnitId.value = value;
},
},
rules: 'required',
},
// {
// label: '服务地址(房间号)',
// component: 'TreeSelect',
// defaultValue: undefined,
// fieldName: 'location',
// rules: 'required',
// },
{
label: '开始时间',
fieldName: 'starTime',
@@ -124,19 +141,7 @@ const modalSchema = [
component: 'Input',
rules: 'required',
},
{
label: '申请单位',
fieldName: 'unitId',
component: 'ApiSelect',
componentProps: {
api: resident_unitList,
resultField: 'rows',
labelField: 'name',
valueField: 'id',
placeholder: '请选择单位',
},
rules: 'required',
},
{
label: '支付状态',
fieldName: 'payState',
@@ -196,7 +201,7 @@ const modalSchema = [
helpMessage: false,
},
dependencies: {
show: (formValue:Clean_orderForm) =>
show: (formValue: Clean_orderForm) =>
isReadonly.value && formValue.imgUrl ? true : false,
triggerFields: [''],
},
@@ -221,7 +226,7 @@ const modalSchema = [
helpMessage: false,
},
dependencies: {
show: (formValue:Clean_orderForm) =>
show: (formValue: Clean_orderForm) =>
isReadonly.value && formValue.signImgUrl ? true : false,
triggerFields: [''],
},
@@ -327,11 +332,12 @@ const [BasicModal, modalApi] = useVbenModal({
for (const item of record.relationList) {
for (let i = 0; i < detailTable.value.length; i++) {
if (item.cleanId === detailTable.value[i].id) {
detailTable.value[i].area = item.areas;
// detailTable.value[i].area = item.areas;
detailTable.value[i].sumPeices = item.sumPrice;
}
}
}
currentUnitId.value = record.unitId;
await formApi.setValues(record);
}
await markInitialized();
@@ -396,11 +402,11 @@ const detailGridOptions: VxeGridProps = {
default: ({ row }) => (row.stater === 1 ? '启用' : '禁用'),
},
},
{
title: '保洁面积',
field: 'area',
width: 'auto',
},
// {
// title: '保洁面积',
// field: 'area',
// width: 'auto',
// },
{
title: '合计费用',
field: 'sumPeices',
@@ -463,7 +469,7 @@ const [CleanDetailModal, detailModalApi] = useVbenModal({
});
function handleAddDetail() {
detailModalApi.setData({});
detailModalApi.setData({ unitId: currentUnitId.value });
detailModalApi.open();
}
// 添加订单服务详情
@@ -480,12 +486,21 @@ function handleDeleteDetail(record: any, index: number) {
}
// 查看产品详情
function handleViewDetail(record: any) {
detailModalApi.setData({ ...record, readonly: true });
detailModalApi.setData({
...record,
readonly: true,
unitId: currentUnitId.value,
});
detailModalApi.open();
}
// 编辑产品详情
function handleEditDetail(record: any, index: number) {
detailModalApi.setData({ ...record, index, readonly: false });
detailModalApi.setData({
...record,
index,
update: true,
unitId: currentUnitId.value,
});
detailModalApi.open();
}
@@ -526,6 +541,7 @@ async function handleConfirm() {
: await clean_orderAdd(data);
resetInitialized();
emit('reload');
currentUnitId.value = ''; //清空当前单位id
modalApi.close();
} catch (error) {
console.error(error);
@@ -537,6 +553,7 @@ async function handleConfirm() {
async function handleClosed() {
await formApi.resetForm();
detailTable.value = []; //清空详情表格
currentUnitId.value = ''; //清空当前单位id
resetInitialized();
}
// 获取服务地址
@@ -636,7 +653,11 @@ async function handleAudit(params: any) {
: '添加保洁订单详情'
}}
</h3>
<a-button v-if="!isReadonly" type="primary" @click="handleAddDetail">
<a-button
v-if="!isReadonly && currentUnitId"
type="primary"
@click="handleAddDetail"
>
{{ $t('pages.common.add') }}
</a-button>
</div>

View File

@@ -72,11 +72,11 @@ export const columns: VxeGridProps['columns'] = [
},
},
},
{
title: '服务地址',
field: 'locationName',
width: '260',
},
// {
// title: '服务地址',
// field: 'locationName',
// width: '260',
// },
{
title: '合计费用(元)',
field: 'sumPeices',
@@ -170,12 +170,25 @@ export const modalSchema: (isReadonly: boolean) => FormSchema[] = (
},
},
{
label: '服务地址(房间号)',
component: 'TreeSelect',
defaultValue: undefined,
fieldName: 'location',
label: '申请单位',
fieldName: 'unitId',
component: 'ApiSelect',
componentProps: {
api: resident_unitList,
resultField: 'rows',
labelField: 'name',
valueField: 'id',
placeholder: '请选择单位',
},
rules: 'required',
},
// {
// label: '服务地址(房间号)',
// component: 'TreeSelect',
// defaultValue: undefined,
// fieldName: 'location',
// rules: 'required',
// },
{
label: '开始时间',
fieldName: 'starTime',
@@ -221,19 +234,7 @@ export const modalSchema: (isReadonly: boolean) => FormSchema[] = (
component: 'Input',
rules: 'required',
},
{
label: '申请单位',
fieldName: 'unitId',
component: 'ApiSelect',
componentProps: {
api: resident_unitList,
resultField: 'rows',
labelField: 'name',
valueField: 'id',
placeholder: '请选择单位',
},
rules: 'required',
},
{
label: '支付状态',
fieldName: 'payState',

View File

@@ -57,9 +57,9 @@ async function handleOpenChange(open: boolean) {
:is="renderDict(costItemSettingDetail.paymentType,'pro_payment_type')"
/>
</DescriptionsItem>
<DescriptionsItem label="费用周期(月)">
{{ costItemSettingDetail.chargeCycle }}
</DescriptionsItem>
<!-- <DescriptionsItem label="费用周期(月)">-->
<!-- {{ costItemSettingDetail.chargeCycle }}-->
<!-- </DescriptionsItem>-->
<DescriptionsItem label="单位">
{{ costItemSettingDetail.unit }}
</DescriptionsItem>
@@ -83,15 +83,15 @@ async function handleOpenChange(open: boolean) {
:is="renderDict(costItemSettingDetail.state,'wy_state')"
/>
</DescriptionsItem>
<DescriptionsItem label="计算公式" v-if="costItemSettingDetail.formula!=null">
<DescriptionsItem label="计算公式" v-if="costItemSettingDetail.formula!=null" :span="2">
<component
:is="renderDict(costItemSettingDetail.formula,'pro_calculation_formula')"
/>
</DescriptionsItem>
<DescriptionsItem label="计费单价">
<DescriptionsItem :label="costItemSettingDetail.formula=='1'?'费用':'计费单价'" v-if="costItemSettingDetail.unitPrice!=null">
{{ costItemSettingDetail.unitPrice }}
</DescriptionsItem>
<DescriptionsItem label="附加费用">
<DescriptionsItem label="附加费用" v-if="costItemSettingDetail.surcharge!=null">
{{ costItemSettingDetail.surcharge }}
</DescriptionsItem>
</Descriptions>

View File

@@ -10,6 +10,7 @@ import { costItemSettingAdd, costItemSettingInfo, costItemSettingUpdate } from '
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
import {getDictOptions} from "#/utils/dict";
const emit = defineEmits<{ reload: [] }>();
@@ -53,7 +54,7 @@ const [BasicModal, modalApi] = useVbenModal({
return null;
}
modalApi.modalLoading(true);
initCostTypeOptions()
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
@@ -67,6 +68,39 @@ const [BasicModal, modalApi] = useVbenModal({
},
});
/**
* 初始化费用类型
*/
async function initCostTypeOptions() {
formApi.updateSchema([
{
componentProps: {
// 费用类型不要保洁、会议、绿植
options: getDictOptions('pro_expense_type'),
onChange: async (value: string) => {
if (value) {
//水费、电费、气费
if(['5','6','8'].includes(value)){
await formApi.setFieldValue('formula', '5');//(本层本次读数-上次读数)*单价/本层实际使用面积*本单位面积
//停车费、租金、押金
}else if(['2','7','9'].includes(value)){
await formApi.setFieldValue('formula', '1');//固定费用
//会议室使用费、绿植租赁费、保洁服务费
}else if(['1','3','4'].includes(value)){
await formApi.setFieldValue('formula', null);
//物业管理费
}else{
await formApi.setFieldValue('formula', '0');//建筑面积*单价
}
}
},
},
fieldName: 'costType',
},
]);
}
async function handleConfirm() {
try {
modalApi.lock(true);
@@ -76,6 +110,18 @@ async function handleConfirm() {
}
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
const data = cloneDeep(await formApi.getValues());
//会议室使用费、绿植租赁费、保洁服务费
if(['1','3','4'].includes(data.costType)){
data.roundingMode=null;
data.currencyDecimals=null;
data.formula=null;
data.unitPrice=null;
data.surcharge=null;
}
//固定费用
if(data.formula=='1'){
data.surcharge=null;
}
await (isUpdate.value ? costItemSettingUpdate(data) : costItemSettingAdd(data));
resetInitialized();
emit('reload');

View File

@@ -32,6 +32,7 @@ export const columns: VxeGridProps['columns'] = [
{
title: '费用编号',
field: 'chargeNo',
width: 180,
},
{
title: '费用类型',
@@ -41,10 +42,12 @@ export const columns: VxeGridProps['columns'] = [
return renderDict(row.costType, 'pro_expense_type');
},
},
width: 150,
},
{
title: '收费项目',
field: 'chargeItem',
minWidth: 180,
},
{
title: '费用标识',
@@ -54,6 +57,7 @@ export const columns: VxeGridProps['columns'] = [
return renderDict(row.costMark, 'pro_cost_identification');
},
},
width: 180,
},
{
title: '付费类型',
@@ -63,33 +67,18 @@ export const columns: VxeGridProps['columns'] = [
return renderDict(row.paymentType, 'pro_payment_type');
},
},
width: 180,
},
{
title: '费用周期(月)',
field: 'chargeCycle',
},
{
title: '计算公式',
field: 'formula',
slots: {
default: ({ row }) => {
return renderDict(row.formula, 'pro_calculation_formula');
},
},
},
{
title: '计费单价',
field: 'unitPrice',
},
{
title: '附加费用',
field: 'surcharge',
title: '单位',
field: 'unit',
minWidth: 120,
},
{
title: '状态',
field: 'state',
slots: {default: 'state'},
width: 100,
slots: { default: 'state' },
width: 120,
},
{
field: 'action',
@@ -114,9 +103,6 @@ export const modalSchema: FormSchemaGetter = () => [
label: '费用类型',
fieldName: 'costType',
component: 'Select',
componentProps: {
options: getDictOptions('pro_expense_type'),
},
rules: 'selectRequired',
},
{
@@ -143,12 +129,6 @@ export const modalSchema: FormSchemaGetter = () => [
},
rules: 'selectRequired',
},
{
label: '费用周期(月)',
fieldName: 'chargeCycle',
component: 'Input',
rules: 'required',
},
{
label: '单位',
fieldName: 'unit',
@@ -158,15 +138,6 @@ export const modalSchema: FormSchemaGetter = () => [
placeholder: '请输入(如:元)',
},
},
{
label: '手机缴费',
fieldName: 'isMobilePay',
component: 'Select',
componentProps: {
options: getDictOptions('pro_mobile_payment'),
},
rules: 'selectRequired',
},
{
label: '进位方式',
fieldName: 'roundingMode',
@@ -175,6 +146,10 @@ export const modalSchema: FormSchemaGetter = () => [
},
component: 'Select',
rules: 'selectRequired',
dependencies: {
show: (fromValue) => !(['1','3','4'].includes(fromValue.costType)),
triggerFields: ['costType'],
},
},
{
label: '保留小数',
@@ -184,6 +159,10 @@ export const modalSchema: FormSchemaGetter = () => [
options: getDictOptions('pro_keep_decimals'),
},
rules: 'selectRequired',
dependencies: {
show: (fromValue) => !(['1','3','4'].includes(fromValue.costType)),
triggerFields: ['costType'],
},
},
{
component: 'Select',
@@ -201,19 +180,42 @@ export const modalSchema: FormSchemaGetter = () => [
componentProps: {
options: getDictOptions('pro_calculation_formula'),
},
rules: 'selectRequired',
formItemClass:'col-span-2',
disabled:true,
defaultValue:'1',
dependencies: {
show: (fromValue) => !(['1','3','4'].includes(fromValue.costType)),
triggerFields: ['costType'],
},
formItemClass:'col-span-2'
},
{
label: '计费单价',
fieldName: 'unitPrice',
component: 'InputNumber',
rules: 'required',
dependencies: {
show: (fromValue) => ['0','5','6','8'].includes(fromValue.costType),
triggerFields: ['costType'],
},
},
{
label: '费用',
fieldName: 'unitPrice',
component: 'InputNumber',
rules: 'required',
dependencies: {
show: (fromValue) => ['2','7','9'].includes(fromValue.costType),
triggerFields: ['costType'],
},
},
{
label: '附加费',
fieldName: 'surcharge',
component: 'InputNumber',
rules: 'required',
dependencies: {
show: (fromValue) => ['0','5','6','8'].includes(fromValue.costType),
triggerFields: ['costType'],
},
},
];

View File

@@ -1,17 +1,26 @@
import type {FormSchemaGetter} from '#/adapter/form';
import type {VxeGridProps} from '#/adapter/vxe-table';
import {getDictOptions} from "#/utils/dict";
import {renderDict} from "#/utils/render";
import {costItemSettingList} from "#/api/property/costManagement/costItemSetting";
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getDictOptions } from '#/utils/dict';
import { renderDict } from '#/utils/render';
import { costItemSettingList } from '#/api/property/costManagement/costItemSetting';
import { h } from 'vue';
import { resident_unitList } from '#/api/property/resident/unit';
export const querySchema: FormSchemaGetter = () => [
{
component: 'Select',
componentProps: {
options: getDictOptions('pro_expense_type'),
},
fieldName: 'costType',
label: '费用类型',
},
{
component: 'ApiSelect',
componentProps: {
api:async ()=>{
return (await costItemSettingList({pageSize: 1000, pageNum: 1}))?.rows
api: async () => {
return (await costItemSettingList({ pageSize: 1000, pageNum: 1 }))
?.rows;
},
afterFetch: (data: { chargeItem: string; id: string }[]) => {
return data.map((item: any) => ({
@@ -28,21 +37,53 @@ export const querySchema: FormSchemaGetter = () => [
fieldName: 'chargeStatus',
label: '缴费状态',
componentProps: {
options: getDictOptions('wy_fyshzt')
options: getDictOptions('wy_fyshzt'),
},
},
{
component: 'ApiSelect',
componentProps: {
api: async () => {
const rows = await resident_unitList({
pageSize: 1000000000,
pageNum: 1,
});
return rows;
},
resultField: 'rows',
labelField: 'name',
valueField: 'id',
},
fieldName: 'residentUnitId',
label: '单位',
},
];
export const columns: VxeGridProps['columns'] = [
{type: 'checkbox', width: 60},
{ type: 'checkbox', width: 60 },
{
title: '单位',
field: 'residentUnitText',
minWidth: 150,
minWidth: 180,
},
{
title: '费用类型',
width: 150,
field: 'costType',
slots: {
default: ({ row }) => {
return renderDict(row.costType||row.type, 'pro_expense_type');
},
},
},
{
title: '收费项目',
field: 'costItemsId',
field: 'chargeItemText',
width: 150,
slots: {
default: ({ row }) => {
return row.chargeItemText||'-';
},
},
},
{
title: '应收金额',
@@ -64,30 +105,25 @@ export const columns: VxeGridProps['columns'] = [
field: 'chargeStatus',
width: 150,
slots: {
default: ({row}) => {
return renderDict(row.chargeStatus, 'wy_fyshzt')
}
}
default: ({ row }) => {
return renderDict(row.chargeStatus, 'wy_fyshzt');
},
},
},
// {
// title: '状态',
// field: 'state',
// width: 150,
// slots: {
// default: ({row}) => {
// return renderDict(row.state, 'wy_fysfzt')
// }
// }
// },
{
title: '说明',
field: 'remark',
width: 150,
width: 180,
slots: {
default: ({ row }) => {
return row.remark||'-';
},
},
},
{
field: 'action',
fixed: 'right',
slots: {default: 'action'},
slots: { default: 'action' },
title: '操作',
width: 180,
},
@@ -107,8 +143,7 @@ export const modalSchema: FormSchemaGetter = () => [
label: '单位',
fieldName: 'residentUnitId',
component: 'Select',
componentProps: {
},
componentProps: {},
rules: 'selectRequired',
formItemClass: 'col-span-2',
},
@@ -136,16 +171,14 @@ export const modalSchema: FormSchemaGetter = () => [
label: '业主',
fieldName: 'personId',
component: 'ApiSelect',
componentProps: {
},
componentProps: {},
rules: 'selectRequired',
},
{
label: '费用类型',
fieldName: 'costType',
component: 'Select',
componentProps: {
},
componentProps: {},
rules: 'selectRequired',
},
{
@@ -167,10 +200,11 @@ export const modalSchema: FormSchemaGetter = () => [
{
label: '计费时间',
fieldName: 'chargeTime',
component: 'RangePicker',
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
format: 'YYYY-MM',
valueFormat: 'YYYY-MM',
picker:'month'
},
rules: 'selectRequired',
},
@@ -201,7 +235,7 @@ export const modalSchemaUpdate: FormSchemaGetter = () => [
fieldName: 'payType',
component: 'Select',
componentProps: {
options:getDictOptions('wy_zffs')
options: getDictOptions('wy_zffs'),
},
rules: 'selectRequired',
},
@@ -210,14 +244,15 @@ export const modalSchemaUpdate: FormSchemaGetter = () => [
fieldName: 'chargeCycle',
component: 'InputNumber',
componentProps: {
min:1,
precision:0,
placeholder:'请输入缴费周期(月)'
min: 1,
precision: 0,
placeholder: '请输入缴费周期(月)',
},
suffix: () => h('span', { style: {fontSize: '0.875rem',fontWeight:500,} }, '月'),
suffix: () =>
h('span', { style: { fontSize: '0.875rem', fontWeight: 500 } }, '月'),
rules: 'selectRequired',
},
]
];
export const modalSchemaRefund: FormSchemaGetter = () => [
{
label: '主键',
@@ -233,6 +268,24 @@ export const modalSchemaRefund: FormSchemaGetter = () => [
fieldName: 'reason',
component: 'Textarea',
formItemClass: 'col-span-2',
rules:'required'
rules: 'required',
},
]
];
export const modalSchemaCorrection: FormSchemaGetter = () => [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '收费项目',
fieldName: 'costItemsId',
component: 'ApiSelect',
componentProps: {},
rules: 'selectRequired',
},
];

View File

@@ -0,0 +1,149 @@
<script setup lang="ts">
import {ref} from 'vue';
import {useVbenModal} from '@vben/common-ui';
import {cloneDeep} from '@vben/utils';
import {useVbenForm} from '#/adapter/form';
import {
houseChargeAddFee,
houseChargeInfo,
} from '#/api/property/costManagement/houseCharge';
import {defaultFormValueGetter, useBeforeCloseDiff} from '#/utils/popup';
import {modalSchemaCorrection} from './data';
import {renderDict} from "#/utils/render";
import {Descriptions, DescriptionsItem, Divider} from "ant-design-vue";
import type {HouseChargeVO} from "#/api/property/costManagement/houseCharge/model";
import {costItemSettingList} from "#/api/property/costManagement/costItemSetting";
import type {CostItemSettingVO} from "#/api/property/costManagement/costItemSetting/model";
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const record = ref<HouseChargeVO>();
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 80,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: modalSchemaCorrection(),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const {onBeforeClose, markInitialized, resetInitialized} = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
// 在这里更改宽度
class: 'w-[550px]',
fullscreenButton: false,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const {id} = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
record.value = await houseChargeInfo(id);
queryCostItemOptions(record.value.type);
await formApi.setValues(record.value);
await markInitialized();
modalApi.modalLoading(false);
},
});
async function handleConfirm() {
try {
modalApi.lock(true);
const {valid} = await formApi.validate();
if (!valid) {
return;
}
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
const data = cloneDeep(await formApi.getValues());
record.value.costItemsId = data.costItemsId
await houseChargeAddFee(record.value);
resetInitialized();
emit('reload');
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.lock(false);
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
const costItemOptions = ref<CostItemSettingVO>([]);
async function queryCostItemOptions(costType: string) {
let params = {
pageSize: 1000,
pageNum: 1,
costType,
};
const res = await costItemSettingList(params);
costItemOptions.value = res.rows;
formApi.updateSchema([
{
componentProps: {
options: costItemOptions.value,
fieldNames: { label: 'chargeItem', value: 'id' },
showSearch: true,
optionFilterProp: 'chargeItem',
},
fieldName: 'costItemsId',
},
]);
}
</script>
<template>
<BasicModal title="更正">
<Descriptions v-if="record" size="small" :column="2"
:labelStyle="{width:'80px'}">
<DescriptionsItem label="单位" :span="2">
{{ record.residentUnitText }}
</DescriptionsItem>
<DescriptionsItem label="单位面积">
{{ `${record.area} (㎡)` }}
</DescriptionsItem>
<DescriptionsItem label="费用类型">
<component
:is="renderDict(record?.type,'pro_expense_type')"
/>
</DescriptionsItem>
<DescriptionsItem label="计费时间" :span="2">
{{ record.startTime + ' 至 ' + record.endTime }}
</DescriptionsItem>
<DescriptionsItem label="应收金额" :span="2">
<span style="font-size: 16px;font-weight: 600;color: red" v-if="record.amountReceivable">
{{ record.amountReceivable }}
</span>
</DescriptionsItem>
</Descriptions>
<Divider/>
<BasicForm/>
</BasicModal>
</template>

View File

@@ -11,7 +11,6 @@ dayjs.extend(duration);
dayjs.extend(relativeTime);
import {houseChargeInfo} from "#/api/property/costManagement/houseCharge";
import type {HouseChargeVO} from "#/api/property/costManagement/houseCharge/model";
import type {RoomVO} from "#/api/property/room/model";
import type {CostItemSettingVO} from "#/api/property/costManagement/costItemSetting/model";
@@ -23,7 +22,6 @@ const [BasicModal, modalApi] = useVbenModal({
});
const houseChargeDetail = shallowRef<null | HouseChargeVO>(null);
const room = ref<RoomVO>();
const costItem = ref<CostItemSettingVO>();
async function handleOpenChange(open: boolean) {
@@ -34,7 +32,6 @@ async function handleOpenChange(open: boolean) {
const {id} = modalApi.getData() as { id: number | string };
houseChargeDetail.value = await houseChargeInfo(id);
if (houseChargeDetail.value) {
room.value = houseChargeDetail.value.roomVo
costItem.value = houseChargeDetail.value.costItemsVo
}
modalApi.modalLoading(false);
@@ -42,33 +39,33 @@ async function handleOpenChange(open: boolean) {
</script>
<template>
<BasicModal :footer="false" :fullscreen-button="false" title="房屋收费详情" class="w-[70%]">
<BasicModal :footer="false" :fullscreen-button="false" title="收费详情" class="w-[70%]">
<Descriptions v-if="houseChargeDetail" size="small" :column="2" bordered
:labelStyle="{width:'100px'}">
<DescriptionsItem label="费用编号">
{{ costItem?.id }}
</DescriptionsItem>
<DescriptionsItem label="费用项目">
{{ costItem?.chargeItem }}
</DescriptionsItem>
<DescriptionsItem label="费用类型">
<component v-if="costItem"
:is="renderDict(costItem.costType,'pro_expense_type')"
/>
</DescriptionsItem>
<DescriptionsItem label="计费时间">
{{ houseChargeDetail.startTime + ' 至 ' + houseChargeDetail.endTime }}
</DescriptionsItem>
<DescriptionsItem label="单位">
<DescriptionsItem label="单位" :span="2">
{{ houseChargeDetail?.residentUnitText }}
</DescriptionsItem>
<DescriptionsItem label="单位面积">
{{ `${houseChargeDetail?.area} (㎡)` }}
</DescriptionsItem>
<DescriptionsItem label="单价">
<DescriptionsItem label="费用类型" v-if="costItem?.costType">
<component v-if="costItem"
:is="renderDict(costItem.costType,'pro_expense_type')"
/>
</DescriptionsItem>
<DescriptionsItem label="费用项目" v-if="costItem?.chargeItem">
{{ costItem?.chargeItem }}
</DescriptionsItem>
<DescriptionsItem label="费用编号" v-if="costItem?.chargeNo">
{{ costItem?.chargeNo }}
</DescriptionsItem>
<DescriptionsItem label="计费时间" :span="2">
{{ houseChargeDetail.startTime + ' 至 ' + houseChargeDetail.endTime }}
</DescriptionsItem>
<DescriptionsItem label="单价" v-if="costItem?.unitPrice&&!houseChargeDetail.orderId">
{{ costItem?.unitPrice }}
</DescriptionsItem>
<DescriptionsItem label="附加费">
<DescriptionsItem label="附加费" v-if="costItem?.surcharge&&!houseChargeDetail.orderId">
{{ costItem?.surcharge }}
</DescriptionsItem>
<DescriptionsItem label="应收金额">

View File

@@ -14,14 +14,12 @@ import {modalSchemaRefund} from './data';
import {renderDict} from "#/utils/render";
import {Descriptions, DescriptionsItem, Divider} from "ant-design-vue";
import type {HouseChargeVO} from "#/api/property/costManagement/houseCharge/model";
import type {RoomVO} from "#/api/property/room/model";
import type {CostItemSettingVO} from "#/api/property/costManagement/costItemSetting/model";
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const record = ref<HouseChargeVO>();
const room = ref<RoomVO>();
const costItem = ref<CostItemSettingVO>();
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
@@ -63,7 +61,6 @@ const [BasicModal, modalApi] = useVbenModal({
if(id){
record.value = await houseChargeInfo(id);
if (record.value) {
room.value = record.value.roomVo
costItem.value = record.value.costItemsVo
}
await formApi.setValues(record.value);
@@ -103,34 +100,34 @@ async function handleClosed() {
</script>
<template>
<BasicModal title="费">
<BasicModal title="退费">
<Descriptions v-if="record" size="small" :column="2"
:labelStyle="{width:'80px'}">
<DescriptionsItem label="费用编号">
{{ costItem?.id }}
</DescriptionsItem>
<DescriptionsItem label="费用项目">
{{ costItem?.chargeItem }}
</DescriptionsItem>
<DescriptionsItem label="费用类型">
<component
v-if="costItem"
:is="renderDict(costItem?.costType,'pro_expense_type')"
/>
</DescriptionsItem>
<DescriptionsItem label="计费起始">
{{ record.startTime }}
</DescriptionsItem>
<DescriptionsItem label="单位">
<DescriptionsItem label="单位" :span="2">
{{ record.residentUnitText }}
</DescriptionsItem>
<DescriptionsItem label="单位面积">
{{ `${record.area} (㎡)` }}
</DescriptionsItem>
<DescriptionsItem label="单价">
<DescriptionsItem label="费用类型" v-if="costItem?.costType">
<component
v-if="costItem"
:is="renderDict(costItem?.costType,'pro_expense_type')"
/>
</DescriptionsItem>
<DescriptionsItem label="费用项目" v-if="costItem?.chargeItem">
{{ costItem?.chargeItem }}
</DescriptionsItem>
<DescriptionsItem label="费用编号" v-if="costItem?.chargeNo">
{{ costItem?.chargeNo }}
</DescriptionsItem>
<DescriptionsItem label="计费时间" :span="2">
{{ record.startTime + ' 至 ' + record.endTime }}
</DescriptionsItem>
<DescriptionsItem label="单价" v-if="costItem?.unitPrice&&!record.orderId">
{{ costItem?.unitPrice }}
</DescriptionsItem>
<DescriptionsItem label="附加费">
<DescriptionsItem label="附加费" v-if="costItem?.surcharge&&!record.orderId">
{{ costItem?.surcharge }}
</DescriptionsItem>
<DescriptionsItem label="退费金额" :span="2">

View File

@@ -20,8 +20,11 @@ import type { CostItemSettingVO } from '#/api/property/costManagement/costItemSe
import { getDictOptions } from '#/utils/dict';
import { personList } from '#/api/property/resident/person';
import { renderDictValue } from '#/utils/render';
import {resident_unitInfo, resident_unitList} from "#/api/property/resident/unit";
import {
resident_unitInfo,
resident_unitList,
} from '#/api/property/resident/unit';
import dayjs from 'dayjs';
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
@@ -74,7 +77,7 @@ const [BasicModal, modalApi] = useVbenModal({
if (isUpdate.value && id) {
const record = await houseChargeInfo(id);
record.chargeTime = [record.startTime, record.endTime];
record.chargeTime = record.startTime?.substring(0, 7);
await formApi.setValues(record);
}
await markInitialized();
@@ -92,9 +95,13 @@ async function handleConfirm() {
}
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
const data = cloneDeep(await formApi.getValues());
if (data.chargeTime && data.chargeTime.length) {
data.startTime = data.chargeTime[0];
data.endTime = data.chargeTime[1];
if (data.chargeTime) {
data.startTime = dayjs(data.chargeTime)
.startOf('month')
.format('YYYY-MM-DD HH:mm:ss');
data.endTime = dayjs(data.chargeTime)
.endOf('month')
.format('YYYY-MM-DD HH:mm:ss');
}
await (isUpdate.value ? houseChargeUpdate(data) : houseChargeAdd(data));
resetInitialized();
@@ -115,7 +122,7 @@ async function handleClosed() {
/**
* 查询费用项设置
*/
async function queryCostItemOptions(costType: string|null) {
async function queryCostItemOptions(costType: string | null) {
let params = {
pageSize: 1000,
pageNum: 1,
@@ -156,7 +163,10 @@ async function initCostTypeOptions() {
formApi.updateSchema([
{
componentProps: {
options: getDictOptions('pro_expense_type'),
// 费用类型不要保洁、会议、绿植
options: getDictOptions('pro_expense_type').filter(
(item) => !['1', '3', '4'].includes(item.value),
),
onChange: async (value: string) => {
if (value) {
await queryCostItemOptions(value);
@@ -206,7 +216,7 @@ async function queryUnitData() {
};
const res = await resident_unitList(params);
const options = res.rows.map((unit) => ({
label:unit.name,
label: unit.name,
value: unit.id,
}));
formApi.updateSchema([
@@ -228,8 +238,8 @@ async function queryUnitData() {
}
async function queryRoomByUnit(unitId: string) {
let unitInfo = await resident_unitInfo(unitId);
if(unitInfo&&unitInfo.locationDetail){
let unitInfo = await resident_unitInfo(unitId);
if (unitInfo && unitInfo.locationDetail) {
await formApi.setFieldValue('roomInfo', unitInfo.locationDetail);
await formApi.setFieldValue('area', unitInfo.area);
}
@@ -240,14 +250,12 @@ async function queryRoomByUnit(unitId: string) {
<BasicModal :title="title">
<BasicForm>
<template #roomInfo="slotProps">
<div>
{{slotProps.field.value}}
</div>
<div>
{{ slotProps.field.value }}
</div>
</template>
<template #area="slotProps">
<div>
{{slotProps.field.value}}
</div>
<div>{{ slotProps.field.value }}</div>
</template>
</BasicForm>
</BasicModal>

View File

@@ -15,14 +15,12 @@ import {modalSchemaUpdate} from './data';
import {renderDict} from "#/utils/render";
import {Descriptions, DescriptionsItem, Divider} from "ant-design-vue";
import type {HouseChargeVO} from "#/api/property/costManagement/houseCharge/model";
import type {RoomVO} from "#/api/property/room/model";
import type {CostItemSettingVO} from "#/api/property/costManagement/costItemSetting/model";
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const record = ref<HouseChargeVO>();
const room = ref<RoomVO>();
const costItem = ref<CostItemSettingVO>();
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
@@ -63,7 +61,6 @@ const [BasicModal, modalApi] = useVbenModal({
isUpdate.value = !!id;
record.value = await houseChargeInfo(id);
if (record.value) {
room.value = record.value.roomVo
costItem.value = record.value.costItemsVo
}
await formApi.setValues(record.value);
@@ -107,31 +104,31 @@ async function handleClosed() {
<BasicModal title="缴费">
<Descriptions v-if="record" size="small" :column="2"
:labelStyle="{width:'80px'}">
<DescriptionsItem label="费用编号">
{{ costItem?.id }}
</DescriptionsItem>
<DescriptionsItem label="费用项目">
{{ costItem?.chargeItem }}
</DescriptionsItem>
<DescriptionsItem label="费用类型">
<component
v-if="costItem"
:is="renderDict(costItem?.costType,'pro_expense_type')"
/>
</DescriptionsItem>
<DescriptionsItem label="计费起始">
{{ record.startTime }}
</DescriptionsItem>
<DescriptionsItem label="单位">
<DescriptionsItem label="单位" :span="2">
{{ record.residentUnitText }}
</DescriptionsItem>
<DescriptionsItem label="单位面积">
{{ `${record.area} (㎡)` }}
</DescriptionsItem>
<DescriptionsItem label="单价">
<DescriptionsItem label="费用类型" v-if="costItem?.costType">
<component
v-if="costItem"
:is="renderDict(costItem?.costType,'pro_expense_type')"
/>
</DescriptionsItem>
<DescriptionsItem label="费用项目" v-if="costItem?.chargeItem">
{{ costItem?.chargeItem }}
</DescriptionsItem>
<DescriptionsItem label="费用编号" v-if="costItem?.chargeNo">
{{ costItem?.chargeNo }}
</DescriptionsItem>
<DescriptionsItem label="计费时间" :span="2">
{{ record.startTime + ' 至 ' + record.endTime }}
</DescriptionsItem>
<DescriptionsItem label="单价" v-if="costItem?.unitPrice&&!record.orderId">
{{ costItem?.unitPrice }}
</DescriptionsItem>
<DescriptionsItem label="附加费">
<DescriptionsItem label="附加费" v-if="costItem?.surcharge&&!record.orderId">
{{ costItem?.surcharge }}
</DescriptionsItem>
<DescriptionsItem label="缴费金额" :span="2">

View File

@@ -23,6 +23,7 @@ import houseChargeAdd from './houseCharge-add.vue';
import houseChargeUpdate from './houseCharge-update.vue';
import houseChargeDetail from './house-charge-detail.vue';
import houseChargeRefund from './house-charge-refund.vue';
import houseChargeCorrection from './house-charge-correction.vue';
import {columns, querySchema} from './data';
const formOptions: VbenFormProps = {
@@ -86,6 +87,9 @@ const [HouseChargeDetail, detailApi] = useVbenModal({
const [HouseChargeRefund, refundApi] = useVbenModal({
connectedComponent: houseChargeRefund,
});
const [HouseChargeCorrection, correctionApi] = useVbenModal({
connectedComponent: houseChargeCorrection,
});
function handleAdd() {
modalApi.setData({});
@@ -111,6 +115,10 @@ async function handleRefund(row: Required<HouseChargeForm>) {
refundApi.setData({id: row.id});
refundApi.open();
}
async function handleCorrection(row: Required<HouseChargeForm>) {
correctionApi.setData({id: row.id});
correctionApi.open();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
@@ -135,7 +143,7 @@ function handleDownloadExcel() {
<template>
<Page :auto-content-height="true">
<BasicTable table-title="房屋收费列表">
<BasicTable table-title="收费列表">
<template #toolbar-tools>
<Space>
<a-button
@@ -184,6 +192,13 @@ function handleDownloadExcel() {
>
退费
</ghost-button>
<ghost-button
v-else-if="row.orderId!==null&&row.costItemsId==null"
v-access:code="['property:houseCharge:edit']"
@click.stop="handleCorrection(row)"
>
更正
</ghost-button>
<Popconfirm
v-else
:get-popup-container="getVxePopupContainer"
@@ -206,6 +221,7 @@ function handleDownloadExcel() {
<HouseChargeAdd @reload="tableApi.query()"/>
<HouseChargeUpdate @reload="tableApi.query()"/>
<HouseChargeRefund @reload="tableApi.query()"/>
<HouseChargeCorrection @reload="tableApi.query()"/>
<HouseChargeDetail/>
</Page>
</template>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts">
import * as echarts from 'echarts';
import { onMounted, ref, nextTick } from 'vue';
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
import { Dayjs } from 'dayjs';
import * as echarts from 'echarts';
import { Page } from '@vben/common-ui';
import { DatePicker } from 'ant-design-vue';
import { onMounted, onUnmounted, ref } from 'vue';
import FloorTree from '../components/floor-tree.vue';
import { meterRecordTrend } from '#/api/property/energyManagement/meterRecord';
@@ -21,13 +21,24 @@ const disabledYear = (current: Dayjs) => {
return current && current > dayjs().endOf('year');
};
onMounted(() => {
setTimeout(() => {
initChart();
}, 300);
window.addEventListener('resize', resizeChart);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeChart);
});
const chartInstances = {
day: null as echarts.ECharts | null,
month: null as echarts.ECharts | null,
year: null as echarts.ECharts | null,
};
onMounted(() => {
function initChart() {
//day
const chartDay = document.getElementById('day');
chartInstances.day = echarts.init(chartDay);
@@ -36,14 +47,10 @@ onMounted(() => {
show: true,
trigger: 'axis',
},
legend: {
data: ['当日', '昨日'],
},
toolbox: {
show: true,
feature: {
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
},
},
calculable: true,
@@ -51,32 +58,6 @@ onMounted(() => {
{
type: 'category',
name: '时',
data: [
'0:00',
'1:00',
'2:00',
'3:00',
'4:00',
'5:00',
'6:00',
'7:00',
'8:00',
'9:00',
'10:00',
'11:00',
'12:00',
'13:00',
'14:00',
'15:00',
'16:00',
'17:00',
'18:00',
'19:00',
'20:00',
'21:00',
'22:00',
'23:00',
],
},
],
yAxis: [
@@ -95,14 +76,10 @@ onMounted(() => {
tooltip: {
trigger: 'axis',
},
legend: {
data: ['当月', '上月'],
},
toolbox: {
show: true,
feature: {
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
},
},
calculable: true,
@@ -110,10 +87,6 @@ onMounted(() => {
{
type: 'category',
name: '日',
data: [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
],
},
],
yAxis: [
@@ -140,14 +113,10 @@ onMounted(() => {
tooltip: {
trigger: 'axis',
},
legend: {
data: ['当年', '去年'],
},
toolbox: {
show: true,
feature: {
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
},
},
calculable: true,
@@ -155,7 +124,6 @@ onMounted(() => {
{
type: 'category',
name: '月',
data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
},
],
yAxis: [
@@ -228,7 +196,13 @@ onMounted(() => {
dataZoomSelectActive: false,
});
});
});
}
function resizeChart() {
chartInstances.day && chartInstances.day.resize();
chartInstances.month && chartInstances.month.resize();
chartInstances.year && chartInstances.year.resize();
}
const hourTotal = ref<number>();
const dayTotal = ref<number>();
@@ -253,11 +227,18 @@ async function handleSelectFloor(selectedKeys, info) {
// 更新日数据图表
if (chartInstances.day && trend.hour) {
const yesterday = currentDay.value
.clone()
.subtract(1, 'day')
.format('YYYY-MM-DD');
hourTotal.value = trend.hour.today.total;
chartInstances.day.setOption({
legend: {
data: [yesterday, data.day],
},
series: [
{
name: '昨日',
name: yesterday,
type: 'bar',
data: trend.hour.yesterday.data || [],
markPoint: {
@@ -271,7 +252,7 @@ async function handleSelectFloor(selectedKeys, info) {
},
},
{
name: '当日',
name: data.day,
type: 'bar',
data: trend.hour.today.data || [],
markPoint: {
@@ -290,11 +271,18 @@ async function handleSelectFloor(selectedKeys, info) {
// 更新月数据图表
if (chartInstances.month && trend.day) {
const lastMonth = currentDay.value
.clone()
.subtract(1, 'month')
.format('YYYY-MM');
dayTotal.value = trend.day.nowMonth.total;
chartInstances.month.setOption({
legend: {
data: [lastMonth, data.month],
},
series: [
{
name: '上月',
name: lastMonth,
type: 'bar',
data: trend.day.lastMonth.data || [],
markPoint: {
@@ -308,7 +296,7 @@ async function handleSelectFloor(selectedKeys, info) {
},
},
{
name: '当月',
name: data.month,
type: 'bar',
data: trend.day.nowMonth.data || [],
markPoint: {
@@ -327,11 +315,18 @@ async function handleSelectFloor(selectedKeys, info) {
// 更新年数据图表
if (chartInstances.year && trend.month) {
const lastYear = currentDay.value
.clone()
.subtract(1, 'year')
.format('YYYY');
monthTotal.value = trend.month.nowYear.total;
chartInstances.year.setOption({
legend: {
data: [lastYear, data.year],
},
series: [
{
name: '去年',
name: lastYear,
type: 'bar',
data: trend.month.lastYear.data || [],
markPoint: {
@@ -345,7 +340,7 @@ async function handleSelectFloor(selectedKeys, info) {
},
},
{
name: '当年',
name: data.year,
type: 'bar',
data: trend.month.nowYear.data || [],
markPoint: {

View File

@@ -1,13 +1,23 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import {getDictOptions} from "#/utils/dict";
import dayjs from 'dayjs';
import type { FormSchemaGetter } from '#/adapter/form'
import type { VxeGridProps } from '#/adapter/vxe-table'
export const querySchema: FormSchemaGetter = () => [
{
component: 'Select',
componentProps: {
options: getDictOptions('pro_qoq_type'),
options: [
{
label: '日',
value: '0',
},
{
label: '月',
value: '1',
},
{
label: '年',
value: '2',
},
],
},
fieldName: 'date',
label: '日期',
@@ -16,47 +26,59 @@ export const querySchema: FormSchemaGetter = () => [
{
component: 'DatePicker',
componentProps: (formData) => {
const type: 0 | 1 | 2 | 3 = formData.date ?? 0;
const today = dayjs();
const type: 0 | 1 | 2 = formData.date ?? 0
const propsMap = {
0: {
picker: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
showTime: false,
defaultValue: today.format('YYYY-MM-DD'),
},
1: {
picker: 'week',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
showTime: false,
defaultValue: today.startOf('week').format('YYYY-MM-DD'),
},
2: {
picker: 'month',
format: 'YYYY-MM',
valueFormat: 'YYYY-MM',
showTime: false,
defaultValue: today.format('YYYY-MM'),
},
3: {
2: {
picker: 'year',
format: 'YYYY',
valueFormat: 'YYYY',
showTime: false,
defaultValue: today.format('YYYY'),
},
};
return propsMap[type];
}
return propsMap[type]
},
fieldName: 'chioceDate',
dependencies: {
triggerFields: ['date'],
},
},
];
]
export function initColumns(type: String) {
const columns: VxeGridProps['columns'] = [
{
title: '能源节点',
field: 'roomNumber',
},
{
title: type === '0' ? '当日用能(kw.h)' : type === '1' ? '当月用能(kw.h)' : '当年用能(kw.h)',
field: 'chargeItem',
},
{
title: type === '0' ? '昨日用能(kw.h)' : type === '1' ? '上月用能(kw.h)' : '去年用能(kw.h)',
field: 'chargeCycle',
},
{
title: '增加值(kw.h)',
field: 'startTime',
},
{
title: '环比(%)',
field: 'endTime',
},
]
return columns
}
export const columns: VxeGridProps['columns'] = [
{
title: '能源节点',
@@ -78,4 +100,4 @@ export const columns: VxeGridProps['columns'] = [
title: '环比(%)',
field: 'endTime',
},
];
]

View File

@@ -1,14 +1,13 @@
<script setup lang="ts">
import { Page, type VbenFormProps } from '@vben/common-ui'
import {
useVbenVxeGrid,
type VxeGridProps
} from '#/adapter/vxe-table'
import {
paymentReviewList,
} from '#/api/property/costManagement/paymentReview'
import { columns, querySchema } from './data'
import FloorTree from "../components/floor-tree.vue"
import dayjs from 'dayjs';
import { onMounted, ref } from 'vue';
import { cloneDeep } from '@vben/utils';
import { message } from 'ant-design-vue';
import { initColumns, querySchema } from './data';
import FloorTree from '../components/floor-tree.vue';
import { Page, type VbenFormProps } from '@vben/common-ui';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { meterRecordTrend } from '#/api/property/energyManagement/meterRecord';
const formOptions: VbenFormProps = {
commonConfig: {
@@ -19,45 +18,111 @@ 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: {
highlight: true,
reserve: true,
},
columns,
height: 'auto',
keepSource: true,
pagerConfig: {},
pagerConfig: {
enabled: false,
},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
return await paymentReviewList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
})
initColumnsTest(formValues);
},
},
},
rowConfig: {
keyField: 'id',
},
id: 'property-paymentReview-index'
}
id: 'property-paymentReview-index',
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const dateType = ref<String>(null);
const chioceDate = ref<String>('');
const idArr = ref<string[]>(['1', '2']);
const levelNode = ref<any>(null);
function initColumnsTest(form: any) {
tableApi.setGridOptions({
columns: initColumns(form.date),
});
tableApi.reload();
dateType.value = form.date;
chioceDate.value =
form.date === '0'
? form.chioceDate.format('YYYY-MM-DD')
: form.date === '1'
? form.chioceDate.format('YYYY-MM')
: form.date === '2'
? form.chioceDate.format('YYYY')
: '';
}
async function handleSelectFloor(selectedKeys, info) {
if (selectedKeys.length === 0) {
return;
}
if (!chioceDate.value) {
message.warning('请选择时间');
return;
}
let data = {
day: null,
month: null,
year: null,
meterType: 1,
meterId: null,
floorId: null,
};
if (dateType.value === '0') {
data.day = chioceDate.value;
} else if (dateType.value === '1') {
data.month = chioceDate.value;
} else if (dateType.value === '2') {
data.year = chioceDate.value;
}
if (info.node.level == 3) {
data.floorId = selectedKeys[0];
} else {
data.meterId = selectedKeys[0];
}
const res = await meterRecordTrend(data);
if (dateType.value === '0') {
res.hour.forEach(element => {
});
} else if (dateType.value === '1') {
data.month = chioceDate.value;
} else if (dateType.value === '2') {
data.year = chioceDate.value;
}
}
</script>
<template>
<Page :auto-content-height="true">
<div class="flex h-full gap-[8px]">
<FloorTree class="w-[260px]"></FloorTree>
<BasicTable class="flex-1 overflow-hidden" table-title="用电环比分析列表" />
<FloorTree class="w-[260px]" @select="handleSelectFloor"></FloorTree>
<BasicTable
class="flex-1 overflow-hidden"
table-title="用电环比分析列表"
/>
</div>
</Page>
</template>

View File

@@ -1,77 +1,60 @@
<script setup lang="ts">
import * as echarts from 'echarts'
import { onMounted, ref } from "vue"
import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import { Page } from '@vben/common-ui'
import { Table, DatePicker } from 'ant-design-vue'
import { SearchOutlined } from '@ant-design/icons-vue'
import FloorTree from "../components/floor-tree.vue"
import dayjs from 'dayjs';
import * as echarts from 'echarts';
import type { Dayjs } from 'dayjs';
import { Page } from '@vben/common-ui';
import { onMounted, onUnmounted, ref } from 'vue';
import { Table, DatePicker, message } from 'ant-design-vue';
import FloorTree from '../components/floor-tree.vue';
import { SearchOutlined } from '@ant-design/icons-vue';
import { meterRecordTrend } from '#/api/property/energyManagement/meterRecord';
const currentYear = ref<Dayjs>(dayjs())
const currentYear = ref<Dayjs>(dayjs());
const disabledYear = (current: Dayjs) => {
return current && current > dayjs().endOf('year')
}
return current && current > dayjs().endOf('year');
};
const chartInstances = {
year: null as echarts.ECharts | null,
};
onMounted(() => {
setTimeout(() => {
initChart();
}, 300);
window.addEventListener('resize', resizeChart);
});
onUnmounted(() => {
window.removeEventListener('resize', resizeChart);
});
function initChart() {
//year
const chartYear = document.getElementById('year')
const myChartYear = echarts.init(chartYear)
const chartYear = document.getElementById('year');
chartInstances.year = echarts.init(chartYear);
const optionYear = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['本期', '同期']
trigger: 'axis',
},
toolbox: {
show: true,
feature: {
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
}
},
},
calculable: true,
xAxis: [
{
type: 'category',
name: '月',
data: ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
}
},
],
yAxis: [
{
type: 'value',
name: 'KW.h'
}
],
series: [
{
name: '本期',
type: 'bar',
data: [
2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3
],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
name: 'KW.h',
},
{
name: '同期',
type: 'bar',
data: [
2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3
],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
}
],
dataZoom: [
{
@@ -81,76 +64,197 @@ onMounted(() => {
filterMode: 'filter',
},
],
}
optionYear && myChartYear.setOption(optionYear)
};
optionYear && chartInstances.year.setOption(optionYear);
// 鼠标悬停时激活缩放
myChartYear.on('mouseover', { seriesIndex: 0 }, () => {
myChartYear.dispatchAction({
chartInstances.year.on('mouseover', { seriesIndex: 0 }, () => {
chartInstances.year.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: true,
})
})
});
});
// 鼠标离开时取消缩放
myChartYear.on('mouseout', { seriesIndex: 0 }, () => {
myChartYear.dispatchAction({
chartInstances.year.on('mouseout', { seriesIndex: 0 }, () => {
chartInstances.year.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: false,
})
})
})
});
});
}
function resizeChart() {
chartInstances.year && chartInstances.year.clear();
}
const compareData = ref<any[]>(null);
const columns = [
{
title: '本期时间',
dataIndex: 'name',
dataIndex: 'month',
},
{
title: '本期能耗(kw.h)',
dataIndex: 'age',
dataIndex: 'now',
},
{
title: '同比能耗(kw.h)',
dataIndex: 'address',
dataIndex: 'last',
},
{
title: '同比(%)',
dataIndex: 'address',
dataIndex: 'percent',
},
]
];
const data = [...Array(32)].map((_, i) => ({
key: i,
name: `${i + 1}`,
age: '--',
address: `--`,
}));
const idArr = ref<string[]>([]);
const levelNode = ref<any>(null);
async function handleSelectFloor(selectedKeys, info) {
if (selectedKeys.length === 0 && idArr.value.length === 0) {
message.warning('请选择楼栋或电表');
return;
}
if (info != null) {
// 赋值节点层级
levelNode.value = info.node.level;
}
if (selectedKeys.length > 0) {
idArr.value = selectedKeys;
}
let data = {
year: currentYear.value.format('YYYY'),
day: null,
month: null,
meterType: 1,
meterId: null,
floorId: null,
};
if (levelNode.value == 3) {
data.floorId = selectedKeys.length > 0 ? selectedKeys[0] : idArr.value[0];
} else {
data.meterId = selectedKeys.length > 0 ? selectedKeys[0] : idArr.value[0];
}
const trend = await meterRecordTrend(data);
// 更新年数据图表
if (chartInstances.year && trend.month) {
const lastYear = currentYear.value
.clone()
.subtract(1, 'year')
.format('YYYY');
chartInstances.year.setOption({
legend: {
data: [lastYear, data.year],
},
series: [
{
name: lastYear,
type: 'bar',
data: trend.month.lastYear.data || [],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' },
],
},
markLine: {
data: [{ type: 'average', name: 'Avg' }],
},
},
{
name: data.year,
type: 'bar',
data: trend.month.nowYear.data || [],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' },
],
},
markLine: {
data: [{ type: 'average', name: 'Avg' }],
},
},
],
});
}
// 同比能耗
const lastYear = trend.month.lastYear.data || [];
const nowYear = trend.month.nowYear.data || [];
let tableData = [];
nowYear.forEach((now, i) => {
if (now[0] === lastYear[i][0]) {
let percent = (
((parseFloat(now[1]) - parseFloat(lastYear[i][1])) /
parseFloat(lastYear[i][1])) *
100
).toFixed(2);
tableData.push({
key: i,
month: `${i + 1}`,
now: parseInt(now[1]) === 0 ? '--' : now[1],
last: parseInt(lastYear[i][1]) === 0 ? '--' : lastYear[i][1],
percent:
parseInt(now[1]) !== 0 && parseInt(lastYear[i][1]) !== 0
? parseFloat(percent) >= 0
? percent + '↑'
: -percent + '↓'
: '--',
});
i;
}
});
compareData.value = tableData;
}
</script>
<template>
<Page :auto-content-height="true">
<div class="flex h-full gap-[8px]">
<FloorTree class="w-[260px]"></FloorTree>
<FloorTree class="w-[260px]" @select="handleSelectFloor"></FloorTree>
<div class="flex-1 overflow-hidden">
<div style="background: #fff;border-radius: 8px;padding: 10px;margin-bottom: 16px;">
<div
style="
background: #fff;
border-radius: 8px;
padding: 10px;
margin-bottom: 16px;
height: 33%;
"
>
<div>
年份
<DatePicker style="margin: 0 10px;" v-model:value="currentYear" :disabled-date="disabledYear" picker="year" />
<a-button type="primary">
<DatePicker
style="margin: 0 10px"
v-model:value="currentYear"
:disabled-date="disabledYear"
picker="year"
/>
<a-button type="primary" @click="handleSelectFloor([], null)">
<template #icon>
<SearchOutlined />
</template>
查询
</a-button>
</div>
<div id="year" style="height: 250px;width: 100%;"></div>
<div id="year" style="height: 100%; width: 100%"></div>
</div>
<div>
<Table style="border-radius: 8px;" :columns="columns" :data-source="data" :pagination="false"
:scroll="{ y: '50vh' }" />
<Table
style="border-radius: 8px"
:columns="columns"
:data-source="compareData"
:pagination="false"
/>
</div>
</div>
</div>

View File

@@ -1,31 +1,29 @@
<script setup lang="ts">
import { TableSwitch } from '#/components/table';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { TableSwitch } from "#/components/table"
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui'
import { getVxePopupContainer } from '@vben/utils'
import { ref } from 'vue'
import { columns, querySchema } from './data'
import { Modal, Popconfirm, Space } from 'ant-design-vue'
import FloorTree from "./floor-tree.vue"
import lightInfoDrawer from './lightInfo-drawer.vue'
import { ref } from 'vue';
import { columns, querySchema } from './data';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import FloorTree from './floor-tree.vue';
import lightInfoDrawer from './lightInfo-drawer.vue';
import {
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps
} from '#/adapter/vxe-table'
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
lightInfoList,
lightInfoRemove,
switchSingleLight,
} from '#/api/property/energyManagement/lightInfo'
import type { LightInfoForm } from '#/api/property/energyManagement/lightInfo/model'
} from '#/api/property/energyManagement/lightInfo';
import type { LightInfoForm } from '#/api/property/energyManagement/lightInfo/model';
// 左边楼层用
const selectFloorId = ref<string[]>([])
const selectFloorId = ref<string[]>([]);
const formOptions: VbenFormProps = {
commonConfig: {
@@ -37,15 +35,15 @@ const formOptions: VbenFormProps = {
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
handleReset: async () => {
selectFloorId.value = []
selectFloorId.value = [];
const { formApi, reload } = tableApi
await formApi.resetForm()
const formValues = formApi.form.values
formApi.setLatestSubmissionValues(formValues)
await reload(formValues)
const { formApi, reload } = tableApi;
await formApi.resetForm();
const formValues = formApi.form.values;
formApi.setLatestSubmissionValues(formValues);
await reload(formValues);
},
}
};
const gridOptions: VxeGridProps = {
checkboxConfig: {
@@ -65,17 +63,17 @@ const gridOptions: VxeGridProps = {
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
// 部门树选择处理
// 楼层树选择处理
if (selectFloorId.value.length === 1) {
formValues.floorId = selectFloorId.value[0]
formValues.floorId = selectFloorId.value[0];
} else {
Reflect.deleteProperty(formValues, 'floorId')
Reflect.deleteProperty(formValues, 'floorId');
}
return await lightInfoList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
})
});
},
},
},
@@ -83,88 +81,126 @@ const gridOptions: VxeGridProps = {
keyField: 'id',
},
// 表格全局唯一表示 保存列配置需要用到
id: 'property-lightInfo-index'
}
id: 'property-lightInfo-index',
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
})
});
const [LightInfoDrawer, drawerApi] = useVbenDrawer({
connectedComponent: lightInfoDrawer,
})
});
function handleAdd() {
drawerApi.setData({})
drawerApi.open()
drawerApi.setData({});
drawerApi.open();
}
async function handleEdit(row: Required<LightInfoForm>) {
drawerApi.setData({ id: row.id })
drawerApi.open()
drawerApi.setData({ id: row.id });
drawerApi.open();
}
async function handleDelete(row: Required<LightInfoForm>) {
await lightInfoRemove(row.id)
await tableApi.query()
await lightInfoRemove(row.id);
await tableApi.query();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords()
const ids = rows.map((row: Required<LightInfoForm>) => row.id)
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: Required<LightInfoForm>) => row.id);
Modal.confirm({
title: '提示',
okType: 'danger',
content: `确认删除选中的${ids.length}条记录吗?`,
onOk: async () => {
await lightInfoRemove(ids)
await tableApi.query()
await lightInfoRemove(ids);
await tableApi.query();
},
})
});
}
</script>
<template>
<Page :auto-content-height="true">
<div class="flex h-full gap-[8px]">
<FloorTree class="w-[260px]" @reload="() => tableApi.reload()" @select="() => tableApi.reload()"
v-model:select-floor-id="selectFloorId"></FloorTree>
<FloorTree
class="w-[260px]"
@reload="() => tableApi.reload()"
@select="() => tableApi.reload()"
v-model:select-floor-id="selectFloorId"
></FloorTree>
<BasicTable class="flex-1 overflow-hidden" table-title="灯控开关信息列表">
<template #toolbar-tools>
<Space>
<a-button type="primary" v-access:code="['property:lightInfo:add']" @click="handleAdd">
<a-button
type="primary"
v-access:code="['property:lightInfo:add']"
@click="handleAdd"
>
{{ $t('pages.common.add') }}
</a-button>
<a-button :disabled="!vxeCheckboxChecked(tableApi)" danger type="primary"
v-access:code="['property:lightInfo:remove']" @click="handleMultiDelete">
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['property:lightInfo:remove']"
@click="handleMultiDelete"
>
{{ $t('pages.common.delete') }}
</a-button>
<a-button :disabled="!vxeCheckboxChecked(tableApi)" danger type="primary">
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
>
关灯
</a-button>
<a-button :disabled="!vxeCheckboxChecked(tableApi)" success type="primary">
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
success
type="primary"
>
开灯
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button v-access:code="['property:lightInfo:edit']" @click.stop="handleEdit(row)">
<ghost-button
v-access:code="['property:lightInfo: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:lightInfo:remove']" @click.stop="">
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认删除?"
@confirm="handleDelete(row)"
>
<ghost-button
danger
v-access:code="['property:lightInfo:remove']"
@click.stop=""
>
{{ $t('pages.common.delete') }}
</ghost-button>
</Popconfirm>
</Space>
</template>
<template #isOn="{ row }">
<TableSwitch :checkedValue=true :unCheckedValue=false v-model:value="row.isOn" checked-children=""
un-checked-children="" :api="() => switchSingleLight({'id':row.id, 'isOn':row.isOn})"
@reload="() => tableApi.query()" />
<TableSwitch
:checkedValue="true"
:unCheckedValue="false"
v-model:value="row.isOn"
checked-children=""
un-checked-children=""
:api="() => switchSingleLight({ id: row.id, isOn: row.isOn })"
@reload="() => tableApi.query()"
/>
</template>
</BasicTable>
</div>

View File

@@ -39,8 +39,8 @@ export const querySchema: FormSchemaGetter = () => [
export const columns: VxeGridProps['columns'] = [
{ type: 'checkbox', width: 60 },
{
title: '仪表编号',
field: 'meterId',
title: '仪表名称',
field: 'meterName',
},
{
title: '仪表类型',
@@ -52,10 +52,6 @@ export const columns: VxeGridProps['columns'] = [
},
},
},
{
title: '抄表员ID',
field: 'readerId',
},
{
title: '抄表时间',
field: 'readingTime',
@@ -82,10 +78,10 @@ export const columns: VxeGridProps['columns'] = [
},
},
},
{
title: '抄表照片',
field: 'imgOssid',
},
// {
// title: '抄表照片',
// field: 'imgOssid',
// },
{
field: 'action',
fixed: 'right',

View File

@@ -0,0 +1,91 @@
<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";
defineOptions({ inheritAttrs: false });
withDefaults(defineProps<{ showSearch?: boolean }>(), { showSearch: true });
const emit = defineEmits<{
/**
* 点击刷新按钮的事件
*/
reload: [];
/**
* 点击节点的事件
*/
select: [];
}>();
const selectFloorId = defineModel("selectFloorId", {
type: Array as PropType<string[]>,
});
const searchValue = defineModel("searchValue", {
type: String,
default: "",
});
type TreeArray = CommunityVO[];
const treeArray = ref<TreeArray>([]);
/** 骨架屏加载 */
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;
}
});
treeArray.value = ret;
showTreeSkeleton.value = false;
}
onMounted(loadTree);
</script>
<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">
<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')">
<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) }}
</span>
<span v-else>{{ label }}</span>
</template>
</Tree>
<div v-else
class="mt-5">
<Empty :image="Empty.PRESENTED_IMAGE_SIMPLE"
description="暂无数据" />
</div>
</div>
</div>
</Skeleton>
</div>
</template>

View File

@@ -1,18 +1,20 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import FloorTree from './floor-tree.vue';
import type { Recordable } from '@vben/types';
import { columns, querySchema } from './data';
import { getVxePopupContainer } from '@vben/utils';
import meterRecordDrawer from './meterRecord-drawer.vue';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import { commonDownloadExcel } from '#/utils/file/download';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
import type { MeterRecordForm } from '#/api/property/energyManagement/meterRecord/model';
import {
import {
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
@@ -21,11 +23,8 @@ import {
meterRecordRemove,
} from '#/api/property/energyManagement/meterRecord';
import type { MeterRecordForm } from '#/api/property/energyManagement/meterRecord/model';
import { commonDownloadExcel } from '#/utils/file/download';
import meterRecordDrawer from './meterRecord-drawer.vue';
import { columns, querySchema } from './data';
// 左边楼层用
const selectFloorId = ref<string[]>([]);
const formOptions: VbenFormProps = {
commonConfig: {
@@ -65,6 +64,12 @@ const gridOptions: VxeGridProps = {
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
// 楼层树选择处理
if (selectFloorId.value.length === 1) {
formValues.floorId = selectFloorId.value[0];
} else {
Reflect.deleteProperty(formValues, 'floorId');
}
return await meterRecordList({
pageNum: page.currentPage,
pageSize: page.pageSize,
@@ -77,7 +82,7 @@ const gridOptions: VxeGridProps = {
keyField: 'id',
},
// 表格全局唯一表示 保存列配置需要用到
id: 'property-meterRecord-index'
id: 'property-meterRecord-index',
};
const [BasicTable, tableApi] = useVbenVxeGrid({
@@ -119,52 +124,67 @@ function handleMultiDelete() {
}
function handleDownloadExcel() {
commonDownloadExcel(meterRecordExport, '抄表记录数据', tableApi.formApi.form.values, {
fieldMappingTime: formOptions.fieldMappingTime,
});
commonDownloadExcel(
meterRecordExport,
'抄表记录数据',
tableApi.formApi.form.values,
{
fieldMappingTime: formOptions.fieldMappingTime,
},
);
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable table-title="抄表记录列表">
<template #toolbar-tools>
<Space>
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['property:meterRecord:remove']"
@click="handleMultiDelete">
{{ $t('pages.common.delete') }}
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
v-access:code="['property:meterRecord:edit']"
@click.stop="handleEdit(row)"
>
{{ $t('pages.common.edit') }}
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认删除?"
@confirm="handleDelete(row)"
>
<ghost-button
<div class="flex h-full gap-[8px]">
<FloorTree
class="w-[260px]"
@reload="() => tableApi.reload()"
@select="() => tableApi.reload()"
v-model:select-floor-id="selectFloorId"
></FloorTree>
<BasicTable class="flex-1 overflow-hidden" table-title="抄表记录列表">
<template #toolbar-tools>
<Space>
<a-button
danger
type="primary"
@click="handleMultiDelete"
:disabled="!vxeCheckboxChecked(tableApi)"
v-access:code="['property:meterRecord:remove']"
@click.stop=""
>
{{ $t('pages.common.delete') }}
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
@click.stop="handleEdit(row)"
v-access:code="['property:meterRecord:edit']"
>
{{ $t('pages.common.edit') }}
</ghost-button>
</Popconfirm>
</Space>
</template>
</BasicTable>
<Popconfirm
placement="left"
title="确认删除?"
@confirm="handleDelete(row)"
:get-popup-container="getVxePopupContainer"
>
<ghost-button
danger
@click.stop=""
v-access:code="['property:meterRecord:remove']"
>
{{ $t('pages.common.delete') }}
</ghost-button>
</Popconfirm>
</Space>
</template>
</BasicTable>
</div>
<MeterRecordDrawer @reload="tableApi.query()" />
</Page>
</template>

View File

@@ -1,80 +1,126 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import type { Dayjs } from 'dayjs'
import * as echarts from 'echarts'
import { onMounted, ref } from "vue"
import { Page } from '@vben/common-ui'
import { DatePicker } from 'ant-design-vue'
import FloorTree from "../components/floor-tree.vue"
import * as echarts from 'echarts';
import { onMounted, ref, nextTick } from 'vue';
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
import { Page } from '@vben/common-ui';
import { DatePicker } from 'ant-design-vue';
import FloorTree from '../components/floor-tree.vue';
import { meterRecordTrend } from '#/api/property/energyManagement/meterRecord';
const currentMonth = ref<Dayjs>(dayjs())
const currentYear = ref<Dayjs>(dayjs())
const currentDay = ref<Dayjs>(dayjs());
const currentMonth = ref<Dayjs>(dayjs());
const currentYear = ref<Dayjs>(dayjs());
const disabledDay = (current: Dayjs) => {
return current && current > dayjs().endOf('day');
};
const disabledMonth = (current: Dayjs) => {
return current && current > dayjs().endOf('month')
}
return current && current > dayjs().endOf('month');
};
const disabledYear = (current: Dayjs) => {
return current && current > dayjs().endOf('year')
}
return current && current > dayjs().endOf('year');
};
const chartInstances = {
day: null as echarts.ECharts | null,
month: null as echarts.ECharts | null,
year: null as echarts.ECharts | null,
};
onMounted(() => {
//month
const chartMonth = document.getElementById('month')
const myChartMonth = echarts.init(chartMonth)
const optionMonth = {
//day
const chartDay = document.getElementById('day');
chartInstances.day = echarts.init(chartDay);
const optionDay = {
tooltip: {
trigger: 'axis'
show: true,
trigger: 'axis',
},
legend: {
data: ['当', '上月']
data: ['当', '昨日'],
},
toolbox: {
show: true,
feature: {
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
}
},
},
calculable: true,
xAxis: [
{
type: 'category',
name: '时',
data: [
'0:00',
'1:00',
'2:00',
'3:00',
'4:00',
'5:00',
'6:00',
'7:00',
'8:00',
'9:00',
'10:00',
'11:00',
'12:00',
'13:00',
'14:00',
'15:00',
'16:00',
'17:00',
'18:00',
'19:00',
'20:00',
'21:00',
'22:00',
'23:00',
],
},
],
yAxis: [
{
type: 'value',
name: 'KW.h',
},
],
};
optionDay && chartInstances.day.setOption(optionDay);
//month
const chartMonth = document.getElementById('month');
chartInstances.month = echarts.init(chartMonth);
const optionMonth = {
tooltip: {
trigger: 'axis',
},
legend: {
data: ['当月', '上月'],
},
toolbox: {
show: true,
feature: {
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
},
},
calculable: true,
xAxis: [
{
type: 'category',
name: '日',
data: ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31']
}
data: [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
],
},
],
yAxis: [
{
type: 'value',
name: 't'
}
],
series: [
{
name: '当月',
type: 'bar',
data: [
2.0, 4.9, 7.0, 23.2, 25.6, 0.0, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3, 2.0, 0.0, 7.0, 23.2, 0.0, 76.7, 135.6, 162.2, 32.6, 0.0, 6.4, 3.3, 2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6
],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
name: 'KW.h',
},
{
name: '上月',
type: 'bar',
data: [
2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 0.0, 48.7, 18.8, 6.0, 2.3, 2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 0.0, 32.6, 20.0, 6.4, 3.3, 2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6
],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
}
],
dataZoom: [
{
@@ -84,67 +130,39 @@ onMounted(() => {
filterMode: 'filter',
},
],
}
optionMonth && myChartMonth.setOption(optionMonth)
};
optionMonth && chartInstances.month.setOption(optionMonth);
//year
const chartYear = document.getElementById('year')
const myChartYear = echarts.init(chartYear)
const chartYear = document.getElementById('year');
chartInstances.year = echarts.init(chartYear);
const optionYear = {
tooltip: {
trigger: 'axis'
trigger: 'axis',
},
legend: {
data: ['当年', '去年']
data: ['当年', '去年'],
},
toolbox: {
show: true,
feature: {
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
}
},
},
calculable: true,
xAxis: [
{
type: 'category',
name: '月',
data: ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
}
data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
},
],
yAxis: [
{
type: 'value',
name: 't'
}
],
series: [
{
name: '当年',
type: 'bar',
data: [
2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3
],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
name: 'KW.h',
},
{
name: '去年',
type: 'bar',
data: [
2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3
],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
}
],
dataZoom: [
{
@@ -154,67 +172,263 @@ onMounted(() => {
filterMode: 'filter',
},
],
};
optionYear && chartInstances.year.setOption(optionYear);
// 鼠标悬停时激活缩放
chartInstances.day.on('mouseover', { seriesIndex: 0 }, () => {
chartInstances.day.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: true,
});
});
// 鼠标离开时取消缩放
chartInstances.day.on('mouseout', { seriesIndex: 0 }, () => {
chartInstances.day.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: false,
});
});
// 鼠标悬停时激活缩放
chartInstances.year.on('mouseover', { seriesIndex: 0 }, () => {
chartInstances.year.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: true,
});
});
// 鼠标离开时取消缩放
chartInstances.year.on('mouseout', { seriesIndex: 0 }, () => {
chartInstances.year.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: false,
});
});
// 鼠标悬停时激活缩放
chartInstances.month.on('mouseover', { seriesIndex: 0 }, () => {
chartInstances.month.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: true,
});
});
// 鼠标离开时取消缩放
chartInstances.month.on('mouseout', { seriesIndex: 0 }, () => {
chartInstances.month.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: false,
});
});
});
const hourTotal = ref<number>();
const dayTotal = ref<number>();
const monthTotal = ref<number>();
async function handleSelectFloor(selectedKeys, info) {
let data = {
day: currentDay.value.format('YYYY-MM-DD'),
month: currentMonth.value.format('YYYY-MM'),
year: currentYear.value.format('YYYY'),
meterType: 2,
meterId: null,
floorId: null,
};
if (info.node.level == 3) {
data.floorId = selectedKeys[0];
} else {
data.meterId = selectedKeys[0];
}
optionYear && myChartYear.setOption(optionYear)
// 鼠标悬停时激活缩放
myChartYear.on('mouseover', { seriesIndex: 0 }, () => {
myChartYear.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: true,
})
})
const trend = await meterRecordTrend(data);
// 鼠标离开时取消缩放
myChartYear.on('mouseout', { seriesIndex: 0 }, () => {
myChartYear.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: false,
})
})
// 更新日数据图表
if (chartInstances.day && trend.hour) {
hourTotal.value = trend.hour.today.total;
chartInstances.day.setOption({
series: [
{
name: '昨日',
type: 'bar',
data: trend.hour.yesterday.data || [],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' },
],
},
markLine: {
data: [{ type: 'average', name: 'Avg' }],
},
},
{
name: '当日',
type: 'bar',
data: trend.hour.today.data || [],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' },
],
},
markLine: {
data: [{ type: 'average', name: 'Avg' }],
},
},
],
});
}
// 鼠标悬停时激活缩放
myChartMonth.on('mouseover', { seriesIndex: 0 }, () => {
myChartMonth.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: true,
})
})
// 更新月数据图表
if (chartInstances.month && trend.day) {
dayTotal.value = trend.day.nowMonth.total;
chartInstances.month.setOption({
series: [
{
name: '上月',
type: 'bar',
data: trend.day.lastMonth.data || [],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' },
],
},
markLine: {
data: [{ type: 'average', name: 'Avg' }],
},
},
{
name: '当月',
type: 'bar',
data: trend.day.nowMonth.data || [],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' },
],
},
markLine: {
data: [{ type: 'average', name: 'Avg' }],
},
},
],
});
}
// 鼠标离开时取消缩放
myChartMonth.on('mouseout', { seriesIndex: 0 }, () => {
myChartMonth.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: false,
})
})
})
// 更新年数据图表
if (chartInstances.year && trend.month) {
monthTotal.value = trend.month.nowYear.total;
chartInstances.year.setOption({
series: [
{
name: '去年',
type: 'bar',
data: trend.month.lastYear.data || [],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' },
],
},
markLine: {
data: [{ type: 'average', name: 'Avg' }],
},
},
{
name: '当年',
type: 'bar',
data: trend.month.nowYear.data || [],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' },
],
},
markLine: {
data: [{ type: 'average', name: 'Avg' }],
},
},
],
});
}
}
</script>
<template>
<Page :auto-content-height="true">
<div class="flex h-full gap-[8px]">
<FloorTree class="w-[260px]"></FloorTree>
<FloorTree class="w-[260px]" @select="handleSelectFloor"></FloorTree>
<div class="flex-1 overflow-hidden">
<div style="background: #fff;border-radius: 8px;padding: 10px;height: 50%;">
<div
style="
background: #fff;
border-radius: 8px;
padding: 10px;
height: 33%;
"
>
<div>
<div style="display: flex;justify-content: space-between;">
<DatePicker v-model:value="currentMonth" :disabled-date="disabledMonth" picker="month" />当月能耗总值30.00t
<div style="display: flex; justify-content: space-between">
<DatePicker
v-model:value="currentDay"
:disabled-date="disabledDay"
/>
<span>当日能耗总值{{ hourTotal }}立方米</span>
</div>
</div>
<div id="month" style="height: 100%;width: 100%;"></div>
<div id="day" style="height: 100%; width: 100%"></div>
</div>
<div style="background: #fff;border-radius: 8px;padding: 10px;margin-top: 16px;height: 50%;">
<div
style="
background: #fff;
border-radius: 8px;
padding: 10px;
margin-top: 16px;
height: 33%;
"
>
<div>
<div style="display: flex;justify-content: space-between;">
<DatePicker v-model:value="currentYear" :disabled-date="disabledYear" picker="year" />当年能耗总值59.00t
<div style="display: flex; justify-content: space-between">
<DatePicker
v-model:value="currentMonth"
:disabled-date="disabledMonth"
picker="month"
/>
<span>当月能耗总值{{ dayTotal }}立方米</span>
</div>
</div>
<div id="year" style="height: 100%;width: 100%;"></div>
<div id="month" style="height: 100%; width: 100%"></div>
</div>
<div
style="
background: #fff;
border-radius: 8px;
padding: 10px;
margin-top: 16px;
height: 33%;
"
>
<div>
<div style="display: flex; justify-content: space-between">
<DatePicker
v-model:value="currentYear"
:disabled-date="disabledYear"
picker="year"
/>
<span>当年能耗总值{{ monthTotal }}立方米</span>
</div>
</div>
<div id="year" style="height: 100%; width: 100%"></div>
</div>
</div>
</div>

View File

@@ -2,7 +2,8 @@ import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getDictOptions } from '#/utils/dict';
import { renderDict } from '#/utils/render';
import {rentalOrderList} from "#/api/property/rentalOrder";
import { rentalOrderList } from '#/api/property/rentalOrder';
import { resident_unitList } from '#/api/property/resident/unit';
export const querySchema: FormSchemaGetter = () => [
{
@@ -62,55 +63,40 @@ export const querySchema: FormSchemaGetter = () => [
export const columns: VxeGridProps['columns'] = [
{ type: 'checkbox', width: 60 },
{
title: '序号',
field: 'id',
slots: {
default: ({ rowIndex }) => {
return (rowIndex + 1).toString();
},
},
minWidth: '120'
},
{
title: '订单号',
field: 'orderId',
minWidth: '120'
},
{
title: '租赁合同编号',
field: 'userId',
minWidth: '120'
minWidth: '120',
},
{
title: '租赁人',
field: 'userName',
minWidth: '120'
minWidth: '120',
},
{
title: '租金',
field: 'rent',
minWidth: '120'
minWidth: '120',
},
{
title: '押金',
field: 'deposit',
minWidth: '120'
minWidth: '120',
},
{
title: '违约金',
field: 'penalty',
minWidth: '120'
minWidth: '120',
},
{
title: '总金额',
field: 'totalAmount',
minWidth: '120'
minWidth: '120',
},
{
title: '收费日期',
field: 'chargeDate',
minWidth: '120'
minWidth: '120',
},
{
title: '支付方式',
@@ -120,7 +106,7 @@ export const columns: VxeGridProps['columns'] = [
return renderDict(row.paymentMethod, 'pro_payment_method');
},
},
minWidth: '120'
minWidth: '120',
},
{
title: '开票状态',
@@ -130,7 +116,7 @@ export const columns: VxeGridProps['columns'] = [
return renderDict(row.invoiceStatus, 'pro_invoice_status');
},
},
minWidth: '120'
minWidth: '120',
},
{
title: '发票类型',
@@ -140,7 +126,7 @@ export const columns: VxeGridProps['columns'] = [
return renderDict(row.invoiceType, 'pro_invoice_type');
},
},
minWidth: '120'
minWidth: '120',
},
{
title: '收费状态',
@@ -150,12 +136,12 @@ export const columns: VxeGridProps['columns'] = [
return renderDict(row.chargeStatus, 'pro_charging_status');
},
},
minWidth: '120'
minWidth: '120',
},
{
title: '创建时间',
field: 'createTime',
minWidth: '120'
minWidth: '120',
},
{
field: 'action',
@@ -184,54 +170,61 @@ export const modalSchema: FormSchemaGetter = () => [
api: rentalOrderList,
resultField: 'rows',
labelField: 'orderNo',
valueField: 'id',
},
rules: 'required',
},
// {
// label: '租赁人ID',
// fieldName: 'userId',
// component: 'ApiSelect',
// componentProps: {
// api: rentalOrderList,
// resultField: 'rows',
// labelField: 'customerName',
// valueField: 'id',
// },
// rules: 'required',
// },
{
component: 'ApiSelect',
fieldName: 'residentUnitId',
label: '租赁单位',
componentProps: () => ({
api: getUnitList,
resultField: 'data',
labelField: 'label',
valueField: 'value',
immediate: true,
debounceTime: 500,
allowClear: true,
placeholder: '',
filterOption: true,
}),
rules: 'selectRequired',
disabled: true,
},
{
label: '租赁人',
fieldName: 'userName',
component: "Select",
component: 'Select',
rules: 'selectRequired',
},
{
label: '租金',
fieldName: 'rent',
component: 'InputNumber',
rules: 'required',
componentProps:{
componentProps: {
precision: 2,
}
},
},
{
label: '押金',
fieldName: 'deposit',
component: 'InputNumber',
rules: 'required',
componentProps:{
componentProps: {
precision: 2,
}
},
},
{
label: '违约金',
fieldName: 'penalty',
component: 'InputNumber',
rules: 'required',
componentProps:{
componentProps: {
precision: 2,
}
},
},
// {
// label: '总金额',
@@ -287,3 +280,21 @@ export const modalSchema: FormSchemaGetter = () => [
rules: 'required',
},
];
export async function getUnitList(): Promise<
{ value: number; label: string }[]
> {
const queryParam = {
pageNum: 1,
pageSize: 1000,
};
const res = await resident_unitList(queryParam);
const data: { value: number; label: string }[] = [];
res.rows.forEach((r: any) => {
data.push({
value: r.id,
label: r.name,
});
});
return data;
}

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
import type {orderChargeDetailForm} from '#/api/property/chargeManagement/model';
import {shallowRef} from 'vue';
import {useVbenModal} from '@vben/common-ui';
import {Descriptions, DescriptionsItem} from 'ant-design-vue';
import type { orderChargeDetailForm } from '#/api/property/chargeManagement/model';
import { shallowRef } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Descriptions, DescriptionsItem } from 'ant-design-vue';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime';
import {orderChargeInfo} from '#/api/property/chargeManagement';
import {renderDict} from "#/utils/render";
import { orderChargeInfo } from '#/api/property/chargeManagement';
import { renderDict } from '#/utils/render';
dayjs.extend(duration);
dayjs.extend(relativeTime);
@@ -25,7 +25,7 @@ async function handleOpenChange(open: boolean) {
return null;
}
modalApi.modalLoading(true);
const {id} = modalApi.getData() as { id: number | string };
const { id } = modalApi.getData() as { id: number | string };
const response = await orderChargeInfo(id);
orderChargeDetail.value = response;
modalApi.modalLoading(false);
@@ -33,16 +33,30 @@ async function handleOpenChange(open: boolean) {
</script>
<template>
<BasicModal :footer="false" :fullscreen-button="false" title="查看收费" class="w-[70%]">
<Descriptions v-if="orderChargeDetail" size="small" :column="2" bordered :labelStyle="{width:'100px'}">
<BasicModal
:footer="false"
:fullscreen-button="false"
title="查看收费"
class="w-[70%]"
>
<Descriptions
v-if="orderChargeDetail"
size="small"
:column="2"
bordered
:labelStyle="{ width: '100px' }"
>
<DescriptionsItem label="订单号">
{{ orderChargeDetail.orderId }}
</DescriptionsItem>
<DescriptionsItem label="租赁合同编号">
{{ orderChargeDetail.rentalOrder.contractCode }}
<!-- <DescriptionsItem label="租赁合同编号">-->
<!-- {{ orderChargeDetail.rentalOrder.contractCode }}-->
<!-- </DescriptionsItem>-->
<DescriptionsItem label="租赁单位">
{{ orderChargeDetail.residentUnitText }}
</DescriptionsItem>
<DescriptionsItem label="租赁人">
{{ orderChargeDetail.userName}}
{{ orderChargeDetail.userName }}
</DescriptionsItem>
<DescriptionsItem label="租金">
{{ orderChargeDetail.rent }}
@@ -53,32 +67,53 @@ async function handleOpenChange(open: boolean) {
<DescriptionsItem label="违约金">
{{ orderChargeDetail.penalty }}
</DescriptionsItem>
<DescriptionsItem label="开票状态" v-if="orderChargeDetail.invoiceStatus!=null">
<DescriptionsItem
label="开票状态"
v-if="orderChargeDetail.invoiceStatus != null"
>
<component
:is="renderDict(orderChargeDetail.invoiceStatus,'pro_invoice_status')"
:is="
renderDict(orderChargeDetail.invoiceStatus, 'pro_invoice_status')
"
/>
</DescriptionsItem>
<DescriptionsItem label="发票类型" v-if="orderChargeDetail.invoiceType!=null">
<DescriptionsItem
label="发票类型"
v-if="orderChargeDetail.invoiceType != null"
>
<component
:is="renderDict(orderChargeDetail.invoiceType,'pro_invoice_type')"
:is="renderDict(orderChargeDetail.invoiceType, 'pro_invoice_type')"
/>
</DescriptionsItem>
<DescriptionsItem label="收费状态" v-if="orderChargeDetail.chargeStatus!=null">
<DescriptionsItem
label="收费状态"
v-if="orderChargeDetail.chargeStatus != null"
>
<component
:is="renderDict(orderChargeDetail.chargeStatus,'pro_charging_status')"
:is="
renderDict(orderChargeDetail.chargeStatus, 'pro_charging_status')
"
/>
</DescriptionsItem>
<DescriptionsItem label="产品编号">
{{ orderChargeDetail.rentalOrder.productList.map(item => item.plantCode).join('、') }}
{{
orderChargeDetail.rentalOrder.productList
.map((item) => item.plantCode)
.join('、')
}}
</DescriptionsItem>
<DescriptionsItem label="购买植物">
{{ orderChargeDetail.rentalOrder.productList.map(item => item.plantName).join('、') }}
{{
orderChargeDetail.rentalOrder.productList
.map((item) => item.plantName)
.join('、')
}}
</DescriptionsItem>
<DescriptionsItem label="总金额">
{{ orderChargeDetail.totalAmount }}
</DescriptionsItem>
<DescriptionsItem label="产品图片">
<img :src="orderChargeDetail.createTime" alt="">
<img :src="orderChargeDetail.createTime" alt="" />
</DescriptionsItem>
</Descriptions>
</BasicModal>

View File

@@ -1,13 +1,18 @@
<script setup lang="ts">
import {computed, reactive, ref} from 'vue';
import { computed, reactive, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { orderChargeAdd, orderChargeInfo, orderChargeUpdate } from '#/api/property/chargeManagement';
import {
orderChargeAdd,
orderChargeInfo,
orderChargeUpdate,
} from '#/api/property/chargeManagement';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
import QueryUserList from "#/views/property/greenPlantRentalManagement/chargeManagement/query-user-list.vue";
import { resident_unitList } from '#/api/property/resident/unit';
import { rentalNotSelectList } from '#/api/property/rentalOrder';
import { getDictOptions } from '#/utils/dict';
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
@@ -15,16 +20,181 @@ const isUpdate = ref(false);
const title = computed(() => {
return isUpdate.value ? $t('pages.common.edit') : '添加收费';
});
const modalSchema = [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '订单号',
fieldName: 'orderNo',
component: 'Input',
dependencies: {
show: () => isUpdate.value,
triggerFields: [''],
},
disabled: true,
},
{
label: '订单号',
fieldName: 'orderId',
component: 'ApiSelect',
componentProps: {
api: rentalNotSelectList,
resultField: 'rows',
labelField: 'orderNo',
valueField: 'id',
onChange: async (value: any, option: any) => {
await formApi.setValues({
residentUnitId: option.residentUnitId, // 假设订单数据中有 residentUnitId 字段
userName:option.customerName,
rent:option.totalAmount
});
},
},
rules: 'required',
dependencies: {
show: () => !isUpdate.value,
triggerFields: [''],
},
},
{
component: 'ApiSelect',
fieldName: 'residentUnitId',
label: '租赁单位',
componentProps: () => ({
api: getUnitList,
resultField: 'data',
labelField: 'label',
valueField: 'value',
immediate: true,
debounceTime: 500,
allowClear: true,
placeholder: '',
filterOption: true,
}),
rules: 'selectRequired',
disabled: true,
},
{
label: '租赁人',
fieldName: 'userName',
component: 'Input',
disabled: true,
},
{
label: '租金',
fieldName: 'rent',
component: 'InputNumber',
componentProps: {
precision: 2,
},
disabled: true,
},
{
label: '押金',
fieldName: 'deposit',
component: 'InputNumber',
rules: 'required',
componentProps: {
precision: 2,
},
},
{
label: '违约金',
fieldName: 'penalty',
component: 'InputNumber',
rules: 'required',
componentProps: {
precision: 2,
},
},
// {
// label: '总金额',
// fieldName: 'totalAmount',
// component: 'Input',
// rules: 'required',
// },
{
label: '开票状态',
fieldName: 'invoiceStatus',
component: 'Select',
componentProps: {
options: getDictOptions('pro_invoice_status'),
},
rules: 'selectRequired',
},
{
label: '发票类型',
fieldName: 'invoiceType',
component: 'Select',
componentProps: {
options: getDictOptions('pro_invoice_type'),
},
rules: 'selectRequired',
},
{
label: '收费状态',
fieldName: 'chargeStatus',
component: 'Select',
componentProps: {
options: getDictOptions('pro_charging_status'),
},
rules: 'selectRequired',
},
{
label: '支付方式',
fieldName: 'paymentMethod',
component: 'Select',
componentProps: {
options: getDictOptions('pro_payment_method'),
},
rules: 'selectRequired',
},
{
label: '收费日期',
fieldName: 'chargeDate',
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
];
async function getUnitList() {
const queryParam = {
pageNum: 1,
pageSize: 1000,
};
const res = await resident_unitList(queryParam);
const data: { value: number; label: string }[] = [];
res.rows.forEach((r: any) => {
data.push({
value: r.id,
label: r.name,
});
});
return data;
}
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
formItemClass: 'col-span-1',
labelWidth: 80,
componentProps: {
class: 'w-full',
}
},
},
schema: modalSchema(),
schema: modalSchema,
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
@@ -55,6 +225,8 @@ const [BasicModal, modalApi] = useVbenModal({
record.paymentMethod = record.paymentMethod?.toString();
record.invoiceType = record.invoiceType?.toString();
record.invoiceStatus = record.invoiceStatus?.toString();
record.orderNo = record.rentalOrder?.orderNo;
record.residentUnitId = record.residentUnitId;
await formApi.setValues(record);
}
await markInitialized();
@@ -70,10 +242,6 @@ async function handleConfirm() {
return;
}
const data = cloneDeep(await formApi.getValues());
if (userInfo) {
data.userId = userInfo.userId
data.userName = userInfo.userName
}
await (isUpdate.value ? orderChargeUpdate(data) : orderChargeAdd(data));
resetInitialized();
emit('reload');
@@ -84,14 +252,6 @@ async function handleConfirm() {
modalApi.lock(false);
}
}
let userInfo = reactive({
userId: '',
userName: '',
});
const userName = ref<number | string>('');
function getUserInfo(user: any) {
userInfo = user;
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
@@ -101,10 +261,6 @@ async function handleClosed() {
<template>
<BasicModal :title="title">
<BasicForm>
<template #userName="slotProps">
<QueryUserList @update:userInfo="getUserInfo" v-bind="slotProps" :isUpdate="isUpdate" :userName="userName"/>
</template>
</BasicForm>
</BasicModal>
</template>

View File

@@ -1,24 +1,23 @@
<script setup lang="ts">
import { ref, shallowRef } from 'vue';
import {ref, shallowRef} from 'vue';
import { useVbenModal } from '@vben/common-ui';
import {useVbenModal} from '@vben/common-ui';
import {Descriptions, DescriptionsItem} from 'ant-design-vue';
import { Descriptions, DescriptionsItem } from 'ant-design-vue';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime';
import {renderDict} from "#/utils/render";
import type {RentalOrderVO} from "#/api/property/rentalOrder/model";
import {rentalOrderInfo} from "#/api/property/rentalOrder";
import {rentalColumns} from "./data";
import {Table} from "ant-design-vue";
import { renderDict } from '#/utils/render';
import type { RentalOrderVO } from '#/api/property/rentalOrder/model';
import { rentalOrderInfo } from '#/api/property/rentalOrder';
import { rentalColumns } from './data';
import { Table } from 'ant-design-vue';
dayjs.extend(duration);
dayjs.extend(relativeTime);
const planProducts=ref([]);
const planProducts = ref([]);
const [BasicModal, modalApi] = useVbenModal({
onOpenChange: handleOpenChange,
@@ -27,7 +26,7 @@ const [BasicModal, modalApi] = useVbenModal({
},
});
const orderDetail = shallowRef<null |RentalOrderVO>(null);
const orderDetail = shallowRef<null | RentalOrderVO>(null);
async function handleOpenChange(open: boolean) {
if (!open) {
@@ -35,73 +34,90 @@ async function handleOpenChange(open: boolean) {
}
modalApi.modalLoading(true);
const {id} = modalApi.getData() as { id: number | string };
const { id } = modalApi.getData() as { id: number | string };
// 赋值
orderDetail.value = await rentalOrderInfo(id);
planProducts.value=orderDetail.value?.productList
planProducts.value = orderDetail.value?.productList;
modalApi.modalLoading(false);
}
</script>
<template>
<BasicModal :footer="false" :fullscreen-button="false" title="租赁订单信息" class="w-[70%]">
<Descriptions v-if="orderDetail" size="small" :column="2" bordered :labelStyle="{width:'100px'}">
<BasicModal
:footer="false"
:fullscreen-button="false"
title="租赁订单信息"
class="w-[70%]"
>
<Descriptions
v-if="orderDetail"
size="small"
:column="2"
bordered
:labelStyle="{ width: '100px' }"
>
<DescriptionsItem label="订单号">
{{ orderDetail.orderNo }}
{{ orderDetail.orderNo }}
</DescriptionsItem>
<DescriptionsItem label="客户名称">
{{ orderDetail.customerName }}
</DescriptionsItem>
<DescriptionsItem label="客户类型" v-if="orderDetail.type!=null">
<component
:is="renderDict(orderDetail.customerType,'wy_khlx')"
/>
<DescriptionsItem label="客户类型" v-if="orderDetail.type != null">
<component :is="renderDict(orderDetail.customerType, 'wy_khlx')" />
</DescriptionsItem>
<DescriptionsItem label="租赁单位">
{{ orderDetail.residentUnitText }}
</DescriptionsItem>
<DescriptionsItem label="租赁周期">
{{ orderDetail.rentalPeriod}}
{{ orderDetail.rentalPeriod }}
</DescriptionsItem>
<DescriptionsItem label="租赁时间" :span="2">
{{dayjs(orderDetail.startTime).format("YYYY-MM-DD")+' 至 '+dayjs(orderDetail.endTime).format("YYYY-MM-DD")}}
{{
dayjs(orderDetail.startTime).format('YYYY-MM-DD') +
' 至 ' +
dayjs(orderDetail.endTime).format('YYYY-MM-DD')
}}
</DescriptionsItem>
<DescriptionsItem label="总金额">
{{ orderDetail.totalAmount +"元"}}
{{ orderDetail.totalAmount + '元' }}
</DescriptionsItem>
<DescriptionsItem label="租赁方式">
<component
:is="renderDict(orderDetail.rentalType,'wy_zlfs')"
/>
<component :is="renderDict(orderDetail.rentalType, 'wy_zlfs')" />
</DescriptionsItem>
<!-- <DescriptionsItem label="支付状态">-->
<!-- <component-->
<!-- :is="renderDict(orderDetail.paymentStatus,'pro_charging_status')"-->
<!-- />-->
<!-- </DescriptionsItem>-->
<!-- <DescriptionsItem label="支付状态">-->
<!-- <component-->
<!-- :is="renderDict(orderDetail.paymentStatus,'pro_charging_status')"-->
<!-- />-->
<!-- </DescriptionsItem>-->
<DescriptionsItem label="是否续租">
<component
:is="renderDict(orderDetail.isRelet,'wy_sf')"
/>
<component :is="renderDict(orderDetail.isRelet, 'wy_sf')" />
</DescriptionsItem>
<DescriptionsItem label="合同状态">
<component
:is="renderDict(orderDetail.contractStatus,'wy_htzt')"
/>
<component :is="renderDict(orderDetail.contractStatus, 'wy_htzt')" />
</DescriptionsItem>
<DescriptionsItem label="合同编号" v-if="orderDetail.contractStatus!='1'">
<DescriptionsItem
label="合同编号"
v-if="orderDetail.contractStatus != '1'"
>
{{ orderDetail.contractCode ?? '无' }}
</DescriptionsItem>
<DescriptionsItem label="签署时间" v-if="orderDetail.contractStatus!='1'">
<DescriptionsItem
label="签署时间"
v-if="orderDetail.contractStatus != '1'"
>
{{ orderDetail.signTime ?? '无' }}
</DescriptionsItem>
</Descriptions>
<div v-if="planProducts.length">
<div style="margin: 10px 0;font-weight: 500">
订单产品详情
</div>
<Table :dataSource="planProducts" :columns="rentalColumns" :pagination="false"
bordered size="small">
<div style="margin: 10px 0; font-weight: 500">订单产品详情</div>
<Table
:dataSource="planProducts"
:columns="rentalColumns"
:pagination="false"
bordered
size="small"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.field === 'id'">
{{ index + 1 }}

View File

@@ -1,25 +1,26 @@
<script setup lang="ts">
import {computed, ref} from 'vue';
import { computed, ref } from 'vue';
import {useVbenModal} from '@vben/common-ui';
import {$t} from '@vben/locales';
import {cloneDeep} from '@vben/utils';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import {useVbenForm} from '#/adapter/form';
import { useVbenForm } from '#/adapter/form';
import {
rentalOrderAdd,
rentalOrderInfo,
rentalOrderUpdate
rentalOrderUpdate,
} from '#/api/property/rentalOrder';
import {defaultFormValueGetter, useBeforeCloseDiff} from '#/utils/popup';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import {plantsProductList} from "#/api/property/productManagement";
import {rentalPlanInfo, rentalPlanList} from "#/api/property/rentalPlan";
import {getDictOptions} from "#/utils/dict";
import type {PropertyVO} from "#/api/property/productManagement/model";
import type {RentalPlanVO} from "#/api/property/rentalPlan/model";
import {planInfoColumns} from './data';
import {Table} from "ant-design-vue";
import { plantsProductList } from '#/api/property/productManagement';
import { rentalPlanInfo, rentalPlanList } from '#/api/property/rentalPlan';
import { getDictOptions } from '#/utils/dict';
import type { PropertyVO } from '#/api/property/productManagement/model';
import type { RentalPlanVO } from '#/api/property/rentalPlan/model';
import { planInfoColumns } from './data';
import { Table } from 'ant-design-vue';
import { resident_unitList } from '#/api/property/resident/unit';
const emit = defineEmits<{ reload: [] }>();
@@ -54,7 +55,24 @@ const modalSchema = [
fieldName: 'customerType',
component: 'Select',
componentProps: {
options: getDictOptions('wy_khlx')
options: getDictOptions('wy_khlx'),
},
rules: 'selectRequired',
},
{
component: 'ApiSelect',
fieldName: 'residentUnitId',
label: '租赁单位',
componentProps: {
api: getUnitList,
resultField: 'data',
labelField: 'label',
valueField: 'value',
immediate: true,
debounceTime: 500,
allowClear: true,
placeholder: '请选择租赁单位',
filterOption: true,
},
rules: 'selectRequired',
},
@@ -63,7 +81,7 @@ const modalSchema = [
fieldName: 'rentalPeriod',
component: 'Select',
componentProps: {
options: getDictOptions('wy_time_unit')
options: getDictOptions('wy_time_unit'),
},
rules: 'selectRequired',
},
@@ -83,10 +101,10 @@ const modalSchema = [
fieldName: 'rentalType',
component: 'Select',
componentProps: {
options: getDictOptions('wy_zlfs')
options: getDictOptions('wy_zlfs'),
},
rules: 'selectRequired',
formItemClass: 'col-span-2'
formItemClass: 'col-span-2',
},
{
label: '租赁方案',
@@ -101,29 +119,34 @@ const modalSchema = [
formItemClass: 'col-span-2',
componentProps: {
api: async () => {
const res = await rentalPlanList({pageNum: 1, pageSize: 1000, state: 1});
const res = await rentalPlanList({
pageNum: 1,
pageSize: 1000,
state: 1,
});
planList.value = res.rows || [];
return planList.value.map(item => ({
return planList.value.map((item) => ({
label: item.planName,
value: item.id,
}));
},
onChange: async (value: string) => {
await getPlanProducts(value)
await getPlanProducts(value);
},
showSearch: true,
filterOption: (input: any, option: any) =>
option.label.toLowerCase().includes(input.toLowerCase()),
}
},
},
{
label: '方案详情',
fieldName: 'planInfo',
component: 'Input',
slots: {default: 'planInfo'},
slots: { default: 'planInfo' },
dependencies: {
// 仅当 租赁方式 为 2套餐 时显示
show: (formValues: any) => formValues.rentalType === '2' && formValues.planId != null,
show: (formValues: any) =>
formValues.rentalType === '2' && formValues.planId != null,
triggerFields: ['planId', 'rentalType'],
},
formItemClass: 'col-span-2',
@@ -141,36 +164,51 @@ const modalSchema = [
formItemClass: 'col-span-2',
componentProps: {
api: async () => {
const res = await plantsProductList({pageNum: 1, pageSize: 1000, inventory: 0});
const res = await plantsProductList({
pageNum: 1,
pageSize: 1000,
inventory: 0,
});
plantsList.value = res.rows || [];
return plantsList.value.map(item => ({
label: item.plantName + '-' + item.plantCode + '\xa0\xa0租金' + item.rent + '\xa0\xa0库存数量' + item.inventory,
return plantsList.value.map((item) => ({
label:
item.plantName +
'-' +
item.plantCode +
'\xa0\xa0租金' +
item.rent +
'\xa0\xa0库存数量' +
item.inventory,
value: item.id,
}));
},
onChange: async (value: string) => {
plantInfo.value = plantsList.value.find(item => item.id === value);
plantInfo.value = plantsList.value.find((item) => item.id === value);
if (plantInfo.value) {
formApi.updateSchema([{
componentProps: () => ({
min: 1,
max: plantInfo.value?.inventory,
precision: 0,
onChange: async () => {
const formValues = await formApi.getValues();
if (formValues.productNum && plantInfo.value?.rent) {
singleAmount.value = (plantInfo.value.rent * formValues.productNum).toFixed(2);
}
},
}),
fieldName: 'productNum',
}])
formApi.updateSchema([
{
componentProps: () => ({
min: 1,
max: plantInfo.value?.inventory,
precision: 0,
onChange: async () => {
const formValues = await formApi.getValues();
if (formValues.productNum && plantInfo.value?.rent) {
singleAmount.value = (
plantInfo.value.rent * formValues.productNum
).toFixed(2);
}
},
}),
fieldName: 'productNum',
},
]);
}
},
showSearch: true,
filterOption: (input: any, option: any) =>
option.label.toLowerCase().includes(input.toLowerCase()),
}
},
},
{
label: '租赁数量',
@@ -198,14 +236,17 @@ const modalSchema = [
label: '总金额(元)',
fieldName: 'totalAmount',
component: 'InputNumber',
slots: {default: 'totalAmount'},
slots: { default: 'totalAmount' },
dependencies: {
// 仅当 租赁方式 为 1单点 时显示
show: (formValues: any) => formValues.productNum && formValues.productId && formValues.rentalType === '1',
show: (formValues: any) =>
formValues.productNum &&
formValues.productId &&
formValues.rentalType === '1',
triggerFields: ['productNum', 'rentalType'],
disabled: true,
},
labelWidth: 110
labelWidth: 110,
},
{
label: '是否续租',
@@ -216,7 +257,7 @@ const modalSchema = [
optionType: 'button',
options: getDictOptions('wy_sf'),
},
rules: 'required'
rules: 'required',
},
{
label: '合同状态',
@@ -225,17 +266,18 @@ const modalSchema = [
componentProps: {
options: getDictOptions('wy_htzt'),
},
rules: 'selectRequired'
rules: 'selectRequired',
},
{
label: '合同编号',
fieldName: 'contractCode',
component: 'Input',
dependencies: {
show: (formValues: any) => formValues.contractStatus != null && formValues.contractStatus != 1,
show: (formValues: any) =>
formValues.contractStatus != null && formValues.contractStatus != 1,
triggerFields: ['contractStatus'],
},
rules: 'required'
rules: 'required',
},
{
label: '签署时间',
@@ -247,10 +289,11 @@ const modalSchema = [
valueFormat: 'YYYY-MM-DD',
},
dependencies: {
show: (formValues: any) => formValues.contractStatus != null && formValues.contractStatus != 1,
show: (formValues: any) =>
formValues.contractStatus != null && formValues.contractStatus != 1,
triggerFields: ['contractStatus'],
},
rules: 'required'
rules: 'required',
},
];
const [BasicForm, formApi] = useVbenForm({
@@ -262,15 +305,14 @@ const [BasicForm, formApi] = useVbenForm({
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
},
schema: modalSchema,
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const {onBeforeClose, markInitialized, resetInitialized} = useBeforeCloseDiff(
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
@@ -289,7 +331,7 @@ const [BasicModal, modalApi] = useVbenModal({
return null;
}
modalApi.modalLoading(true);
const {id} = modalApi.getData() as { id?: number | string };
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
const record = await rentalOrderInfo(id);
@@ -300,11 +342,26 @@ const [BasicModal, modalApi] = useVbenModal({
modalApi.modalLoading(false);
},
});
async function getUnitList() {
const queryParam = {
pageNum: 1,
pageSize: 1000,
};
const res = await resident_unitList(queryParam);
const data: { value: number; label: string }[] = [];
res.rows.forEach((r: any) => {
data.push({
value: r.id,
label: r.name,
});
});
return data;
}
async function handleConfirm() {
try {
modalApi.lock(true);
const {valid} = await formApi.validate();
const { valid } = await formApi.validate();
if (!valid) {
return;
}
@@ -312,18 +369,18 @@ async function handleConfirm() {
const data = cloneDeep(await formApi.getValues());
if (data.rentalType == 1) {
data.planId = undefined;
data.totalAmount = singleAmount.value
data.totalAmount = singleAmount.value;
} else {
data.productId = undefined;
data.productNum = undefined;
data.productList = planProducts.value
data.totalAmount = totalAmount.value
data.productList = planProducts.value;
data.totalAmount = totalAmount.value;
}
if (data.rentalTime) {
data.startTime = data.rentalTime[0];
data.endTime = data.rentalTime[1];
}
data.paymentStatus = 0
data.paymentStatus = 0;
await (isUpdate.value ? rentalOrderUpdate(data) : rentalOrderAdd(data));
resetInitialized();
emit('reload');
@@ -337,22 +394,22 @@ async function handleConfirm() {
//获取有库存的绿植
async function getPlanProducts(id: string) {
const res = await rentalPlanInfo(id)
planProducts.value = res.productList.map(item => ({
id:item.productId,
const res = await rentalPlanInfo(id);
planProducts.value = res.productList.map((item) => ({
id: item.productId,
plantName: item.product?.plantName,
rent: item.product?.rent,
inventory: item.productNum,
}))
totalAmount.value = planProducts.value?.reduce((sum, item: any) => sum + (item.rent * item.inventory), 0).toFixed(2)
}));
totalAmount.value = planProducts.value
?.reduce((sum, item: any) => sum + item.rent * item.inventory, 0)
.toFixed(2);
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
@@ -360,8 +417,13 @@ async function handleClosed() {
<BasicForm>
<template #planInfo="slotProps">
<div v-if="planProducts.length" style="width: 100%" v-bind="slotProps">
<Table :dataSource="planProducts" :columns="planInfoColumns" :pagination="false"
bordered size="small">
<Table
:dataSource="planProducts"
:columns="planInfoColumns"
:pagination="false"
bordered
size="small"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.field === 'id'">
{{ index + 1 }}
@@ -371,7 +433,7 @@ async function handleClosed() {
</template>
</template>
<template #footer>
<div style="text-align: right;margin-right: 20px">
<div style="text-align: right; margin-right: 20px">
<b>总金额</b>
{{ totalAmount }}
</div>
@@ -385,4 +447,3 @@ async function handleClosed() {
</BasicForm>
</BasicModal>
</template>

View File

@@ -0,0 +1,141 @@
<script setup lang="ts">
import type { PropType } from 'vue';
import { onMounted, ref } from 'vue';
import { SyncOutlined } from '@ant-design/icons-vue';
import { Empty, InputSearch, Skeleton, Tree } from 'ant-design-vue';
import {communityTree} from "#/api/property/community";
import type {CommunityVO} from "#/api/property/community/model";
defineOptions({ inheritAttrs: false });
withDefaults(defineProps<{ showSearch?: boolean }>(), { showSearch: true });
const emit = defineEmits<{
/**
* 点击刷新按钮的事件
*/
reload: [];
/**
* 点击节点的事件
*/
select: [];
}>();
const selectRoomId = defineModel('selectRoomId', {
type: Array as PropType<string[]>,
default: () => [],
});
const checkedRoomId = defineModel('checkedRoomId', {
type: Array as PropType<string[]>,
default: () => [],
});
const searchValue = defineModel('searchValue', {
type: String,
default: '',
});
const checkable = defineModel('checkable', {
type: Boolean,
default: false,
});
/** 房间数据源 */
type RoomTreeArray = CommunityVO[];
const roomTreeArray = ref<RoomTreeArray>([]);
/** 骨架屏加载 */
const showTreeSkeleton = ref<boolean>(true);
async function loadTree() {
showTreeSkeleton.value = true;
searchValue.value = '';
selectRoomId.value = [];
checkedRoomId.value = [];
roomTreeArray.value = await communityTree(4);
showTreeSkeleton.value = false;
}
async function handleReload() {
await loadTree();
emit('reload');
}
function selectStyle(id:string){
if(selectRoomId.value.includes(id)){
return {
backgroundColor: '#e6f4ff',
padding:'0 10px',
borderRadius:'4px',
fontWeight:'bold'
}
}
return {}
}
onMounted(loadTree);
</script>
<template>
<div :class="$attrs.class">
<Skeleton
:loading="showTreeSkeleton"
:paragraph="{ rows: 8 }"
active
class="p-[8px]"
>
<div
class="bg-background flex h-full flex-col overflow-y-auto rounded-lg"
>
<!-- 固定在顶部 必须加上bg-background背景色 否则会产生'穿透'效果 -->
<div
v-if="showSearch"
class="bg-background z-100 sticky left-0 top-0 p-[8px]"
>
<InputSearch
v-model:value="searchValue"
:placeholder="$t('pages.common.search')"
size="small"
>
<template #enterButton>
<a-button @click="handleReload">
<SyncOutlined class="text-primary" />
</a-button>
</template>
</InputSearch>
</div>
<div class="h-full overflow-x-hidden px-[8px]">
<Tree
:checkable="checkable"
:selectable="false"
v-bind="$attrs"
v-if="roomTreeArray.length > 0"
v-model:checked-keys="checkedRoomId"
:class="$attrs.class"
:field-names="{ title: 'label', key: 'id' }"
:tree-data="roomTreeArray"
:virtual="false"
default-expand-all
>
<template #title="{ label,id }">
<span v-if="label.indexOf(searchValue) > -1" :style="selectStyle(id)">
{{ label.substring(0, label.indexOf(searchValue)) }}
<span style="color: #f50">{{ searchValue }}</span>
{{
label.substring(
label.indexOf(searchValue) + searchValue.length,
)
}}
</span>
<span v-else :style="selectStyle(id)">{{ label}}</span>
</template>
</Tree>
<!-- 仅本人数据权限 可以考虑直接不显示 -->
<div v-else class="mt-5">
<Empty
:image="Empty.PRESENTED_IMAGE_SIMPLE"
description="无部门数据"
/>
</div>
</div>
</div>
</Skeleton>
</div>
</template>

View File

@@ -47,12 +47,12 @@ export const columns: VxeGridProps['columns'] = [
return row.id
}
},
width: 100
width: 180
},
{
title: '单位名称',
field: 'name',
width: 100
minWidth: 180
},
{
title: '单位类型',
@@ -72,7 +72,7 @@ export const columns: VxeGridProps['columns'] = [
{
title: '联系电话',
field: 'phone',
width: 100
width: 120
},
// {
// title: '入驻位置',
@@ -82,8 +82,18 @@ export const columns: VxeGridProps['columns'] = [
{
title: '入驻时间',
field: 'time',
width: 180,
},
{
title: '入驻房间数',
field: 'roomNumber',
width: 100,
},
{
title: '入驻面积(㎡)',
field: 'area',
width: 180,
},
{
title: '状态',
field: 'state',
@@ -98,19 +108,22 @@ export const columns: VxeGridProps['columns'] = [
{
title: '备注',
field: 'remark',
width: 100,
width: 180,
slots: { default: ({ row }) => {
return row.remark || '-'
}, },
},
{
title: '创建时间',
field: 'createTime',
width: 100,
width: 180,
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
minWidth: 180,
width: 180,
},
]
@@ -155,13 +168,13 @@ export const modalSchema: FormSchemaGetter = () => [
z.number().int().min(1000000000).max(19999999999, { message: '手机号格式错误' })
]).transform(val => val.toString()),
},
{
label: '入驻位置',
fieldName: 'locations',
component: 'TreeSelect',
rules: 'selectRequired',
formItemClass: 'col-span-2'
},
// {
// label: '入驻位置',
// fieldName: 'locations',
// component: 'TreeSelect',
// rules: 'selectRequired',
// formItemClass: 'col-span-2'
// },
{
label: '入驻时间',
fieldName: 'time',
@@ -173,11 +186,6 @@ export const modalSchema: FormSchemaGetter = () => [
},
rules: 'required',
},
{
label: '',
fieldName: 'Placeholder',
component: ''
},
{
label: '授权期限',
fieldName: 'authTime',
@@ -188,6 +196,7 @@ export const modalSchema: FormSchemaGetter = () => [
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
formItemClass: 'col-span-1'
},
{
label: '通行权限组',

View File

@@ -12,6 +12,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
import {resident_unitInfo, authGroupList} from '#/api/property/resident/unit';
import {renderDict} from "#/utils/render";
import RoomTree from "./components/room-tree.vue";
dayjs.extend(duration);
dayjs.extend(relativeTime);
@@ -39,55 +40,68 @@ async function handleOpenChange(open: boolean) {
authGroupName.value = authGroup.find(item => item.id === authGroupId)?.name;
// 赋值
unitDetail.value = response;
if(response.location){
selectRoomId.value=response.location.split(',');
}
modalApi.modalLoading(false);
}
const selectRoomId = ref<string[]>([]);
</script>
<template>
<BasicModal :footer="false" :fullscreen-button="false" title="入驻单位信息" class="w-[70%]">
<Descriptions v-if="unitDetail" size="small" :column="2" bordered :labelStyle="{width:'120px'}">
<DescriptionsItem label="单位编号">
{{ unitDetail.id }}
</DescriptionsItem>
<DescriptionsItem label="单位名称">
{{ unitDetail.name }}
</DescriptionsItem>
<DescriptionsItem label="单位类型" v-if="unitDetail.type!=null">
<component
:is="renderDict(unitDetail.type,'wy_qylx')"
<div class="flex gap-[8px]">
<div class="w-[260px]">
<RoomTree
class="max-h-[calc(100vh-38vh)]"
v-model:select-room-id="selectRoomId"
/>
</DescriptionsItem>
<DescriptionsItem label="联系人">
{{ unitDetail.contactPerson +'-'+unitDetail.phone}}
</DescriptionsItem>
<DescriptionsItem label="入驻位置" :span="2">
{{ unitDetail.locationDetail }}
</DescriptionsItem>
<DescriptionsItem label="入驻面积(㎡)" :span="2">
{{ unitDetail.area }}
</DescriptionsItem>
<DescriptionsItem label="入驻时间">
{{ unitDetail.time }}
</DescriptionsItem>
<DescriptionsItem label="员工数量">
{{ unitDetail.number }}
</DescriptionsItem>
<DescriptionsItem label="创建时间">
{{ unitDetail.createTime ?? '-' }}
</DescriptionsItem>
<DescriptionsItem label="状态" v-if="unitDetail.state!=null">
<component
:is="renderDict(unitDetail.state,'wy_state')"
/>
</DescriptionsItem>
<DescriptionsItem label="通行权限组">
{{ authGroupName }}
</DescriptionsItem>
<DescriptionsItem label="备注">
{{ unitDetail.remark ?? '-' }}
</DescriptionsItem>
</Descriptions>
</div>
<Descriptions class="flex-1" v-if="unitDetail" size="middle" :column="1" bordered :labelStyle="{width:'120px'}">
<DescriptionsItem label="单位编号">
{{ unitDetail.id }}
</DescriptionsItem>
<DescriptionsItem label="单位名称">
{{ unitDetail.name }}
</DescriptionsItem>
<DescriptionsItem label="单位类型" v-if="unitDetail.type!=null">
<component
:is="renderDict(unitDetail.type,'wy_qylx')"
/>
</DescriptionsItem>
<DescriptionsItem label="联系人">
{{ unitDetail.contactPerson +'-'+unitDetail.phone}}
</DescriptionsItem>
<DescriptionsItem label="入驻房间数" >
{{ unitDetail.roomNumber }}
</DescriptionsItem>
<DescriptionsItem label="入驻面积">
<span v-if="unitDetail.area">{{ unitDetail.area }}</span>
</DescriptionsItem>
<DescriptionsItem label="入驻时间">
{{ unitDetail.time }}
</DescriptionsItem>
<DescriptionsItem label="员工数量">
{{ unitDetail.number }}
</DescriptionsItem>
<DescriptionsItem label="创建时间">
{{ unitDetail.createTime ?? '-' }}
</DescriptionsItem>
<DescriptionsItem label="状态" v-if="unitDetail.state!=null">
<component
:is="renderDict(unitDetail.state,'wy_state')"
/>
</DescriptionsItem>
<DescriptionsItem label="通行权限组">
{{ authGroupName }}
</DescriptionsItem>
<DescriptionsItem label="备注">
{{ unitDetail.remark ?? '-' }}
</DescriptionsItem>
</Descriptions>
</div>
</BasicModal>
</template>

View File

@@ -1,20 +1,21 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import {computed, ref} from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep, handleNode } from '@vben/utils';
import {useVbenModal} from '@vben/common-ui';
import {$t} from '@vben/locales';
import {cloneDeep} from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import {useVbenForm} from '#/adapter/form';
import {
resident_unitAdd,
resident_unitInfo,
resident_unitUpdate,
} from '#/api/property/resident/unit';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import {defaultFormValueGetter, useBeforeCloseDiff} from '#/utils/popup';
import { modalSchema } from './data';
import { communityTree } from '#/api/property/community';
import {modalSchema} from './data';
import RoomTree from "./components/room-tree.vue";
import {message} from "ant-design-vue";
const emit = defineEmits<{ reload: [] }>();
@@ -26,7 +27,7 @@ const title = computed(() => {
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-1',
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 100,
// 通用配置项 会影响到所有表单项
@@ -39,7 +40,7 @@ const [BasicForm, formApi] = useVbenForm({
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
const {onBeforeClose, markInitialized, resetInitialized} = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
@@ -59,16 +60,16 @@ const [BasicModal, modalApi] = useVbenModal({
}
modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string };
const {id} = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
await initLocationOptions();
if (isUpdate.value && id) {
const record = await resident_unitInfo(id);
let roomIds=record.location.split(',')
await formApi.setValues({...record,
authTime:[record.authBegDate,record.authEndDate],
locations:roomIds
let roomIds = record.location.split(',')
await formApi.setValues({
...record,
authTime: [record.authBegDate, record.authEndDate],
});
checkedRoomId.value = roomIds
}
await markInitialized();
@@ -79,7 +80,11 @@ const [BasicModal, modalApi] = useVbenModal({
async function handleConfirm() {
try {
modalApi.lock(true);
const { valid } = await formApi.validate();
const {valid} = await formApi.validate();
if (!checkedRoomId.value.length) {
message.error('请选择入驻位置');
return;
}
if (!valid) {
return;
}
@@ -88,7 +93,7 @@ async function handleConfirm() {
data.authBegDate = data.authTime[0];
data.authEndDate = data.authTime[1];
data.location=data.locations.join(',')
data.location = checkedRoomId.value.join(',')
await (isUpdate.value ? resident_unitUpdate(data) : resident_unitAdd(data));
resetInitialized();
emit('reload');
@@ -99,51 +104,26 @@ async function handleConfirm() {
modalApi.lock(false);
}
}
/**
* 入驻位置数据
*/
async function initLocationOptions() {
const locationList = await communityTree(4);
const splitStr = '/';
handleNode(locationList, 'label', splitStr, function (node: any) {
if (node.level != 4) {
node.disabled = true;
}
});
formApi.updateSchema([
{
componentProps: () => ({
class: 'w-full',
fieldNames: {
key: 'id',
label: 'label',
value: 'code',
children: 'children',
},
placeholder: '请选择入驻位置',
showSearch: true,
treeData: locationList,
treeDefaultExpandAll: true,
treeLine: { showLeafIcon: false },
// 筛选的字段
treeNodeFilterProp: 'label',
// 选中后显示在输入框的值
treeNodeLabelProp: 'fullName',
multiple:true
}),
fieldName: 'locations',
},
]);
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
const checkedRoomId = ref<string[]>([]);
</script>
<template>
<BasicModal :title="title">
<BasicForm> </BasicForm>
<div class="flex gap-[8px]">
<div class="w-[260px]">
<RoomTree
:checkable="true"
class="max-h-[calc(100vh-45vh)]"
v-model:checked-room-id="checkedRoomId"
/>
</div>
<BasicForm class="flex-1"></BasicForm>
</div>
</BasicModal>
</template>

View File

@@ -83,6 +83,9 @@ async function handleConfirm() {
data.scheduledEndtime = data.timeSpan[1]?.format("HH:mm");
}
data.price=totalAmount.value
if(conferenceSettingDetail.value?.expenseType == '2'){
data.price=Number(totalAmount.value)+Number(conferenceSettingDetail.value?.basePrice||0)
}
if(data.attach=='0'){
data.meetAttachOrderBoList=addServiceList.value.filter(item=>item.quantity>0);
}

View File

@@ -20,9 +20,9 @@ const getDaysInMonth = (date: any) => {
const emit = defineEmits(['getPowerDate']);
const getMeterRecordTrend = async (selectedDate: any) => {
const res = await meterRecordTrend({
day: dayjs().format('YYYY-MM-DD'),
month: selectedDate.value,
year: dayjs().format('YYYY'),
day: null,
year: null,
meterType: 1,
meterId: null,
floorId: null,

View File

@@ -718,17 +718,16 @@ onBeforeUnmount(() => {
.left {
display: flex;
width: 18.3125rem;
font-size: 1.875rem;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
.left-first {
padding-left: 2.3125rem;
font-size: 1.875rem;
width: 10.5rem;
color: #ffffff;
}
.left-second {
width: 6.5rem;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
font-size: 1.25rem;
color: #ffffff;
}
}
@@ -759,7 +758,7 @@ onBeforeUnmount(() => {
}
}
.header {
margin-top: 1.125rem;
margin-top: 2.125rem;
margin-left: 4.625rem;
margin-right: 4.25rem;
height: 6rem;

View File

@@ -13,7 +13,7 @@ export const querySchema: FormSchemaGetter = () => [
fieldName: 'rosterType',
component: 'Select',
componentProps: {
options: getDictOptions('roster_type'),
options: getDictOptions(DictEnum.ROSTER_TYPE, true),
},
},
{
@@ -25,7 +25,7 @@ export const querySchema: FormSchemaGetter = () => [
component: 'Select',
componentProps: {
getPopupContainer,
options: getDictOptions(DictEnum.SYS_USER_SEX),
options: getDictOptions(DictEnum.SYS_USER_SEX, true),
},
fieldName: 'sex',
label: '性别',
@@ -41,7 +41,7 @@ export const columns: VxeGridProps['columns'] = [
field: 'libCode',
},*/
{
title: '人名称',
title: '人名称',
field: 'imgName',
},
{
@@ -49,7 +49,7 @@ export const columns: VxeGridProps['columns'] = [
field: 'sex',
slots: {
default: ({ row }) => {
return renderDict(row.sex, 'sys_user_sex');
return renderDict(row.sex, DictEnum.SYS_USER_SEX);
},
},
},
@@ -66,7 +66,7 @@ export const columns: VxeGridProps['columns'] = [
field: 'certificateType',
slots: {
default: ({ row }) => {
return renderDict(row.certificateType, 'sys_certificate_type');
return renderDict(row.certificateType, DictEnum.SYS_CERTIFICATE_TYPE);
},
},
},
@@ -83,7 +83,7 @@ export const columns: VxeGridProps['columns'] = [
field: 'rosterType',
slots: {
default: ({ row }) => {
return renderDict(row.rosterType, 'roster_type');
return renderDict(row.rosterType, DictEnum.ROSTER_TYPE);
},
},
width: 100,
@@ -97,11 +97,6 @@ export const columns: VxeGridProps['columns'] = [
},
];
const test = getDictOptions('roster_type');
test.forEach((item) => {
console.log('item', item);
});
export const modalSchema: FormSchemaGetter = () => [
{
label: '主键',
@@ -118,11 +113,12 @@ export const modalSchema: FormSchemaGetter = () => [
component: 'Select',
rules: 'required',
componentProps: {
options: getDictOptions('roster_type'),
getPopupContainer,
options: getDictOptions(DictEnum.ROSTER_TYPE, true),
},
},
{
label: '人名称',
label: '人名称',
fieldName: 'imgName',
component: 'Input',
rules: 'required',
@@ -133,7 +129,7 @@ export const modalSchema: FormSchemaGetter = () => [
component: 'Select',
componentProps: {
getPopupContainer,
options: getDictOptions(DictEnum.SYS_USER_SEX),
options: getDictOptions(DictEnum.SYS_USER_SEX, true),
},
},
{
@@ -152,7 +148,7 @@ export const modalSchema: FormSchemaGetter = () => [
component: 'Select',
componentProps: {
getPopupContainer,
options: getDictOptions(DictEnum.SYS_CERTIFICATE_TYPE),
options: getDictOptions(DictEnum.SYS_CERTIFICATE_TYPE, true),
},
},
{

View File

@@ -112,7 +112,7 @@ function handleMultiDelete() {
</script>
<template>
<Page :auto-content-height="true">
<BasicTable class="flex-1 overflow-hidden" table-title="视频预警处理">
<BasicTable class="flex-1 overflow-hidden">
<template #toolbar-tools>
<Space>
<a-button

View File

@@ -0,0 +1,246 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { h } from 'vue';
import { getPopupContainer } from '@vben/utils';
import { getDictOptions } from '#/utils/dict';
import { DictEnum } from '@vben/constants';
export const querySchema: FormSchemaGetter = () => [
{
component: 'Input',
fieldName: 'deviceName',
label: '设备名称',
},
{
component: 'DatePicker',
fieldName: 'reportTime',
label: '预警时间',
componentProps: {
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
component: 'Input',
fieldName: 'alarmType',
label: '预警类型',
},
{
component: 'Select',
componentProps: {
getPopupContainer,
options: getDictOptions(DictEnum.alarm_level, true),
},
fieldName: 'level',
label: '预警级别',
},
];
export const columns: VxeGridProps['columns'] = [
{ type: 'checkbox', width: 60 },
{
title: '预警编号',
field: 'id',
},
{
title: '预警时间',
field: 'reportTime',
},
{
title: '设备IP',
field: 'deviceIp',
},
{
title: '设备名称',
field: 'deviceName',
},
{
title: '预警级别',
field: 'level',
slots: {
default: ({ row }: any) => {
const levelColors: Record<string, string> = {
1: 'blue',
2: 'orange',
3: 'red',
};
return h(
'span',
{
style: {
color: levelColors[row.level] || '#666',
fontWeight: 'bold',
},
},
row.levelName,
);
},
},
},
{
title: '预警类型',
field: 'alarmTypeName',
slots: {
default: ({ row }: any) => {
return h('span', row.bigTypeName + '-' + row.smallTypeName);
},
},
},
{
title: '处理状态',
field: 'stateName',
},
{
title: '创建时间',
field: 'createTime',
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 380,
},
];
export const modalSchema: FormSchemaGetter = () => [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '预警编号',
fieldName: 'id',
component: 'Input',
rules: 'required',
disabled: true,
},
{
label: '预警时间',
fieldName: 'reportTime',
component: 'DatePicker',
componentProps: {
format: 'YYYY.MM.DD HH:mm',
valueFormat: 'YYYY.MM.DD HH:mm',
showTime: true,
},
disabled: true,
rules: 'required',
},
{
label: '预警类型',
fieldName: 'alarmType',
component: 'Input',
rules: 'required',
disabled: true,
},
{
label: '设备IP',
fieldName: 'deviceIp',
component: 'Input',
disabled: true,
rules: 'required',
},
{
label: '设备名称',
fieldName: 'deviceName',
disabled: true,
component: 'Input',
rules: 'required',
},
{
label: '预警级别',
fieldName: 'level',
component: 'Select',
disabled: true,
componentProps: {
getPopupContainer,
options: getDictOptions(DictEnum.alarm_level, true),
},
rules: 'selectRequired',
},
{
label: '创建时间',
fieldName: 'createTime',
component: 'DatePicker',
disabled: true,
componentProps: {
format: 'YYYY.MM.DD HH:mm',
valueFormat: 'YYYY.MM.DD HH:mm',
showTime: true,
},
},
{
label: '处理状态',
component: 'Select',
disabled: true,
componentProps: {
getPopupContainer,
options: getDictOptions(DictEnum.alarm_state, true),
},
fieldName: 'state',
rules: 'selectRequired',
},
{
label: '预警描述',
disabled: true,
fieldName: 'description',
component: 'Textarea',
formItemClass: 'col-span-2',
componentProps: {
rows: 3,
},
},
// 插入分割线
{
component: 'Divider',
fieldName: '_divider',
formItemClass: 'col-span-2',
hideLabel: true,
renderComponentContent: () => {
return {
default: () => h('div', '处理'),
};
},
},
{
label: '处理人',
fieldName: 'solveId',
component: 'Select',
},
{
label: '联系电话',
fieldName: 'phonenumber',
component: 'Input',
},
{
label: '邮箱',
fieldName: 'email',
component: 'Input',
},
{
label: '所在部门',
fieldName: 'deptName',
component: 'Input',
disabled: true,
},
{
label: '备注',
fieldName: 'remark',
component: 'Textarea',
formItemClass: 'col-span-2',
componentProps: {
rows: 3,
},
},
];

View File

@@ -0,0 +1,186 @@
<script setup lang="ts">
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import { columns, querySchema } from './data';
import warningModal from './warning-modal.vue';
import warningDetail from './warning-detail.vue';
import { alarmEventsList, alarmEventsRemove } from '#/api/sis/alarmEvents';
import type { AlarmEventsForm } from '#/api/sis/alarmEvents/model';
/* 搜索栏配置 */
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 100,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
};
/* table栏配置 */
const gridOptions: VxeGridProps = {
checkboxConfig: {
// 高亮
highlight: true,
// 翻页时保留选中状态
reserve: true,
// 点击行选中
// trigger: 'row',
},
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// columns: columns(),
columns,
height: 'auto',
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
return await alarmEventsList({
pageNum: page.currentPage,
pageSize: page.pageSize,
state: 10,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
id: 'video-warning-processing-index',
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
// 监听数据变化,强制重新渲染表格
const [WarningModal, modalApi] = useVbenModal({
connectedComponent: warningModal,
});
const [WarningDetail, detailApi] = useVbenModal({
connectedComponent: warningDetail,
});
// 查看详情
async function handleView(row: any) {
detailApi.setData({ id: row.id, data: row });
detailApi.open();
}
// 编辑
async function handleEdit(row: any) {
modalApi.setData({ id: row.id, data: row });
modalApi.open();
}
// 删除
async function handleDelete(row: any) {
const index = row.id;
if (index !== -1) {
await tableApi.query();
}
}
// 批量删除
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: Required<AlarmEventsForm>) => row.id);
Modal.confirm({
title: '提示',
okType: 'danger',
content: `确认删除选中的${ids.length}条记录吗?`,
onOk: async () => {
await alarmEventsRemove(ids);
await tableApi.query();
},
});
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable class="flex-1 overflow-hidden">
<template #toolbar-tools>
<Space>
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['video:warning:remove']"
@click="handleMultiDelete"
>
{{ $t('pages.common.delete') }}
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<!-- <ghost-button
v-access:code="['video:warning:level']"
@click.stop="handleLevelSetting(row)"
>
级别设置
</ghost-button> -->
<ghost-button
v-access:code="['video:warning:view']"
@click.stop="handleView(row)"
>
{{ $t('pages.common.info') }}
</ghost-button>
<ghost-button
v-access:code="['video:warning:edit']"
@click.stop="handleEdit(row)"
>
指派
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认删除?"
@confirm="handleDelete(row)"
>
<ghost-button
danger
v-access:code="['video:warning:remove']"
@click.stop=""
>
{{ $t('pages.common.delete') }}
</ghost-button>
</Popconfirm>
</Space>
</template>
</BasicTable>
<WarningModal @reload="tableApi.query()" />
<WarningDetail />
</Page>
</template>
<style scoped lang="scss">
.ant-table-wrapper {
.ant-table-thead > tr > th {
background-color: #fafafa;
font-weight: 600;
}
}
.ant-pagination {
.ant-pagination-item-active {
background-color: #1890ff;
border-color: #1890ff;
}
}
</style>

View File

@@ -0,0 +1,148 @@
<script setup lang="ts">
import { ref, shallowRef } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Descriptions, DescriptionsItem, Image, Tag } from 'ant-design-vue';
import { queryAlarmEventAttachmentsList } from '#/api/sis/alarmEventAttachments';
import type { AlarmEventAttachmentsVO } from '#/api/sis/alarmEventAttachments/model';
import { fallImg } from '../../common';
import { alarmEventProcessList } from '#/api/sis/alarmEventProcess';
import type { AlarmEventProcessVO } from '#/api/sis/alarmEventProcess/model';
const [BasicModal, modalApi] = useVbenModal({
onOpenChange: handleOpenChange,
onClosed() {
modalApi.close();
},
});
const warningDetail = shallowRef<any>(null);
const currFiles = ref<AlarmEventAttachmentsVO[]>([]);
async function handleOpenChange(open: boolean) {
if (!open) {
return null;
}
modalApi.modalLoading(true);
const { id, data } = modalApi.getData() as {
id: number | string;
data: any[];
};
// 从传递的数据中查找对应的记录
if (data) {
warningDetail.value = data;
}
// 加载事件附件信息
currFiles.value = await queryAlarmEventAttachmentsList(id);
// 加载处理流程
loadProcessList();
modalApi.modalLoading(false);
}
const process = ref<AlarmEventProcessVO[]>([]);
function loadProcessList() {
const data = modalApi.getData();
const params = {
pageNum: 1,
pageSize: 50,
alarmId: data.id,
};
alarmEventProcessList(params).then((res) => {
const { rows = [] } = res;
process.value = rows;
});
}
</script>
<template>
<BasicModal
:footer="false"
:fullscreen-button="false"
title="预警详情"
class="w-[60%]"
>
<Descriptions
v-if="warningDetail"
size="small"
:column="2"
bordered
:labelStyle="{ width: '120px' }"
style="margin-bottom: 30px"
>
<DescriptionsItem label="预警编码">
{{ warningDetail.id }}
</DescriptionsItem>
<DescriptionsItem label="预警类型">
{{ warningDetail.bigTypeName + ' - ' + warningDetail.smallTypeName }}
</DescriptionsItem>
<DescriptionsItem label="设备IP"
>{{ warningDetail.deviceIp }}
</DescriptionsItem>
<DescriptionsItem label="设备名称"
>{{ warningDetail.deviceName }}
</DescriptionsItem>
<DescriptionsItem label="预警位置" :span="2">
{{ warningDetail.deviceName }}
</DescriptionsItem>
<DescriptionsItem label="预警级别">
<Tag
:color="
warningDetail.level === '特大'
? 'red'
: warningDetail.level === '重要'
? 'orange'
: 'blue'
"
>
{{ warningDetail.levelName }}
</Tag>
</DescriptionsItem>
<DescriptionsItem label="预警时间">
{{ warningDetail.reportTime }}
</DescriptionsItem>
<DescriptionsItem label="预警描述" :span="2">
{{ warningDetail.description }}
</DescriptionsItem>
<DescriptionsItem label="处理状态" :span="2">
<Tag color="processing">
{{ warningDetail.stateName }}
</Tag>
</DescriptionsItem>
<!-- <DescriptionsItem label="处理情况" :span="2">
{{ warningDetail.processingDetails || '-' }}
</DescriptionsItem>
<DescriptionsItem label="处理时间" :span="2">
{{ warningDetail.processingTime || '-' }}
</DescriptionsItem>-->
<DescriptionsItem :span="2" label="相关图片">
<div class="file-box">
<div class="img-box" v-for="item in currFiles">
<Image
style="width: 120px; height: 120px"
:src="item.imagePath"
:fallback="fallImg"
/>
</div>
</div>
</DescriptionsItem>
<DescriptionsItem :span="2" label="报警视频"></DescriptionsItem>
</Descriptions>
</BasicModal>
</template>
<style scoped lang="scss">
.file-box {
.img-box {
float: left;
margin-left: 5px;
}
}
</style>

View File

@@ -0,0 +1,159 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
import { queryCurrentSchedu } from '#/api/property/attendanceManagement/arrangement';
import type { AttendanceUserGroup } from '#/api/property/attendanceManagement/arrangement/model';
import { taskAssignment } from '#/api/sis/alarmEvents';
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
formItemClass: 'col-span-1',
labelWidth: 110,
componentProps: {
class: 'w-full',
},
},
schema: modalSchema(),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
let currentSelectData: any = null;
const [BasicModal, modalApi] = useVbenModal({
class: 'w-[70%]',
fullscreenButton: false,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
// 更新表单属性
const userList: AttendanceUserGroup[] = await queryCurrentSchedu();
const arr = userList.map((item) => {
const { remoteUserVo, deptName } = item;
return {
label: remoteUserVo.nickName,
value: remoteUserVo.userId,
deptName: deptName,
phonenumber: remoteUserVo.phonenumber,
email: remoteUserVo.email,
userName: remoteUserVo.userName,
nickName: remoteUserVo.nickName,
};
});
formApi.updateSchema([
{
componentProps: {
options: arr,
onChange: async (_value: any, data: any) => {
const {
deptName = '',
email = '',
phonenumber = '',
nickName = '',
} = data;
const formData = cloneDeep(await formApi.getValues());
formData.phonenumber = phonenumber;
formData.email = email;
formData.deptName = deptName;
currentSelectData = data;
formApi.setValues(formData);
},
},
fieldName: 'solveId',
},
]);
// 从传递的数据中查找对应的记录
const { data } = modalApi.getData() as {
id: number | string;
data: any;
};
if (data) {
// 处理预警上报类型
data.alarmType = `${data.bigTypeName} - ${data.smallTypeName}`;
await formApi.setValues(data);
}
}
await markInitialized();
modalApi.modalLoading(false);
},
});
async function handleConfirm() {
try {
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
// 表单数据
const formData = cloneDeep(await formApi.getValues());
// 更新数据
const params = {
alarmId: formData.id,
solveId: currentSelectData.value,
solveName: `${currentSelectData.userName}(${currentSelectData.nickName})`,
solvePhone: currentSelectData.phonenumber,
solveEmail: currentSelectData.email,
remark: formData.remark,
};
await taskAssignment(params);
resetInitialized();
emit('reload');
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.lock(false);
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicModal title="处理">
<BasicForm></BasicForm>
</BasicModal>
</template>
<style scoped>
/* 使用 :deep() 穿透 scoped 样式,影响子组件 */
:deep(.ant-input[disabled]),
:deep(.ant-input-number-disabled .ant-input-number-input),
:deep(.ant-select-disabled .ant-select-selection-item),
:deep(.ant-picker-disabled .ant-picker-input > input) {
/* 设置一个更深的颜色 */
color: rgb(0 0 0 / 65%) !important;
/* 有些浏览器需要这个来覆盖默认颜色 */
-webkit-text-fill-color: rgb(0 0 0 / 65%) !important;
}
</style>

View File

@@ -1,7 +1,6 @@
<script setup lang="ts">
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
useVbenVxeGrid,
@@ -112,7 +111,7 @@ function handleMultiDelete() {
</script>
<template>
<Page :auto-content-height="true">
<BasicTable class="flex-1 overflow-hidden" table-title="视频预警处理">
<BasicTable class="flex-1 overflow-hidden">
<template #toolbar-tools>
<Space>
<a-button
@@ -141,12 +140,12 @@ function handleMultiDelete() {
{{ $t('pages.common.info') }}
</ghost-button>
<ghost-button
<!-- <ghost-button
v-access:code="['video:warning:edit']"
@click.stop="handleEdit(row)"
>
处理
</ghost-button>
</ghost-button>-->
<Popconfirm
:get-popup-container="getVxePopupContainer"

View File

@@ -112,7 +112,7 @@ function handleMultiDelete() {
</script>
<template>
<Page :auto-content-height="true">
<BasicTable class="flex-1 overflow-hidden" table-title="视频预警处理">
<BasicTable class="flex-1 overflow-hidden">
<template #toolbar-tools>
<Space>
<a-button
@@ -145,7 +145,7 @@ function handleMultiDelete() {
v-access:code="['video:warning:edit']"
@click.stop="handleEdit(row)"
>
处理
处理
</ghost-button>
<Popconfirm

View File

@@ -11,6 +11,7 @@ export const DictEnum = {
SYS_USER_SEX: 'sys_user_sex', // 性别
SYS_YES_NO: 'sys_yes_no', // 是否
SYS_CERTIFICATE_TYPE: 'sys_certificate_type', // 证件类型
ROSTER_TYPE:"roster_type",
WF_BUSINESS_STATUS: 'wf_business_status', // 业务状态
WF_FORM_TYPE: 'wf_form_type', // 表单类型
WF_TASK_STATUS: 'wf_task_status', // 任务状态