Merge branch 'master' of http://47.109.37.87:3000/by2025/admin-vben5
Some checks are pending
Gitea Actions Demo / Explore-Gitea-Actions (push) Waiting to run

This commit is contained in:
FLL
2025-07-18 19:48:20 +08:00
34 changed files with 3541 additions and 121 deletions

View File

@@ -0,0 +1,91 @@
<script setup lang="ts">
import { computed, 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 { carChargeAdd, carChargeInfo, carChargeUpdate } from '#/api/property/carCharge';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { addModalSchema } from './data';
const emit = defineEmits<{ reload: [] }>();
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 140,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: addModalSchema(),
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 };
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());
await carChargeAdd(data);
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 />
</BasicModal>
</template>

View File

@@ -0,0 +1,101 @@
<script setup lang="ts">
import { computed, 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 { carChargeAdd, carChargeInfo, carChargeUpdate } from '#/api/property/carCharge';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const title = computed(() => {
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 80,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: modalSchema(),
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;
if (isUpdate.value && id) {
const record = await carChargeInfo(id);
await formApi.setValues(record);
}
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());
await (isUpdate.value ? carChargeUpdate(data) : carChargeAdd(data));
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="title">
<BasicForm />
</BasicModal>
</template>

View File

@@ -0,0 +1,89 @@
<script setup lang="ts">
import { computed, 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 { carChargeAdd, carChargeInfo, carChargeUpdate } from '#/api/property/carCharge';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { payModalSchema } from './data';
const emit = defineEmits<{ reload: [] }>();
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
formItemClass: 'col-span-1',
// 默认label宽度 px
labelWidth: 1400,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: payModalSchema(),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
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 };
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());
await carChargeAdd(data);
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 />
</BasicModal>
</template>

View File

@@ -0,0 +1,350 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getDictOptions } from '#/utils/dict';
import { renderDict } from '#/utils/render';
export const querySchema: FormSchemaGetter = () => [
{
component: 'Input',
fieldName: 'carNumber',
label: '车牌号',
},
{
component: 'Input',
fieldName: 'personId',
label: '业主',
},
{
component: 'Select',
componentProps: {
// 可选从DictEnum中获取 DictEnum.WY_CSZT 便于维护
options: getDictOptions('wy_cszt'),
},
fieldName: 'state',
label: '状态',
},
];
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// export const columns: () => VxeGridProps['columns'] = () => [
export const columns: VxeGridProps['columns'] = [
{ type: 'checkbox', width: 60 },
{
title: '序号',
field: 'id',
slots: {
default: ({ rowIndex }) => {
return (rowIndex + 1).toString();
},
},
},
{
title: '车牌号',
field: 'carNumber',
},
{
title: '车位',
field: 'location',
},
{
title: '业主',
field: 'personId',
},
{
title: '状态',
field: 'state',
slots: {
default: ({ row }) => {
// 可选从DictEnum中获取 DictEnum.WY_CSZT 便于维护
return renderDict(row.state, 'wy_cszt');
},
},
},
{
title: '收费项目',
field: 'costItemsId',
},
{
title: '计费开始时间',
field: 'starTime',
},
{
title: '计费结束时间',
field: 'endTime',
},
{
title: '说明',
field: 'remark',
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
},
];
//详情
export const modalSchema: FormSchemaGetter = () => [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '车牌号',
fieldName: 'carNumber',
component: 'Input',
rules: 'required',
},
{
label: '业主',
fieldName: 'personId',
component: 'Input',
},
{
label: '楼层',
fieldName: 'floorId',
component: 'Input',
rules: 'required',
},
{
label: '车位',
fieldName: 'location',
component: 'Input',
},
{
label: '状态',
fieldName: 'state',
component: 'Select',
componentProps: {
// 可选从DictEnum中获取 DictEnum.WY_CSZT 便于维护
options: getDictOptions('wy_cszt'),
},
},
{
label: '收费项目',
fieldName: 'costItemsId',
component: 'Input',
},
{
label: '计费开始时间',
fieldName: 'starTime',
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
label: '计费结束时间',
fieldName: 'endTime',
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
label: '说明',
fieldName: 'remark',
component: 'Input',
},
{
label: '搜索值',
fieldName: 'searchValue',
component: 'Input',
},
];
//创建
export const addModalSchema: FormSchemaGetter = () => [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '车牌号',
fieldName: 'carNumber',
component: 'Input',
rules: 'required',
},
// {
// label: '费用类型',//一个费用下有对各费用项目
// fieldName: 'personId',
// component: 'Input',
// },
{
label: '收费项目',//一个费用收费项目对应一个费用类型
fieldName: 'costItemsId',
component: 'Input',
rules: 'required',
},
{
label: '计费开始时间',
fieldName: 'starTime',
component: 'DatePicker',
rules: 'required',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
label: '计费结束时间',
fieldName: 'endTime',
component: 'DatePicker',
rules:'required',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
label: '说明',
fieldName: 'remark',
component: 'Input',
},
];
//缴费
export const payModalSchema: FormSchemaGetter = () => [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '车牌号',
fieldName: 'carNumber',
component: 'Input',
rules: 'required',
},
// {
// label: '费用类型',//一个费用下有对各费用项目
// fieldName: 'personId',
// component: 'Input',
// },
{
label: '收费项目',//一个费用收费项目对应一个费用类型
fieldName: 'costItemsId',
component: 'Input',
rules: 'required',
},
{
label: '计费开始时间',
fieldName: 'starTime',
component: 'DatePicker',
rules: 'required',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
label: '计费结束时间',
fieldName: 'endTime',
component: 'DatePicker',
rules:'required',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
label: '说明',
fieldName: 'remark',
component: 'Input',
},
{
label: '支付方式',
fieldName: 'carNumber',
component: 'Input',
rules: 'required',
},
{
label: '缴费周期',
fieldName: 'personId',
component: 'Input',
},
{
label: '实收金额',
fieldName: 'costItemsId',
component: 'Input',
},
{
label: '自定义周期',
fieldName: 'remark',
component: 'Input',
},
];
export const detailColumns: VxeGridProps['columns'] = [
{
title: '序号',
field: 'id',
slots: {
default: ({ rowIndex }) => {
return (rowIndex + 1).toString();
},
},
},
{
title: '费用项目',
field: 'carNumber',
},
{
title: '费用标识',
field: 'location',
},
{
title: '应收金额',
field: 'personId',
},
{
title: '状态',
field: 'state',
slots: {
default: ({ row }) => {
// 可选从DictEnum中获取 DictEnum.WY_CSZT 便于维护
return renderDict(row.state, 'wy_cszt');
},
},
},
{
title: '建帐时间',
field: 'starTime',
},
{
title: '应收时间',
field: 'endTime',
},
{
title: '说明',
field: 'remark',
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
},
];

