feat: 项目管理列表搜索、新增/编辑

This commit is contained in:
Zzz1zz
2025-08-06 23:26:38 +08:00
parent 40a28f38cb
commit 52102d2e72
17 changed files with 843 additions and 68 deletions
+1
View File
@@ -65,6 +65,7 @@ module.exports = {
},
],
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
'no-param-reassign': 0,
'prefer-regex-literals': 0,
'import/no-extraneous-dependencies': 0,
+2 -2
View File
@@ -5,7 +5,7 @@ import {
CreateProjectParam,
ProjectDetailRes,
} from '@/types/business';
import { PaginationParams } from '@/types/comm';
import { ProjectPaginationParams } from '@/types/comm';
import { HttpResponse } from './interceptor';
// import type { RouteRecordNormalized } from 'vue-router';
// import { UserState } from '@/store/modules/user/types';
@@ -22,7 +22,7 @@ export function getAllProjectList(): Promise<AllProjectListRes> {
}
// 项目列表(分页)
export function getProjectList(
params: PaginationParams
params: ProjectPaginationParams
): Promise<ProjectListRes> {
return axios.post('project/getProjectList', params);
}
+74
View File
@@ -0,0 +1,74 @@
import axios from 'axios';
import {
CommonProvinceRes,
CommonCityRes,
CommonDistrictRes,
AllProjectIdNameListRes,
AllBuildingIdNameListRes,
AllFloorIdNameListRes,
AllInvoiceTypeIdNameListRes,
AllFilingTypeIdNameListRes,
UploadParam,
BankListRes,
} from '@/types/comm';
import { HttpResponse } from './interceptor';
export interface ProjectRecord {
id: string;
name: string;
type: string;
}
// 省份列表
export function getProvinceList(): Promise<CommonProvinceRes> {
return axios.get(`common/getProvinceList`);
}
// 城市列表
export function getCityListByProvinceId(id: number): Promise<CommonCityRes> {
return axios.get(`common/getCityList?province_id=${id}`);
}
// 区域列表
export function getDistrictListByCityId(
id: number
): Promise<CommonDistrictRes> {
return axios.get(`common/getDistrictList?city_id=${id}`);
}
// 全部项目(id,名称)列表
export function getAllProjectIdNameList(): Promise<AllProjectIdNameListRes> {
return axios.get(`common/getAllProjectList`);
}
// 全部楼栋(id,名称)列表(根据项目id查询)
export function getAllBuildingIdNameList(
id: number
): Promise<AllBuildingIdNameListRes> {
return axios.get(`common/getAllBuildingList?project_id=${id}`);
}
// 全部楼层(id,名称)列表(根据楼栋id查询)
export function getAllFloorIdNameList(
id: number
): Promise<AllFloorIdNameListRes> {
return axios.get(`common/getAllFloorList?building_id=${id}`);
}
// 发票类型列表
export function getInvoiceTypeList(): Promise<AllInvoiceTypeIdNameListRes> {
return axios.get(`common/getInvoiceTypeList`);
}
// 租客归档类型列表
export function getFilingTypeList(): Promise<AllFilingTypeIdNameListRes> {
return axios.get(`common/getFilingTypeList`);
}
// 上传文档
export function ossUpload(
params: UploadParam | FormData
): Promise<HttpResponse> {
return axios.post('common/ossUpload', params);
}
// 下载文档
export function ossDownload(key: string): Promise<HttpResponse> {
return axios.get(`common/ossDownload?key=${key}`);
}
// 银行列表
export function getBanksList(keyword: string): Promise<BankListRes> {
return axios.post('common/getBanksList', { keyword });
}
+115
View File
@@ -0,0 +1,115 @@
# ImageUpload 图片上传组件
一个可复用的图片上传组件,支持自定义上传类型、文件大小限制等功能。
## 功能特性
- 支持图片格式:JPG、PNG、GIF、WEBP
- 可自定义文件大小限制
- 支持上传进度显示
- 支持图片预览
- 支持多文件上传
- 上传成功后自动回传URL
## Props
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| modelValue | UploadFile[] | [] | 文件列表,支持v-model |
| uploadType | UploadType | - | 上传类型:1合同模版,2图片 |
| maxSize | number | 10 * 1024 * 1024 | 最大文件大小(字节) |
| maxCount | number | - | 最大上传数量 |
## Events
| 事件名 | 参数 | 说明 |
|--------|------|------|
| update:modelValue | (value: UploadFile[]) | 文件列表更新时触发 |
| upload-success | (file: UploadFile, response: UploadResponse) | 上传成功时触发 |
| upload-error | (error: Error) | 上传失败时触发 |
## 类型定义
```typescript
interface UploadFile {
uid: string;
name: string;
url?: string;
status: 'done' | 'uploading' | 'error';
percent?: number;
}
interface UploadResponse {
filename: string;
size: number;
url: string;
}
type UploadType = 1 | 2; // 1合同模版,2图片
```
## 使用示例
### 基础用法
```vue
<template>
<ImageUpload
v-model="images"
:upload-type="2"
@upload-success="handleUploadSuccess"
@upload-error="handleUploadError"
/>
</template>
<script setup>
import { ref } from 'vue';
import ImageUpload from '@/components/common/ImageUpload.vue';
const images = ref([]);
const handleUploadSuccess = (file, response) => {
console.log('上传成功:', response.url);
};
const handleUploadError = (error) => {
console.error('上传失败:', error.message);
};
</script>
```
### 自定义文件大小限制
```vue
<template>
<ImageUpload
v-model="images"
:upload-type="2"
:max-size="5 * 1024 * 1024"
@upload-success="handleUploadSuccess"
/>
</template>
```
### 在表单中使用
```vue
<template>
<a-form-item label="项目图片" field="images">
<ImageUpload
v-model="form.images"
:upload-type="2"
:max-size="10 * 1024 * 1024"
@upload-success="handleUploadSuccess"
@upload-error="handleUploadError"
/>
</a-form-item>
</template>
```
## 注意事项
1. 组件会自动处理文件验证,包括文件大小和格式检查
2. 上传成功后,文件对象会自动更新URL和状态
3. 支持v-model双向绑定,父组件可以直接操作文件列表
4. 上传类型必须从父组件传入,用于区分不同的上传场景
+165
View File
@@ -0,0 +1,165 @@
<template>
<a-upload
:list-type="listType"
:accept="'image/*'"
:custom-request="customRequest"
:file-list="fileList"
:multiple="maxCount > 1"
:limit="maxCount"
image-preview
@change="handleChange"
/>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { ossUpload } from '@/api/common';
import type { UploadType } from '@/types/comm';
interface UploadFile {
uid: string;
name: string;
url?: string;
status: 'done' | 'uploading' | 'error';
percent?: number;
}
interface UploadResponse {
filename: string;
size: number;
url: string;
}
const props = defineProps<{
modelValue: string[]; // 只暴露图片url数组
uploadType: UploadType;
maxSize?: number; // 默认10MB
maxCount?: number; // 最大上传数量,默认1(单图)
listType?: 'text' | 'picture' | 'picture-card'; // 上传样式
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: string[]): void;
(e: 'upload-success', url: string): void;
(e: 'upload-error', error: Error): void;
}>();
const fileList = ref<UploadFile[]>([]);
// 默认最大上传数:1(单图)
const maxCount = props.maxCount ?? 1;
// listType 默认值
const listType = props.listType ?? 'picture-card';
// 将外部 url[] 映射为 UploadFile[]
watch(
() => props.modelValue,
(newVal) => {
fileList.value = (newVal || []).map((url, index) => ({
uid: `${index}`,
name: `图片-${index + 1}`,
url,
status: 'done',
}));
},
{ immediate: true }
);
// 自定义上传请求
const customRequest = async (option: any) => {
const { onProgress, onError, onSuccess, fileItem } = option;
try {
const file = fileItem.file;
const maxSize = props.maxSize || 10 * 1024 * 1024;
const allowedTypes = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
];
// 文件大小校验
if (file.size > maxSize) {
onError(
new Error(`文件大小不能超过${Math.round(maxSize / 1024 / 1024)}MB`)
);
return;
}
// 文件类型校验
if (!allowedTypes.includes(file.type)) {
onError(new Error('只支持 JPG、PNG、GIF、WEBP 格式的图片'));
return;
}
// 模拟上传进度
let progress = 0;
const progressInterval = setInterval(() => {
progress += Math.random() * 30;
if (progress >= 90) {
clearInterval(progressInterval);
progress = 90;
}
onProgress({ percent: Math.round(progress) });
}, 200);
// 上传到服务器
const formData = new FormData();
formData.append('file', file);
formData.append('type', props.uploadType.toString());
const { code, data, message } = await ossUpload(formData);
clearInterval(progressInterval);
if (code === 200 && data) {
const response = data as UploadResponse;
fileItem.status = 'done';
fileItem.url = response.url;
onProgress({ percent: 100 });
onSuccess(response);
// 新的图片 url
let updatedUrls = [...props.modelValue];
if (maxCount === 1) {
updatedUrls = [response.url];
} else {
updatedUrls.push(response.url);
// 截取至 maxCount
updatedUrls = updatedUrls.slice(0, maxCount);
}
emit('update:modelValue', updatedUrls);
emit('upload-success', response.url);
} else {
const error = new Error(message || '上传失败');
onError(error);
emit('upload-error', error);
}
} catch (error) {
const err = error instanceof Error ? error : new Error('上传失败');
onError(err);
emit('upload-error', err);
}
};
// 同步 fileList(删除时触发)
const handleChange = (newFileList: UploadFile[]) => {
const validUrls = newFileList
.filter((f) => f.status === 'done' && f.url)
.map((f) => f.url!);
emit('update:modelValue', validUrls);
};
</script>
<style lang="less" scoped>
:deep(.arco-upload-list-picture-card) {
.arco-upload-list-item {
width: 104px;
height: 104px;
}
}
</style>
+2
View File
@@ -12,6 +12,7 @@ import {
import Chart from './chart/index.vue';
import Breadcrumb from './breadcrumb/index.vue';
import DataTable from './common/DataTable.vue';
import ImageUpload from './common/ImageUpload.vue';
// Manually introduce ECharts modules to reduce packing size
@@ -33,5 +34,6 @@ export default {
Vue.component('Chart', Chart);
Vue.component('Breadcrumb', Breadcrumb);
Vue.component('DataTable', DataTable);
Vue.component('ImageUpload', ImageUpload);
},
};
+9 -6
View File
@@ -9,10 +9,11 @@ import {
} from './comm';
export interface BuildingItem {
customId?: string;
id?: string; // 楼栋id
name: string; // 楼栋名称
up_floor?: number; // 地上层数
down_floor?: number; // 地下层数
up_floor?: number | null; // 地上层数
down_floor?: number | null; // 地下层数
}
export interface AllProjectsItem {
id: number; // 项目id
@@ -57,9 +58,9 @@ export interface CreateProjectParam {
id?: number;
type: ProjectType; // 项目类型:1写字楼,2商业综合体,3园区
name: string; // 楼栋名称
province_id: number; // 省份id
city_id: number; // 城市id
district_id: number; // 区域id
province_id: number | null; // 省份id
city_id: number | null; // 城市id
district_id: number | null; // 区域id
address: string; // 详细地址
images: string[]; // 图片数组
buildings: BuildingItem[]; // 楼栋数据
@@ -93,4 +94,6 @@ export interface ProjectInfo extends BaseEntity {
buildings: BuildingInfo[]; // 楼栋数组
}
export type ProjectDetailRes = ProjectInfo;
export type ProjectDetailRes = HttpResponse<{
project: ProjectInfo;
}>;
+64
View File
@@ -1,3 +1,4 @@
import { HttpResponse } from '@/api/interceptor';
// ==================== 通用类型定义 ====================
export interface BaseEntity {
id: number;
@@ -30,3 +31,66 @@ export type CommStatus = 1 | 2; // 1:是 2否
export type FeeManagementType = 1 | 2; // 费项属性:1收入费项,2支出费项
export type FeeType = 1 | 2 | 3 | 4; // 费项类型:1租赁类,2物业类,3特殊类,4其他类
export type ProjectType = 1 | 2 | 3; // 项目类型:1写字楼,2商业综合体,3园区
export type UploadType = 1 | 2; // 1合同模版,2图片
// ==================== 公共接口类型定义 ====================
export interface CommonProvinceItem {
ID?: number;
id?: number;
name?: string; // 省份
Name?: string; // 省份
code: string; // 编码
}
export interface CommonCityItem extends CommonProvinceItem {
ProvinceId: number; // 所属省份id
}
export interface CommonDistrictItem extends CommonProvinceItem {
CityId: number; // 所属城市id
}
// 省份
export type CommonProvinceRes = HttpResponse<{
provinceList?: CommonProvinceItem[];
}>;
// 城市
export type CommonCityRes = HttpResponse<{
cityList?: CommonCityItem[];
}>;
// 区域
export type CommonDistrictRes = HttpResponse<{
districtList?: CommonDistrictItem[];
}>;
// 全部项目(id、名称的列表)
export interface AllIdNameItem {
id: number;
name: string;
}
export interface ProjectPaginationParams extends PaginationParams {
type?: ProjectType[] | null;
province_ids?: number[] | null;
rent_rate_min?: number | null;
rent_rate_max?: number | null;
}
export type AllProjectIdNameListRes = HttpResponse<{
projectsList?: AllIdNameItem[];
}>;
export type AllBuildingIdNameListRes = HttpResponse<{
buildingList?: AllIdNameItem[];
}>;
export type AllFloorIdNameListRes = HttpResponse<{
floorList?: AllIdNameItem[];
}>;
export type AllInvoiceTypeIdNameListRes = HttpResponse<{
invoiceTypeList?: AllIdNameItem[];
}>;
export type AllFilingTypeIdNameListRes = HttpResponse<{
filingTypeList?: AllIdNameItem[];
}>;
export interface UploadParam {
file: any;
type: UploadType;
}
export type BankListRes = HttpResponse<{
banksList?: AllIdNameItem[];
}>;
+10
View File
@@ -34,5 +34,15 @@ export function debounce<T extends (...args: any[]) => any>(
}, delay);
};
}
export const generateRandomId = (length = 8) => {
const chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
const charsLength = chars.length;
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * charsLength));
}
return result;
};
export default null;
@@ -14,10 +14,10 @@
auto-label-width
layout="vertical"
>
<a-row>
<a-row :gutter="24">
<a-col :span="12">
<!-- 项目名称 -->
<a-form-item label="条款名称" field="name" required>
<a-form-item label="项目名称" field="name" required>
<a-input
v-model="form.name"
:placeholder="$t('please.enter', { placeholder: '项目名称' })"
@@ -26,7 +26,7 @@
</a-col>
<a-col :span="12">
<!-- 项目类型 -->
<a-form-item label="项目类型" field="name" required>
<a-form-item label="项目类型" field="type" required>
<a-select v-model="form.type" placeholder="请选择项目类型">
<a-option
v-for="item in projectTypeList"
@@ -40,16 +40,131 @@
</a-col>
</a-row>
<!-- 项目地址 -->
<a-form-item label="项目地址" field="type" required>
<!-- <a-space>
<a-select v-model="form.finance_fee_id" :style="{ width: '200px' }" placeholder="请选择省份...">
<a-option v-for="item in financeFeeList" :key="item.id" :value="item.id">
{{ item.name }}
</a-option>
</a-select>
<a-select v-model="form.finance_fee_periodicity" :style="{ width: '200px' }"
:options="financeFeeFeriodicityList" placeholder="请选择..." />
</a-space> -->
<a-form-item label="项目地址" field="address" required>
<a-row :gutter="12" wrap :style="{ width: '100%' }">
<a-col :span="12">
<a-select
v-model="form.province_id"
:style="{ width: '100%' }"
placeholder="请选择省份..."
@change="handleChangeProvince"
>
<a-option
v-for="item in provinceList"
:key="item.id"
:value="item.id"
>
{{ item.name }}
</a-option>
</a-select>
</a-col>
<a-col :span="12">
<a-row :gutter="12">
<a-col :span="12">
<a-select
v-model="form.city_id"
:disabled="cityList.length === 0"
:style="{ width: '100%' }"
placeholder="请选择城市..."
@change="handleChangeCity"
>
<a-option
v-for="item in cityList"
:key="item.id"
:value="item.id"
>
{{ item.name }}
</a-option>
</a-select>
</a-col>
<a-col :span="12">
<a-select
v-model="form.district_id"
:disabled="districtList.length === 0"
:style="{ width: '100%' }"
placeholder="请选择区域..."
>
<a-option
v-for="item in districtList"
:key="item.id"
:value="item.id"
>
{{ item.name }}
</a-option>
</a-select>
</a-col>
</a-row>
</a-col>
<a-col :span="24" style="margin-top: 10px">
<a-input
v-model="form.address"
:disabled="!form.district_id"
placeholder="请输入详细地址..."
/>
</a-col>
</a-row>
</a-form-item>
<a-form-item label="项目图片" field="images">
<ImageUpload
v-model="form.images"
:upload-type="2"
:max-count="5"
:list-type="'picture-card'"
/>
</a-form-item>
<a-form-item>
<template #label>
<div class="flex_center_between">
<p class="left">楼栋管理</p>
<div class="right">
<a-button type="outline" @click="handleAddBuilding">
<icon-plus /> 新增楼栋
</a-button>
</div>
</div>
</template>
<draggable
v-model="form.buildings"
:animation="200"
:item-key="props.isEdit ? 'id' : 'customId'"
@end="handleDragEnd"
>
<template #item="{ element }">
<a-row align="center" :gutter="12" style="margin-top: 10px">
<a-col :span="2">
<icon-unordered-list :size="24" />
</a-col>
<a-col :span="8">
<a-input
v-model="element.name"
placeholder="输入楼栋号(如A座)"
/>
</a-col>
<a-col :span="6">
<a-input-number
v-model="element.up_floor"
prepend="地面"
append="层"
/>
</a-col>
<a-col :span="6">
<a-input-number
v-model="element.down_floor"
prepend="地下"
append="层"
/>
</a-col>
<a-col :span="2">
<icon-delete
:size="16"
style="cursor: pointer"
@click="handleDelBuilding(element.customId)"
/>
</a-col>
<a-divider />
</a-row>
</template>
</draggable>
</a-form-item>
</a-form>
</a-drawer>
@@ -58,10 +173,25 @@
<script lang="ts" setup>
import { ref, watch, reactive, onMounted } from 'vue';
import type { FormInstance } from '@arco-design/web-vue';
// import { Message } from '@arco-design/web-vue';
import { Message } from '@arco-design/web-vue';
import { projectTypeList } from '@/utils/constant';
// import { CreateTermSettingParam, FeeManagement } from '@/types/setting';
// import { FeeManagementType } from '@/types/comm';
import {
CommonProvinceItem,
CommonCityItem,
CommonDistrictItem,
ProjectType,
} from '@/types/comm';
import {
getProvinceList,
getCityListByProvinceId,
getDistrictListByCityId,
} from '@/api/common';
import { createProject, updateProject } from '@/api/business';
import { CreateProjectParam } from '@/types/business';
import ImageUpload from '@/components/common/ImageUpload.vue';
import draggable from 'vuedraggable';
import { generateRandomId } from '@/utils';
const props = defineProps<{
visible: boolean;
isEdit: boolean;
@@ -71,34 +201,128 @@
(e: 'update:visible', val: boolean): void;
(e: 'submitted'): void;
}>();
const createEmptyBuilding = () => {
return {
customId: generateRandomId(),
name: '',
up_floor: null,
down_floor: null,
};
};
const isLoading = ref<boolean>(false);
// 表单数据
const formRef = ref<FormInstance>();
const defaultForm: any = {
name: '', // 名称
type: 1, // 条款类型:1租赁类,2物业类,3特殊类,4其他类
is_fee: 1, // 是否勾选费项:1是,2否
finance_fee_id: null, // 财务费项id(除保证金外
finance_fee_periodicity: 1, // 周期性:1周期性付款,2一次性付款
is_bond: 2, // 是否勾选保证金:1是,2否
finance_bond_fee_id: null, // 财务保证金费项id
is_incremental: 2, // 是否勾选递增项:1是,2否
is_discount: 2, // 是否勾选优惠项:1是,2否
is_late_fee: 2, // 是否勾选滞纳金:1是,2否
const defaultForm: CreateProjectParam = {
type: 1 as ProjectType, // 项目类型:1写字楼,2商业综合体,3园区
name: '', // 楼栋名称
province_id: null, // 省份id
city_id: null, // 城市id
district_id: null, // 区域id
address: '', // 详细地址
images: [], // 图片数组
buildings: [createEmptyBuilding()], // 楼栋数据
};
const form = reactive<any>({ ...defaultForm });
const form = reactive<CreateProjectParam>({ ...defaultForm });
// 校验规则
const rules = reactive({
name: [{ required: true, message: '请输入条款名称' }],
type: [{ required: true, message: '请选择条款类别' }],
name: [{ required: true, message: '请输入项目名称' }],
type: [{ required: true, message: '请选择项目类型' }],
address: [
{ required: true, message: '详细地址不能为空' },
{
'validator': (_, cb) => {
if (!form.province_id) {
cb('请选择项目地址所在省份');
} else if (!form.city_id) {
cb('请选择项目地址所在城市');
} else if (!form.district_id) {
cb('请选择项目地址所在区域');
} else {
cb();
}
},
'validate-trigger': 'blur',
},
],
});
const provinceList = ref<CommonProvinceItem[]>([]);
const cityList = ref<CommonCityItem[]>([]);
const districtList = ref<CommonDistrictItem[]>([]);
const getProvinceListData = async () => {
const { code, data } = await getProvinceList();
if (code === 200 && data) {
provinceList.value = Array.isArray(data?.provinceList)
? data?.provinceList
: [];
}
};
const getCityListByProvinceIdData = async (provinceId: number) => {
const { code, data } = await getCityListByProvinceId(provinceId);
if (code === 200 && data) {
cityList.value = Array.isArray(data?.cityList) ? data?.cityList : [];
}
};
const getDistrictListByCityIdData = async (cityId: number) => {
const { code, data } = await getDistrictListByCityId(cityId);
if (code === 200 && data) {
districtList.value = Array.isArray(data?.districtList)
? data?.districtList
: [];
}
};
const handleChangeProvince = async (id) => {
form.city_id = null;
await getCityListByProvinceIdData(id);
};
const handleChangeCity = async (id) => {
form.district_id = null;
await getDistrictListByCityIdData(id);
};
const handleAddBuilding = () => {
form.buildings.push(createEmptyBuilding());
};
const handleDelBuilding = (id: string) => {
if (form.buildings.length === 1) return;
const index = form.buildings.findIndex((b) => b.customId === id);
if (index !== -1) {
form.buildings.splice(index, 1);
}
};
watch(
() => props.visible,
(val) => {
async (val) => {
if (val) {
console.log(props.record, 'props.record');
if (props.isEdit && props.record) {
Object.assign(form, props.record);
const record = props.record;
if (record?.buildings?.length) {
record.buildings.forEach((element) => {
element.up_floor = element.upFloor;
element.down_floor = element.downFloor;
});
}
if (record?.imageUrls?.length) {
record.images = record?.imageUrls;
}
form.name = record.name;
Object.keys(form).forEach((key) => {
form[key] = record[key];
});
form.province_id = record.provinceId;
form.city_id = record.cityId;
form.district_id = record.districtId;
form.id = record.id;
// Object.assign(form, record);
if (form.province_id) {
await getCityListByProvinceIdData(form.province_id);
}
if (form.city_id) {
await getDistrictListByCityIdData(form.city_id);
}
} else {
Object.assign(form, defaultForm);
}
@@ -107,12 +331,45 @@
{ immediate: true }
);
const handleOk = async () => {};
const handleOk = async () => {
if (isLoading.value) return;
try {
isLoading.value = true;
const valid = await formRef.value.validate();
if (valid) return;
form.buildings = form.buildings.map((item) => {
return {
name: item.name,
up_floor: item.up_floor,
down_floor: item.down_floor,
id: item?.id,
};
});
const params = {
...form,
};
const res = props?.isEdit
? await updateProject(params)
: await createProject(params);
if (res?.code === 200) {
Message.success(res?.message);
emit('submitted');
emit('update:visible', false);
}
} catch (err) {
// 捕获异常,暂不处理
} finally {
isLoading.value = false;
}
};
const handleCancel = () => {
emit('update:visible', false);
};
onMounted(() => {});
const handleDragEnd = () => {};
onMounted(() => {
getProvinceListData();
});
</script>
<style lang="less" scoped>
+79 -24
View File
@@ -8,8 +8,10 @@
<div class="flex_center_between">
<a-space>
<a-input-search
v-model="queryParams.keyword"
:style="{ width: '320px' }"
:placeholder="t('please.enter', { placeholder: t('content') })"
@search="handleSearch"
/>
<a-button>
<template #icon>
@@ -23,7 +25,7 @@
</template>
{{ t('table.export') }}
</a-button>
<a-button type="primary">
<a-button type="primary" @click="handleAddProject">
<template #icon>
<icon-plus />
</template>
@@ -44,23 +46,37 @@
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
>
<!-- <template #type="{ record }">
<a-tag checkable :color="getTypeColor(record.type)">{{
getTypeText(record.type)
}}</a-tag>
<template #type="{ record }">
<a-tag checkable :color="getProjectTypeColor(record.type)">
{{ record.typeName }}
</a-tag>
</template>
<template #rate="{ record }">
<a-progress :percent="record.rate" color="green" :style="{ width: '100px' }" />
</template> -->
<template #operate>
<a-progress
:percent="Number(record.rentRate)"
color="green"
:style="{ width: '100px' }"
/>
</template>
<template #operate="{ record }">
<a-space>
<a-button>查看</a-button>
<a-button type="primary">编辑</a-button>
<a-button
:loading="isLoadingEdit"
type="primary"
@click="handleEditProject(record.id)"
>编辑</a-button
>
<a-button>更多</a-button>
</a-space>
</template>
</DataTable>
</a-card>
<projectFormDrawer
v-model:visible="showDrawer"
:is-edit="isEdit"
:record="currentProjectInfo"
/>
</div>
</template>
@@ -70,14 +86,18 @@
import { useI18n } from 'vue-i18n';
import type { TableColumnData } from '@arco-design/web-vue';
import useDataTable from '@/composables/useDataTable';
import { ProjectItem } from '@/types/business';
import { getProjectList } from '@/api/business';
import { ProjectItem, ProjectInfo } from '@/types/business';
import { getProjectList, getProjectDetail } from '@/api/business';
import { ProjectPaginationParams, ProjectType } from '@/types/comm';
import projectFormDrawer from './drawer/project-form-drawer.vue';
const { t } = useI18n();
const { loading } = useLoading(false);
const showDrawer = ref<boolean>(false);
const isLoadingEdit = ref<boolean>(false);
const isEdit = ref<boolean>(false);
const currentProjectInfo = ref<ProjectInfo | null>(null);
const projectList = ref<ProjectItem[]>([]);
const {
data: renderData,
columns: projectColumn,
@@ -100,28 +120,46 @@
const columns: TableColumnData[] = [
{ title: '项目信息', dataIndex: 'name', key: 'name' },
{ title: '项目类型', dataIndex: 'type', key: 'type', slotName: 'type' },
{ title: '管理面积', dataIndex: 'size', key: 'size' },
{ title: '可租面积', dataIndex: 'kezu', key: 'kezu' },
{ title: '出租率', dataIndex: 'rate', key: 'rate', slotName: 'rate' },
{ title: '房源数量', dataIndex: 'count', key: 'count' },
{ title: '创建时间', dataIndex: 'create_time', key: 'create_time' },
{ title: '管理面积', dataIndex: 'RentAreaStr', key: 'RentAreaStr' },
{ title: '可租面积', dataIndex: 'RoomAreaStr', key: 'RoomAreaStr' },
{
title: '出租率',
dataIndex: 'RentRateStr',
key: 'RentRateStr',
slotName: 'rate',
},
{ title: '房源数量', dataIndex: 'roomNum', key: 'roomNum' },
{ title: '创建时间', dataIndex: 'createdAt', key: 'createdAt' },
{ title: '操作', key: 'operate', slotName: 'operate', width: 200 },
];
const queryParams = ref<ProjectPaginationParams>({
page: current.value,
page_size: pageSize.value,
keyword: '',
type: null,
province_ids: null,
rent_rate_min: null,
rent_rate_max: null,
});
const getProjectListData = async () => {
const params = {
page: current.value,
page_size: pageSize.value,
};
setLoading(true);
const { code, data } = await getProjectList(params);
const { code, data } = await getProjectList(queryParams.value);
if (code === 200 && data) {
const { projectsList, page } = data;
projectList.value = Array.isArray(projectsList) ? projectsList : [];
setData(projectList.value);
setTotal(page?.total ? page.total : 0);
}
setLoading(false);
};
const getProjectDetailData = async (id: number) => {
isLoadingEdit.value = true;
const { code, data } = await getProjectDetail(id);
if (code === 200 && data) {
currentProjectInfo.value = data.project;
}
isLoadingEdit.value = false;
};
// 分页事件处理
const onPageChange = (page: number) => {
@@ -132,6 +170,23 @@
setPageSize(size);
pageSize.value = size;
};
const handleAddProject = () => {
isEdit.value = false;
currentProjectInfo.value = null;
showDrawer.value = true;
};
const handleEditProject = async (id: number) => {
isEdit.value = true;
await getProjectDetailData(id);
showDrawer.value = true;
};
const handleSearch = () => {
getProjectListData();
};
const getProjectTypeColor = (type: ProjectType) => {
const typeMap = { 1: 'green', 2: 'red', 3: 'orange' };
return typeMap[type];
};
onMounted(() => {
setColumns(columns);
getProjectListData();
@@ -109,6 +109,7 @@
// import { CreateCodeSettingParam } from '@/types/setting'
import draggable from 'vuedraggable';
const isLoading = ref<boolean>(false);
const props = defineProps<{
visible: boolean;
isEdit: boolean;
@@ -240,7 +241,9 @@
form.arrange = JSON.parse(JSON.stringify(arrangeList.value));
};
const handleOk = async () => {
if (isLoading.value) return;
try {
isLoading.value = true;
const valid = await formRef.value.validate();
if (valid) return;
// 同步编号排序
@@ -265,6 +268,8 @@
}
} catch (err) {
// 捕获异常,暂不处理
} finally {
isLoading.value = false;
}
};
@@ -120,7 +120,7 @@
(e: 'update:visible', val: boolean): void;
(e: 'submitted'): void;
}>();
const isLoading = ref<boolean>(false);
// 表单数据
const formRef = ref<FormInstance>();
const defaultForm: CreateTermSettingParam = {
@@ -182,7 +182,9 @@
};
const handleOk = async () => {
if (isLoading.value) return;
try {
isLoading.value = true;
const valid = await formRef.value.validate();
if (valid) return;
const { is_bond, is_fee, finance_bond_fee_id, finance_fee_id } = form;
@@ -208,6 +210,8 @@
}
} catch (err) {
// 捕获异常,暂不处理
} finally {
isLoading.value = false;
}
};
@@ -78,6 +78,7 @@
import { CreateFeeParams } from '@/types/setting';
import { feeTypeList } from '@/utils/constant';
const isLoading = ref<boolean>(false);
const props = defineProps<{
visible: boolean;
isEdit: boolean;
@@ -132,7 +133,9 @@
{ immediate: true }
);
const handleOk = async () => {
if (isLoading.value) return;
try {
isLoading.value = true;
const valid = await formRef.value.validate();
if (valid) return;
const res = props?.isEdit ? await updateFee(form) : await createFee(form);
@@ -143,6 +146,8 @@
}
} catch (err) {
// 捕获异常,暂不处理
} finally {
isLoading.value = false;
}
};
@@ -40,6 +40,7 @@
import { updateUserRole } from '@/api/setting';
import { UpdateUserRoleParam, RoleItem } from '@/types/setting';
const isLoading = ref<boolean>(false);
const props = defineProps<{
visible: boolean;
roleId: number | null;
@@ -76,7 +77,9 @@
{ immediate: true }
);
const handleOk = async () => {
if (isLoading.value) return;
try {
isLoading.value = true;
const valid = await formRef.value.validate();
if (valid) return;
const params = { ...form };
@@ -88,6 +91,8 @@
}
} catch (error) {
// 捕获异常,暂不处理
} finally {
isLoading.value = false;
}
};
@@ -40,6 +40,7 @@
import { updateRole, createRole } from '@/api/setting';
import { RoleFormParam, RoleItem } from '@/types/setting';
const isLoading = ref<boolean>(false);
const props = defineProps<{
visible: boolean;
isEdit: boolean;
@@ -92,7 +93,9 @@
{ immediate: true }
);
const handleOk = async () => {
if (isLoading.value) return;
try {
isLoading.value = true;
const valid = await formRef.value.validate();
if (valid) return;
const params = { ...form };
@@ -106,6 +109,8 @@
}
} catch (err) {
// 捕获异常,暂不处理
} finally {
isLoading.value = false;
}
};
@@ -114,6 +114,7 @@
import { createUser, updateUser } from '@/api/setting';
import { DepartmentItem, RoleItem, FormUserItem } from '@/types/setting';
const isLoading = ref<boolean>(false);
const props = defineProps<{
visible: boolean;
isEdit: boolean;
@@ -242,7 +243,9 @@
{ immediate: true }
);
const handleOk = async () => {
if (isLoading.value) return;
try {
isLoading.value = true;
const valid = await formRef.value.validate();
if (valid) return;
const params = { ...form };
@@ -255,6 +258,8 @@
emit('update:visible', false);
} catch (err) {
// 处理错误
} finally {
isLoading.value = false;
}
};