|
|
@@ -1,12 +1,22 @@
|
|
|
<script setup lang="ts">
|
|
|
-
|
|
|
-import { onMounted } from 'vue'
|
|
|
+import { onMounted, ref, reactive } from 'vue'
|
|
|
import { clientDownloadExcel, clientGet, clientPost } from '@/utils/request.ts'
|
|
|
-import { ElLoading, ElMessage } from 'element-plus'
|
|
|
+import { ElLoading, ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import type { UploadProps, FormInstance } from 'element-plus'
|
|
|
+import { Plus, Delete, Edit, Search, Refresh, Download, Upload } from '@element-plus/icons-vue'
|
|
|
+
|
|
|
+
|
|
|
+interface PageType<T> {
|
|
|
+ records: T[];
|
|
|
+ total: number;
|
|
|
+ size: number;
|
|
|
+ current: number;
|
|
|
+ pages: number;
|
|
|
+}
|
|
|
|
|
|
interface Property {
|
|
|
id: string; // 主键
|
|
|
- receiptNumber: string; // 收据号(必填)
|
|
|
+ receiptNumber?: string; // 收据号
|
|
|
building?: string; // 楼栋
|
|
|
roomNumber?: string; // 房号
|
|
|
collectionTime?: Date; // 收取时间
|
|
|
@@ -33,7 +43,7 @@ interface PropertyListResponse extends BaseResponse{
|
|
|
}
|
|
|
|
|
|
interface AddProperty {
|
|
|
- receiptNumber: string; // 收据号(必填)
|
|
|
+ receiptNumber?: string; // 收据号
|
|
|
building?: string; // 楼栋
|
|
|
roomNumber?: string; // 房号
|
|
|
collectionTime?: Date; // 收取时间
|
|
|
@@ -63,8 +73,24 @@ interface UpdateProperty {
|
|
|
remarks?: string; // 备注
|
|
|
}
|
|
|
|
|
|
+// Reactive state variables
|
|
|
+const tableData = ref<Property[]>([]);
|
|
|
+const total = ref(0);
|
|
|
+const pageSize = ref(10);
|
|
|
+const pageNum = ref(1);
|
|
|
|
|
|
+const searchForm = reactive({
|
|
|
+ receiptNumber: '',
|
|
|
+ roomNumber: '',
|
|
|
+ building: ''
|
|
|
+});
|
|
|
|
|
|
+const dialogVisible = ref(false);
|
|
|
+const dialogTitle = ref('');
|
|
|
+const isEdit = ref(false);
|
|
|
+const formData = reactive<Property>({});
|
|
|
+const formRef = ref<FormInstance>();
|
|
|
+const selectedIds = ref<string[]>([]);
|
|
|
|
|
|
//新增
|
|
|
const add = async (data:AddProperty) =>{
|
|
|
@@ -74,10 +100,11 @@ const add = async (data:AddProperty) =>{
|
|
|
|
|
|
if(res.code !== 200){
|
|
|
ElMessage.error(res.msg);
|
|
|
- return;
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
ElMessage.success(res.msg);
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
//根据id获取数据
|
|
|
@@ -85,9 +112,9 @@ const getById = async (id:string) =>{
|
|
|
const res = await clientGet<null,PropertyOneResponse>('/ainnovation/getById/'+id);
|
|
|
if(res.code !== 200){
|
|
|
ElMessage.error(res.msg);
|
|
|
- return;
|
|
|
+ return null;
|
|
|
}
|
|
|
- console.log(res.data);
|
|
|
+ return res.data;
|
|
|
}
|
|
|
|
|
|
//分页获取数据
|
|
|
@@ -100,11 +127,11 @@ const getList = async () =>{
|
|
|
building?: string;
|
|
|
},PropertyListResponse>('/ainnovation/findByPage',{
|
|
|
params:{
|
|
|
- pageNum: 1,
|
|
|
- pageSize: 10,
|
|
|
- building:'',//这是模糊搜索的值(如果没有值就不要传个空的过去)
|
|
|
- roomNumber:'',//这是模糊搜索的值(如果没有值就不要传个空的过去)
|
|
|
- receiptNumber:''//这是模糊搜索的值(如果没有值就不要传个空的过去)
|
|
|
+ pageNum: pageNum.value,
|
|
|
+ pageSize: pageSize.value,
|
|
|
+ building: searchForm.building || undefined,
|
|
|
+ roomNumber: searchForm.roomNumber || undefined,
|
|
|
+ receiptNumber: searchForm.receiptNumber || undefined
|
|
|
}
|
|
|
});
|
|
|
|
|
|
@@ -112,30 +139,33 @@ const getList = async () =>{
|
|
|
ElMessage.error(res.msg);
|
|
|
return;
|
|
|
}
|
|
|
- console.log(res.data);
|
|
|
+ tableData.value = res.data.records;
|
|
|
+ total.value = res.data.total;
|
|
|
}
|
|
|
|
|
|
//批量删除
|
|
|
const delBatch = async (ids: string[]) => {
|
|
|
- const res = await clientPost<string, BaseResponse>('/ainnovation/deleteBatch', JSON.stringify(ids));
|
|
|
+ // Note: clientPost expects the data directly, not JSON.stringify
|
|
|
+ const res = await clientPost<string[], BaseResponse>('/ainnovation/deleteBatch', ids);
|
|
|
if (res.code !== 200) {
|
|
|
ElMessage.error(res.msg)
|
|
|
- return
|
|
|
+ return false;
|
|
|
}
|
|
|
ElMessage.success(res.msg)
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
//修改
|
|
|
-const update = async (updateId:string,data:UpdateProperty) =>{
|
|
|
+const update = async (data:UpdateProperty) =>{
|
|
|
const res = await clientPost<UpdateProperty,BaseResponse>('/ainnovation/update',{
|
|
|
- ...data,
|
|
|
- id:updateId
|
|
|
+ ...data
|
|
|
})
|
|
|
if(res.code !== 200){
|
|
|
ElMessage.error(res.msg);
|
|
|
- return;
|
|
|
+ return false;
|
|
|
}
|
|
|
ElMessage.success(res.msg);
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
//导出为excel
|
|
|
@@ -150,11 +180,12 @@ const exportExcel = async () => {
|
|
|
try {
|
|
|
await clientDownloadExcel('/ainnovation/exportData', {
|
|
|
params: {
|
|
|
- roomNumber: '',//这是模糊搜索的值(如果没有值就不要传个空的过去)
|
|
|
- building: '',//这是模糊搜索的值(如果没有值就不要传个空的过去)
|
|
|
- receiptNumber: '',//这是模糊搜索的值(如果没有值就不要传个空的过去)
|
|
|
+ roomNumber: searchForm.roomNumber || undefined,
|
|
|
+ building: searchForm.building || undefined,
|
|
|
+ receiptNumber: searchForm.receiptNumber || undefined,
|
|
|
},
|
|
|
})
|
|
|
+ ElMessage.success('导出成功');
|
|
|
} catch (error) {
|
|
|
console.error('导出失败:', error)
|
|
|
ElMessage.error('导出失败')
|
|
|
@@ -165,29 +196,317 @@ const exportExcel = async () => {
|
|
|
|
|
|
//excel导入
|
|
|
const importExcel = async (file: File) => {
|
|
|
- const res = await clientPost<{file: File},BaseResponse>('/ainnovation/importData',{
|
|
|
- file
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append('file', file);
|
|
|
+ const res = await clientPost<FormData,BaseResponse>('/ainnovation/importData', formData, {
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'multipart/form-data'
|
|
|
+ }
|
|
|
})
|
|
|
if(res.code !== 200){
|
|
|
ElMessage.error(res.msg);
|
|
|
- return;
|
|
|
+ return false;
|
|
|
}
|
|
|
ElMessage.success(res.msg);
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
+// --- CRUD Operations and Handlers ---
|
|
|
+
|
|
|
+const handleAdd = () => {
|
|
|
+ isEdit.value = false;
|
|
|
+ dialogTitle.value = '新增公租房收入信息';
|
|
|
+ // Reset form data
|
|
|
+ Object.keys(formData).forEach(key => delete formData[key as keyof Property]);
|
|
|
+ dialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const handleEdit = async (row: Property) => {
|
|
|
+ isEdit.value = true;
|
|
|
+ dialogTitle.value = '编辑公租房收入信息';
|
|
|
+ const data = await getById(row.id);
|
|
|
+ if (data) {
|
|
|
+ Object.assign(formData, data);
|
|
|
+ dialogVisible.value = true;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleDelete = async (id: string) => {
|
|
|
+ ElMessageBox.confirm('确定删除此条记录吗?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ }).then(async () => {
|
|
|
+ const success = await delBatch([id]);
|
|
|
+ if (success) {
|
|
|
+ getList();
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ ElMessage.info('已取消删除');
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const handleBatchDelete = () => {
|
|
|
+ if (selectedIds.value.length === 0) {
|
|
|
+ ElMessage.warning('请选择要删除的记录');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ElMessageBox.confirm(`确定删除选中的 ${selectedIds.value.length} 条记录吗?`, '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ }).then(async () => {
|
|
|
+ const success = await delBatch(selectedIds.value);
|
|
|
+ if (success) {
|
|
|
+ getList();
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ ElMessage.info('已取消批量删除');
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const handleSearch = () => {
|
|
|
+ pageNum.value = 1; // Reset to first page on search
|
|
|
+ getList();
|
|
|
+};
|
|
|
+
|
|
|
+const handleResetSearch = () => {
|
|
|
+ searchForm.receiptNumber = '';
|
|
|
+ searchForm.roomNumber = '';
|
|
|
+ searchForm.building = '';
|
|
|
+ pageNum.value = 1;
|
|
|
+ getList();
|
|
|
+};
|
|
|
+
|
|
|
+const handleConfirm = async () => {
|
|
|
+ if (!formRef.value) return;
|
|
|
+ await formRef.value.validate(async (valid) => {
|
|
|
+ if (valid) {
|
|
|
+ let success = false;
|
|
|
+ if (isEdit.value) {
|
|
|
+ success = await update(formData);
|
|
|
+ } else {
|
|
|
+ success = await add(formData);
|
|
|
+ }
|
|
|
+ if (success) {
|
|
|
+ dialogVisible.value = false;
|
|
|
+ getList();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ ElMessage.error('请检查表单填写');
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const handleCurrentChange = (val: number) => {
|
|
|
+ pageNum.value = val;
|
|
|
+ getList();
|
|
|
+};
|
|
|
+
|
|
|
+const handleSizeChange = (val: number) => {
|
|
|
+ pageSize.value = val;
|
|
|
+ pageNum.value = 1; // Reset to first page when page size changes
|
|
|
+ getList();
|
|
|
+};
|
|
|
|
|
|
+const handleSelectionChange = (selection: Property[]) => {
|
|
|
+ selectedIds.value = selection.map(item => item.id);
|
|
|
+};
|
|
|
|
|
|
+// Modified handleUploadSuccess as per user's request
|
|
|
+const handleUploadSuccess: UploadProps['onSuccess'] = async (response, uploadFile) => {
|
|
|
+ if (response) { // Check if response exists
|
|
|
+ ElMessage.success('文件导入成功');
|
|
|
+ getList();
|
|
|
+ } else {
|
|
|
+ // Assuming response might be null/undefined or not have a msg property if it's an error
|
|
|
+ ElMessage.error(`文件导入失败: ${response?.msg || '未知错误'}`);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleUploadError: UploadProps['onError'] = (error) => {
|
|
|
+ ElMessage.error(`文件导入失败: ${error.message}`);
|
|
|
+};
|
|
|
|
|
|
+const handleBeforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
|
|
+ const isExcel = rawFile.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || rawFile.type === 'application/vnd.ms-excel';
|
|
|
+ if (!isExcel) {
|
|
|
+ ElMessage.error('导入文件只能是 Excel 格式!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+};
|
|
|
|
|
|
const init = () => {
|
|
|
+ getList();
|
|
|
}
|
|
|
|
|
|
onMounted(()=>{
|
|
|
init();
|
|
|
})
|
|
|
-
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
- <div>创新创业收入情况信息</div>
|
|
|
+ <div class="p-4">
|
|
|
+ <h1 class="text-2xl font-bold mb-6">创新创业收入情况信息</h1>
|
|
|
+
|
|
|
+ <!-- Search and Action Bar -->
|
|
|
+ <div class="mb-6 p-4 bg-white rounded-lg shadow-sm flex flex-wrap items-center gap-4">
|
|
|
+ <el-form :inline="true" :model="searchForm" class="flex-grow flex flex-wrap gap-x-4">
|
|
|
+ <el-form-item label="收据号">
|
|
|
+ <el-input v-model="searchForm.receiptNumber" placeholder="请输入收据号" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="楼栋">
|
|
|
+ <el-input v-model="searchForm.building" placeholder="请输入楼栋" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="房号">
|
|
|
+ <el-input v-model="searchForm.roomNumber" placeholder="请输入房号" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
|
|
|
+ <el-button :icon="Refresh" @click="handleResetSearch">重置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
|
|
|
+ <el-button type="danger" :icon="Delete" @click="handleBatchDelete" :disabled="selectedIds.length === 0">批量删除</el-button>
|
|
|
+ <el-button type="success" :icon="Download" @click="exportExcel">导出</el-button>
|
|
|
+ <el-upload
|
|
|
+ class="inline-block ml-2"
|
|
|
+ action="/api/ainnovation/importData"
|
|
|
+ :show-file-list="false"
|
|
|
+ :on-success="handleUploadSuccess"
|
|
|
+ :on-error="handleUploadError"
|
|
|
+ :before-upload="handleBeforeUpload"
|
|
|
+ :http-request="(options) => importExcel(options.file)"
|
|
|
+ >
|
|
|
+ <el-button type="info" :icon="Upload">导入</el-button>
|
|
|
+ </el-upload>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Table -->
|
|
|
+ <div class="bg-white rounded-lg shadow-sm p-4">
|
|
|
+ <el-table :data="tableData" style="width: 100%" max-height="65vh" border @selection-change="handleSelectionChange">
|
|
|
+ <el-table-column type="selection" width="55" />
|
|
|
+ <el-table-column prop="receiptNumber" label="收据号" width="100" />
|
|
|
+ <el-table-column prop="building" label="楼栋" width="80" />
|
|
|
+ <el-table-column prop="roomNumber" label="房号" width="80" />
|
|
|
+ <el-table-column prop="collectionTime" label="收取时间" width="180">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{
|
|
|
+ row.collectionTime
|
|
|
+ ? new Date(row.collectionTime).toLocaleString('zh-CN', {
|
|
|
+ year: 'numeric',
|
|
|
+ month: '2-digit',
|
|
|
+ day: '2-digit',
|
|
|
+ hour: '2-digit',
|
|
|
+ minute: '2-digit',
|
|
|
+ second: '2-digit',
|
|
|
+ hour12: false,
|
|
|
+ }).replace(/\//g, '-')
|
|
|
+ : '-'
|
|
|
+ }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="rent" label="房租(元)" width="100" />
|
|
|
+ <el-table-column prop="deposit" label="押金(元)" width="100" />
|
|
|
+ <el-table-column prop="waterFee" label="水费(元)" width="100" />
|
|
|
+ <el-table-column prop="electricityFee" label="电费(元)" width="100" />
|
|
|
+ <el-table-column prop="propertyManagementFee" label="物业管理费(元)" width="120" />
|
|
|
+ <el-table-column prop="rentPeriod" label="租金收取时段" />
|
|
|
+ <el-table-column prop="handler" label="经手人" width="100" />
|
|
|
+ <el-table-column prop="remarks" label="备注" />
|
|
|
+ <el-table-column prop="createTime" label="创建时间" width="180">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ row.createTime ? new Date(row.createTime).toLocaleString() : '-' }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="150" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button :icon="Edit" size="small" @click="handleEdit(row)">编辑</el-button>
|
|
|
+ <el-button type="danger" :icon="Delete" size="small" @click="handleDelete(row.id)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- Pagination -->
|
|
|
+ <div class="mt-4 flex justify-end">
|
|
|
+ <el-pagination
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
+ :current-page="pageNum"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
+ :page-size="pageSize"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ :total="total"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Add/Edit Dialog -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="dialogVisible"
|
|
|
+ :title="dialogTitle"
|
|
|
+ width="600px"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ :close-on-press-escape="false"
|
|
|
+ >
|
|
|
+ <el-form :model="formData" ref="formRef" label-width="120px">
|
|
|
+ <el-form-item label="收据号" prop="receiptNumber" :rules="[{ required: true, message: '请输入收据号', trigger: 'blur' }]">
|
|
|
+ <el-input v-model="formData.receiptNumber" placeholder="请输入收据号" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="楼栋" prop="building" :rules="[{ required: true, message: '请输入楼栋', trigger: 'blur' }]">
|
|
|
+ <el-input v-model="formData.building" placeholder="请输入楼栋" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="房号" prop="roomNumber" :rules="[{ required: true, message: '请输入房号', trigger: 'blur' }]">
|
|
|
+ <el-input v-model="formData.roomNumber" placeholder="请输入房号" />
|
|
|
+ </el-form-item>
|
|
|
+ <!-- 找到以下这段 -->
|
|
|
+ <el-form-item label="收取时间" prop="collectionTime">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="formData.collectionTime"
|
|
|
+ type="datetime"
|
|
|
+ placeholder="选择日期和时间"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%;"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="房租(元)" prop="rent">
|
|
|
+ <el-input-number v-model="formData.rent" :min="0" :precision="2" :step="100" style="width: 100%;" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="押金(元)" prop="deposit">
|
|
|
+ <el-input-number v-model="formData.deposit" :min="0" :precision="2" :step="100" style="width: 100%;" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="水费(元)" prop="waterFee">
|
|
|
+ <el-input-number v-model="formData.waterFee" :min="0" :precision="2" :step="10" style="width: 100%;" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="电费(元)" prop="electricityFee">
|
|
|
+ <el-input-number v-model="formData.electricityFee" :min="0" :precision="2" :step="10" style="width: 100%;" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="物业管理费(元)" prop="propertyManagementFee">
|
|
|
+ <el-input-number v-model="formData.propertyManagementFee" :min="0" :precision="2" :step="10" style="width: 100%;" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="租金收取时段" prop="rentPeriod">
|
|
|
+ <el-input v-model="formData.rentPeriod" placeholder="请输入租金收取时段" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="经手人" prop="handler">
|
|
|
+ <el-input v-model="formData.handler" placeholder="请输入经手人" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="备注" prop="remarks">
|
|
|
+ <el-input v-model="formData.remarks" type="textarea" placeholder="请输入备注" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleConfirm">确定</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* Add any specific styles here if needed, otherwise UnoCSS handles most */
|
|
|
+</style>
|