View File

@@ -0,0 +1,173 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import {
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps
} from '#/adapter/vxe-table';
import {
carChargeExport,
carChargeList,
carChargeRemove,
} from '#/api/property/carCharge';
import type { CarChargeForm } from '#/api/property/carCharge/model';
import { commonDownloadExcel } from '#/utils/file/download';
import carChargeDetailModal from './carCharge-detail-modal.vue';// 详情弹窗
import carChargePayModal from './carCharge-pay-modal.vue';//缴费弹窗
import carCharfeAddModal from './carCharge-add-modal.vue';//创建费用弹窗
import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
// 处理区间选择器RangePicker时间格式 将一个字段映射为两个字段 搜索/导出会用到
// 不需要直接删除
// fieldMappingTime: [
// [
// 'createTime',
// ['params[beginTime]', 'params[endTime]'],
// ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
// ],
// ],
};
const gridOptions: VxeGridProps = {
checkboxConfig: {
// 高亮
highlight: true,
// 翻页时保留选中状态
reserve: true,
// 点击行选中
// trigger: 'row',
},
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// columns: columns(),
columns,
height: 'auto',
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
return await carChargeList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
// 表格全局唯一表示 保存列配置需要用到
id: 'property-carCharge-index'
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
// 详情弹窗
const [CarChargeDetailModal, modalApi] = useVbenModal({
connectedComponent: carChargeDetailModal,
});
// 创建费用弹窗
const [CarCharfeAddModal, addModalApi] = useVbenModal({
connectedComponent: carCharfeAddModal,
});
// 缴费弹窗
const [CarChargePayModal, payModalApi] = useVbenModal({
connectedComponent: carChargePayModal,
});
//打开创建费用弹窗
function handleAdd() {
addModalApi.setData({});
addModalApi.open();
}
//打开详情
async function handleEdit(row: Required<CarChargeForm>) {
modalApi.setData({ id: row.id });
modalApi.open();
}
//打开缴费
async function handleSave(row: Required<CarChargeForm>) {
payModalApi.setData({ id: row.id });
payModalApi.open();
}
async function handleDelete(row: Required<CarChargeForm>) {
await carChargeRemove(row.id);
await tableApi.query();
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable table-title="费用-车辆收费列表">
<template #toolbar-tools>
<Space>
<a-button
type="primary"
v-access:code="['property:carCharge:add']"
@click="handleAdd"
>
创建费用
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
v-access:code="['property:carCharge:edit']"
@click.stop="handleSave(row)"
>
缴费
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认删除?"
@confirm="handleDelete(row)"
>
<ghost-button
danger
v-access:code="['property:carCharge:remove']"
@click.stop=""
>
{{ $t('pages.common.delete') }}
</ghost-button>
</Popconfirm>
<ghost-button
v-access:code="['property:carCharge:edit']"
@click.stop="handleEdit(row)"
>
详情
</ghost-button>
</Space>
</template>
</BasicTable>
<!-- 详情弹窗 -->
<CarChargeDetailModal @reload="tableApi.query()" />
<CarCharfeAddModal @reload="tableApi.query()" />
<CarChargePayModal @reload="tableApi.query()" />
</Page>
</template>

View File

@@ -0,0 +1,101 @@
<script setup lang="ts">
import { computed, 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 { costMeterWaterAdd, costMeterWaterInfo, costMeterWaterUpdate } from '#/api/property/costMeterWater';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const title = computed(() => {
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 80,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: modalSchema(),
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;
if (isUpdate.value && id) {
const record = await costMeterWaterInfo(id);
await formApi.setValues(record);
}
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());
await (isUpdate.value ? costMeterWaterUpdate(data) : costMeterWaterAdd(data));
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="title">
<BasicForm />
</BasicModal>
</template>

View File

@@ -0,0 +1,183 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
export const querySchema: FormSchemaGetter = () => [
{
component: 'Input',
fieldName: 'itemId',
label: '费用类型id',
},
{
component: 'Input',
fieldName: 'meterTypeId',
label: '抄表类型id',
},
{
component: 'Input',
fieldName: 'objName',
label: '对象名称',
},
{
component: 'Input',
fieldName: 'curDegrees',
label: '本期度数',
},
{
component: 'Input',
fieldName: 'preDegrees',
label: '上期度数',
},
{
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
fieldName: 'preReadingTime',
label: '上期读表时间',
},
{
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
fieldName: 'curReadingTime',
label: '本期读表时间',
},
{
component: 'Input',
fieldName: 'searchValue',
label: '搜索值',
},
];
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// export const columns: () => VxeGridProps['columns'] = () => [
export const columns: VxeGridProps['columns'] = [
{ type: 'checkbox', width: 60 },
{
title: '主键',
field: 'id',
},
{
title: '费用类型id',
field: 'itemId',
},
{
title: '抄表类型id',
field: 'meterTypeId',
},
{
title: '对象名称',
field: 'objName',
},
{
title: '本期度数',
field: 'curDegrees',
},
{
title: '上期度数',
field: 'preDegrees',
},
{
title: '上期读表时间',
field: 'preReadingTime',
},
{
title: '本期读表时间',
field: 'curReadingTime',
},
{
title: '备注',
field: 'remark',
},
{
title: '搜索值',
field: 'searchValue',
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
},
];
export const modalSchema: FormSchemaGetter = () => [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '费用类型id',
fieldName: 'itemId',
component: 'Input',
rules: 'required',
},
{
label: '抄表类型id',
fieldName: 'meterTypeId',
component: 'Input',
rules: 'required',
},
{
label: '对象名称',
fieldName: 'objName',
component: 'Input',
rules: 'required',
},
{
label: '本期度数',
fieldName: 'curDegrees',
component: 'Input',
rules: 'required',
},
{
label: '上期度数',
fieldName: 'preDegrees',
component: 'Input',
rules: 'required',
},
{
label: '上期读表时间',
fieldName: 'preReadingTime',
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
{
label: '本期读表时间',
fieldName: 'curReadingTime',
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
{
label: '备注',
fieldName: 'remark',
component: 'Textarea',
},
{
label: '搜索值',
fieldName: 'searchValue',
component: 'Input',
},
];

View File

@@ -0,0 +1,182 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import {
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps
} from '#/adapter/vxe-table';
import {
costMeterWaterExport,
costMeterWaterList,
costMeterWaterRemove,
} from '#/api/property/costMeterWater';
import type { CostMeterWaterForm } from '#/api/property/costMeterWater/model';
import { commonDownloadExcel } from '#/utils/file/download';
import costMeterWaterModal from './costMeterWater-modal.vue';
import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
// 处理区间选择器RangePicker时间格式 将一个字段映射为两个字段 搜索/导出会用到
// 不需要直接删除
// fieldMappingTime: [
// [
// 'createTime',
// ['params[beginTime]', 'params[endTime]'],
// ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
// ],
// ],
};
const gridOptions: VxeGridProps = {
checkboxConfig: {
// 高亮
highlight: true,
// 翻页时保留选中状态
reserve: true,
// 点击行选中
// trigger: 'row',
},
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// columns: columns(),
columns,
height: 'auto',
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
return await costMeterWaterList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
// 表格全局唯一表示 保存列配置需要用到
id: 'property-costMeterWater-index'
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [CostMeterWaterModal, modalApi] = useVbenModal({
connectedComponent: costMeterWaterModal,
});
function handleAdd() {
modalApi.setData({});
modalApi.open();
}
async function handleEdit(row: Required<CostMeterWaterForm>) {
modalApi.setData({ id: row.id });
modalApi.open();
}
async function handleDelete(row: Required<CostMeterWaterForm>) {
await costMeterWaterRemove(row.id);
await tableApi.query();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: Required<CostMeterWaterForm>) => row.id);
Modal.confirm({
title: '提示',
okType: 'danger',
content: `确认删除选中的${ids.length}条记录吗?`,
onOk: async () => {
await costMeterWaterRemove(ids);
await tableApi.query();
},
});
}
function handleDownloadExcel() {
commonDownloadExcel(costMeterWaterExport, '费用-水电抄数据', tableApi.formApi.form.values, {
fieldMappingTime: formOptions.fieldMappingTime,
});
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable table-title="费用-水电抄列表">
<template #toolbar-tools>
<Space>
<a-button
v-access:code="['property:costMeterWater:export']"
@click="handleDownloadExcel"
>
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['property:costMeterWater:remove']"
@click="handleMultiDelete">
{{ $t('pages.common.delete') }}
</a-button>
<a-button
type="primary"
v-access:code="['property:costMeterWater:add']"
@click="handleAdd"
>
{{ $t('pages.common.add') }}
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
v-access:code="['property:costMeterWater: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:costMeterWater:remove']"
@click.stop=""
>
{{ $t('pages.common.delete') }}
</ghost-button>
</Popconfirm>
</Space>
</template>
</BasicTable>
<CostMeterWaterModal @reload="tableApi.query()" />
</Page>
</template>

View File

@@ -0,0 +1,181 @@
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';
export const querySchema: FormSchemaGetter = () => [
{
component: 'ApiSelect',
componentProps: {
api:async ()=>{
return (await costItemSettingList({pageSize: 1000, pageNum: 1}))?.rows
},
afterFetch: (data: { chargeItem: string; id: string }[]) => {
return data.map((item: any) => ({
label: item.chargeItem,
value: item.id,
}));
},
},
fieldName: 'costItemsId',
label: '收费项目',
},
{
component: 'Select',
fieldName: 'state',
label: '状态',
componentProps: {
options: getDictOptions('wy_fysfzt')
},
},
];
export const columns: VxeGridProps['columns'] = [
{type: 'checkbox', width: 60},
{
title: '房屋',
field: 'roomId',
minWidth: 150,
},
{
title: '收费项目',
field: 'costItemsId',
width: 150,
},
{
title: '应收金额',
field: 'amountReceivable',
width: 150,
},
{
title: '计费开始时间',
field: 'startTime',
width: 150,
},
{
title: '计费结束时间',
field: 'endTime',
width: 150,
},
{
title: '状态',
field: 'state',
width: 150,
slots: {
default: ({row}) => {
return renderDict(row.state, 'wy_fysfzt')
}
}
},
{
title: '说明',
field: 'remark',
width: 150,
},
{
field: 'action',
fixed: 'right',
slots: {default: 'action'},
title: '操作',
width: 180,
},
];
export const modalSchema: FormSchemaGetter = () => [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '房屋',
fieldName: 'roomId',
component: 'TreeSelect',
componentProps: {
},
rules: 'selectRequired',
},
{
label: '费用类型',
fieldName: 'costType',
component: 'Select',
componentProps: {
},
rules: 'selectRequired',
},
{
label: '收费项目',
fieldName: 'costItemsId',
component: 'ApiSelect',
componentProps: {},
rules: 'selectRequired',
},
{
label: '应收金额',
fieldName: 'amountReceivable',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '计费时间',
fieldName: 'chargeTime',
component: 'RangePicker',
componentProps: {
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
},
rules: 'selectRequired',
},
{
label: '说明',
fieldName: 'remark',
component: 'Textarea',
formItemClass: 'col-span-2',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
];
export const modalSchemaUpdate: FormSchemaGetter = () => [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '支付方式',
fieldName: 'payType',
component: 'Select',
componentProps: {
options:getDictOptions('wy_zffs')
},
rules: 'selectRequired',
},
{
label: '缴费周期',
fieldName: 'chargeCycle',
component: 'InputNumber',
componentProps: {
min:1,
precision:0,
placeholder:'请输入缴费周期(月)'
},
suffix: () => h('span', { style: {fontSize: '0.875rem',fontWeight:500,} }, '月'),
rules: 'selectRequired',
},
]

View File

@@ -0,0 +1,91 @@
<script setup lang="ts">
import {ref, 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 {renderDict} from "#/utils/render";
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";
const [BasicModal, modalApi] = useVbenModal({
onOpenChange: handleOpenChange,
onClosed() {
houseChargeDetail.value = null;
},
});
const houseChargeDetail = shallowRef<null | HouseChargeVO>(null);
const room=ref<RoomVO>();
const costItem=ref<CostItemSettingVO>();
async function handleOpenChange(open: boolean) {
if (!open) {
return null;
}
modalApi.modalLoading(true);
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);
}
</script>
<template>
<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="房间">
{{room?.roomNumber}}
</DescriptionsItem>
<DescriptionsItem label="房间面积">
{{`建筑面积:${room?.area} 套内面积:${room?.insideInArea}`}}
</DescriptionsItem>
<DescriptionsItem label="单价">
{{ costItem?.unitPrice }}
</DescriptionsItem>
<DescriptionsItem label="附加费">
{{costItem?.surcharge}}
</DescriptionsItem>
<DescriptionsItem label="应收金额">
<span style="font-size: 16px;font-weight: 600;color: red" v-if="houseChargeDetail.amountReceivable">
{{houseChargeDetail.amountReceivable}}
</span>
</DescriptionsItem>
<DescriptionsItem label="支付方式" v-if="houseChargeDetail.payType">
<component
:is="renderDict(houseChargeDetail.payType,'wy_zffs')"
/>
</DescriptionsItem>
<DescriptionsItem label="缴费周期" v-if="houseChargeDetail.chargeCycle">
{{houseChargeDetail.chargeCycle}}
</DescriptionsItem>
<DescriptionsItem label="说明" :span="2">
{{houseChargeDetail.remark}}
</DescriptionsItem>
</Descriptions>
</BasicModal>
</template>

View File

@@ -0,0 +1,201 @@
<script setup lang="ts">
import {computed, ref} from 'vue';
import {useVbenModal} from '@vben/common-ui';
import {$t} from '@vben/locales';
import {cloneDeep, handleNode} from '@vben/utils';
import {useVbenForm} from '#/adapter/form';
import {
houseChargeAdd,
houseChargeInfo,
houseChargeUpdate
} from '#/api/property/costManagement/houseCharge';
import {defaultFormValueGetter, useBeforeCloseDiff} from '#/utils/popup';
import {modalSchema} from './data';
import {communityTree} from "#/api/property/community";
import {costItemSettingList} from "#/api/property/costManagement/costItemSetting";
import type {CostItemSettingVO} from "#/api/property/costManagement/costItemSetting/model";
import {getDictOptions} from "#/utils/dict";
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const costItemOptions = ref<CostItemSettingVO[]>([]);
const title = computed(() => {
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 80,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: modalSchema(),
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);
await initRoomOptions()
await queryCostItemOptions()
await initCostTypeOptions()
const {id} = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
const record = await houseChargeInfo(id);
record.chargeTime = [record.startTime, record.endTime]
await formApi.setValues(record);
}
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());
if (data.chargeTime && data.chargeTime.length) {
data.startTime = data.chargeTime[0]
data.endTime = data.chargeTime[1]
}
await (isUpdate.value ? houseChargeUpdate(data) : houseChargeAdd(data));
resetInitialized();
emit('reload');
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.lock(false);
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
/**
* 房间数据
*/
async function initRoomOptions() {
const roomList = await communityTree(5);
const splitStr = '/';
handleNode(roomList, 'label', splitStr, function (node: any) {
if (node.level != 5) {
node.disabled = true;
}
});
formApi.updateSchema([
{
componentProps: () => ({
class: 'w-full',
fieldNames: {
key: 'id',
label: 'label',
value: 'code',
children: 'children',
},
placeholder: '请选择房间',
showSearch: true,
treeData: roomList,
treeDefaultExpandAll: true,
treeLine: {showLeafIcon: false},
// 筛选的字段
treeNodeFilterProp: 'label',
// 选中后显示在输入框的值
treeNodeLabelProp: 'fullName',
}),
fieldName: 'roomId',
},
]);
}
/**
* 查询费用项设置
*/
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',
onChange: async (value: string) => {
if (value) {
const costItem = costItemOptions.value.find(item => item.id == value)
if(costItem){
await formApi.setFieldValue('remark', `单价:${costItem.unitPrice} 附加费:${costItem.surcharge}`);
}
}
}
},
fieldName: 'costItemsId'
}])
}
/**
* 初始化费用类型
*/
async function initCostTypeOptions(){
formApi.updateSchema([{
componentProps: {
options: getDictOptions('pro_expense_type'),
onChange: async (value: string) => {
if (value) {
await queryCostItemOptions(value)
await formApi.setFieldValue('costItemsId', null)
}
}
},
fieldName: 'costType'
}])
}
</script>
<template>
<BasicModal :title="title">
<BasicForm/>
</BasicModal>
</template>

View File

@@ -0,0 +1,147 @@
<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 {
houseChargeAdd,
houseChargeInfo,
houseChargeUpdate
} from '#/api/property/costManagement/houseCharge';
import {defaultFormValueGetter, useBeforeCloseDiff} from '#/utils/popup';
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: {
// 默认占满两列
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 80,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: modalSchemaUpdate(),
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);
if (record.value) {
room.value = record.value.roomVo
costItem.value = record.value.costItemsVo
}
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.payType = data.payType
record.value.chargeCycle = data.chargeCycle
await houseChargeUpdate(record.value) ;
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="缴费">
<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="房间">
{{ room.roomNumber }}
</DescriptionsItem>
<DescriptionsItem label="面积(㎡)">
{{ `${room?.area} (套内面积:${room?.insideInArea}` }}
</DescriptionsItem>
<DescriptionsItem label="单价">
{{ costItem?.unitPrice }}
</DescriptionsItem>
<DescriptionsItem label="附加费">
{{ costItem?.surcharge }}
</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

@@ -0,0 +1,189 @@
<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 {
houseChargeExport,
houseChargeList,
houseChargeRemove,
} from '#/api/property/costManagement/houseCharge';
import type { HouseChargeForm } from '#/api/property/costManagement/houseCharge/model';
import { commonDownloadExcel } from '#/utils/file/download';
import houseChargeAdd from './houseCharge-add.vue';
import houseChargeUpdate from './houseCharge-update.vue';
import houseChargeDetail from './house-charge-detail.vue';
import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
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,
// 点击行选中
// trigger: 'row',
},
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// columns: columns(),
columns,
height: 'auto',
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
return await houseChargeList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
// 表格全局唯一表示 保存列配置需要用到
id: 'property-houseCharge-index'
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [HouseChargeAdd, modalApi] = useVbenModal({
connectedComponent: houseChargeAdd,
});
const [HouseChargeUpdate, updateApi] = useVbenModal({
connectedComponent: houseChargeUpdate,
});
const [HouseChargeDetail, detailApi] = useVbenModal({
connectedComponent: houseChargeDetail,
});
function handleAdd() {
modalApi.setData({});
modalApi.open();
}
async function handleEdit(row: Required<HouseChargeForm>) {
updateApi.setData({ id: row.id });
updateApi.open();
}
async function handleInfo(row: Required<HouseChargeForm>) {
detailApi.setData({ id: row.id });
detailApi.open();
}
async function handleDelete(row: Required<HouseChargeForm>) {
await houseChargeRemove(row.id);
await tableApi.query();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: Required<HouseChargeForm>) => row.id);
Modal.confirm({
title: '提示',
okType: 'danger',
content: `确认删除选中的${ids.length}条记录吗?`,
onOk: async () => {
await houseChargeRemove(ids);
await tableApi.query();
},
});
}
function handleDownloadExcel() {
commonDownloadExcel(houseChargeExport, '房屋收费数据', tableApi.formApi.form.values, {
fieldMappingTime: formOptions.fieldMappingTime,
});
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable table-title="房屋收费列表">
<template #toolbar-tools>
<Space>
<a-button
v-access:code="['property:houseCharge:export']"
@click="handleDownloadExcel"
>
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['property:houseCharge:remove']"
@click="handleMultiDelete">
取消
</a-button>
<a-button
type="primary"
v-access:code="['property:houseCharge:add']"
@click="handleAdd"
>
{{ $t('pages.common.add') }}
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
v-access:code="['property:houseCharge:info']"
@click.stop="handleInfo(row)"
>
{{ $t('pages.common.info') }}
</ghost-button>
<ghost-button
v-access:code="['property:houseCharge:edit']"
@click.stop="handleEdit(row)"
>
缴费
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认取消?"
@confirm="handleDelete(row)"
>
<ghost-button
danger
v-access:code="['property:houseCharge:remove']"
@click.stop=""
>
取消
</ghost-button>
</Popconfirm>
</Space>
</template>
</BasicTable>
<HouseChargeAdd @reload="tableApi.query()" />
<HouseChargeUpdate @reload="tableApi.query()" />
<HouseChargeDetail/>
</Page>
</template>

View File

@@ -0,0 +1,103 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import {getDictOptions} from "#/utils/dict";
import {renderDict} from "#/utils/render";
export const querySchema: FormSchemaGetter = () => [
{
component: 'Input',
fieldName: 'returnNo',
label: '退款单号',
},
{
component: 'Select',
componentProps:{
options:getDictOptions('wy_tfshzt')
},
fieldName: 'state',
label: '审核状态',
},
];
export const columns: VxeGridProps['columns'] = [
{ type: 'checkbox', width: 60 },
{
title: '退款单号',
field: 'returnNo',
width: 180,
},
{
title: '支付单号',
field: 'payNo',
width: 180,
},
{
title: '支付金额',
field: 'payAcount',
width: 100,
},
{
title: '支付时间',
field: 'payTime',
width: 180,
},
{
title: '退款原因',
field: 'reason',
minWidth: 180,
},
{
title: '审核状态',
field: 'state',
width: 150,
slots:{
default:({row})=>{
return renderDict(row.state,'wy_tfshzt')
}
}
},
{
title: '审核意见',
field: 'remark',
width: 180,
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
},
];
export const stateOptions = [
{ label: '审核通过', value: '1' },
{ label: '审核不通过', value: '2' },
];
export const modalSchema: FormSchemaGetter = () => [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '审核状态',
fieldName: 'state',
component: 'RadioGroup',
componentProps: {
buttonStyle: 'solid',
options: stateOptions,
},
rules:'required'
},
{
label: '审核意见',
fieldName: 'remark',
component: 'Textarea',
},
];

View File

@@ -0,0 +1,172 @@
<script setup lang="ts">
import {Page, useVbenModal, type VbenFormProps} from '@vben/common-ui';
import {Modal, Popconfirm, Space} from 'ant-design-vue';
import {
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps
} from '#/adapter/vxe-table';
import {
returnPayFeeExport,
returnPayFeeList,
returnPayFeeRemove,
} from '#/api/property/costManagement/returnPayFee';
import type {ReturnPayFeeForm} from '#/api/property/costManagement/returnPayFee/model';
import {commonDownloadExcel} from '#/utils/file/download';
import returnPayFeeModal from './returnPayFee-modal.vue';
import returnPayFeeDetail from './return-pay-detail.vue';
import {columns, querySchema} from './data';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
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,
// 点击行选中
// trigger: 'row',
},
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// columns: columns(),
columns,
height: 'auto',
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({page}, formValues = {}) => {
return await returnPayFeeList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
// 表格全局唯一表示 保存列配置需要用到
id: 'property-returnPayFee-index'
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [ReturnPayFeeModal, modalApi] = useVbenModal({
connectedComponent: returnPayFeeModal,
});
const [ReturnPayFeeDetail, detailApi] = useVbenModal({
connectedComponent: returnPayFeeDetail,
});
function handleAdd() {
modalApi.setData({});
modalApi.open();
}
async function handleEdit(row: Required<ReturnPayFeeForm>) {
modalApi.setData({id: row.id});
modalApi.open();
}
async function handleInfo(row: Required<ReturnPayFeeForm>) {
detailApi.setData({id: row.id});
detailApi.open();
}
async function handleDelete(row: Required<ReturnPayFeeForm>) {
await returnPayFeeRemove(row.id);
await tableApi.query();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: Required<ReturnPayFeeForm>) => row.id);
Modal.confirm({
title: '提示',
okType: 'danger',
content: `确认删除选中的${ids.length}条记录吗?`,
onOk: async () => {
await returnPayFeeRemove(ids);
await tableApi.query();
},
});
}
function handleDownloadExcel() {
commonDownloadExcel(returnPayFeeExport, '退费审核数据', tableApi.formApi.form.values, {
fieldMappingTime: formOptions.fieldMappingTime,
});
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable table-title="退费审核列表">
<template #toolbar-tools>
<Space>
<a-button
v-access:code="['property:returnPayFee:export']"
@click="handleDownloadExcel"
>
{{ $t('pages.common.export') }}
</a-button>
<!-- <a-button-->
<!-- :disabled="!vxeCheckboxChecked(tableApi)"-->
<!-- danger-->
<!-- type="primary"-->
<!-- v-access:code="['property:returnPayFee:remove']"-->
<!-- @click="handleMultiDelete">-->
<!-- {{ $t('pages.common.delete') }}-->
<!-- </a-button>-->
<!-- <a-button-->
<!-- type="primary"-->
<!-- v-access:code="['property:returnPayFee:add']"-->
<!-- @click="handleAdd"-->
<!-- >-->
<!-- {{ $t('pages.common.add') }}-->
<!-- </a-button>-->
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
:disabled="row.state!='0'"
v-access:code="['property:returnPayFee:edit']"
@click.stop="handleEdit(row)"
>
审核
</ghost-button>
<ghost-button
v-access:code="['property:returnPayFee:info']"
@click.stop="handleInfo(row)"
>
{{ $t('pages.common.info') }}
</ghost-button>
</Space>
</template>
</BasicTable>
<ReturnPayFeeModal @reload="tableApi.query()"/>
<ReturnPayFeeDetail/>
</Page>
</template>

View File

@@ -0,0 +1,64 @@
<script setup lang="ts">
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 {renderDict} from "#/utils/render";
dayjs.extend(duration);
dayjs.extend(relativeTime);
import {returnPayFeeInfo} from "#/api/property/costManagement/returnPayFee";
import type {ReturnPayFeeVO} from "#/api/property/costManagement/returnPayFee/model";
const [BasicModal, modalApi] = useVbenModal({
onOpenChange: handleOpenChange,
onClosed() {
returnPayDetail.value = null;
},
});
const returnPayDetail = shallowRef<null | ReturnPayFeeVO>(null);
async function handleOpenChange(open: boolean) {
if (!open) {
return null;
}
modalApi.modalLoading(true);
const {id} = modalApi.getData() as { id: number | string };
returnPayDetail.value = await returnPayFeeInfo(id);
modalApi.modalLoading(false);
}
</script>
<template>
<BasicModal :footer="false" :fullscreen-button="false" title="退费审核详情" class="w-[70%]">
<Descriptions v-if="returnPayDetail" size="small" :column="2" bordered
:labelStyle="{width:'100px'}">
<DescriptionsItem label="退款单号">
{{ returnPayDetail.returnNo }}
</DescriptionsItem>
<DescriptionsItem label="支付单号">
{{ returnPayDetail.payNo }}
</DescriptionsItem>
<DescriptionsItem label="支付金额">
{{returnPayDetail.payAcount}}
</DescriptionsItem>
<DescriptionsItem label="支付时间">
{{ returnPayDetail.payTime }}
</DescriptionsItem>
<DescriptionsItem label="退款原因" :span="2">
{{returnPayDetail.reason}}
</DescriptionsItem>
<DescriptionsItem label="审核状态" :span="2">
<component
:is="renderDict(returnPayDetail.state,'wy_tfshzt')"
/>
</DescriptionsItem>
<DescriptionsItem label="审核意见" :span="2">
{{returnPayDetail.remark}}
</DescriptionsItem>
</Descriptions>
</BasicModal>
</template>

View File

@@ -0,0 +1,108 @@
<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 {
returnPayFeeInfo,
returnPayFeeUpdate
} from '#/api/property/costManagement/returnPayFee';
import {defaultFormValueGetter, useBeforeCloseDiff} from '#/utils/popup';
import {modalSchema} from './data';
import type {ReturnPayFeeVO} from "#/api/property/costManagement/returnPayFee/model";
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const record = ref<ReturnPayFeeVO>({
id:'',
state:'1',
remark:''
})
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 80,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: modalSchema(),
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;
if (isUpdate.value && id) {
record.value = await returnPayFeeInfo(id);
record.value.state = '1'
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.state=data.state
record.value.remark=data.remark
await returnPayFeeUpdate(record.value);
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/>
</BasicModal>
</template>