mirror of
http://183.237.82.114:10086/kilo/leaser_front.git
synced 2026-10-23 19:35:25 +00:00
feat: 空间管理 -> 房源列表开发
This commit is contained in:
parent
39ef0b7785
commit
a53cd0e8b8
15 changed files with 1637 additions and 28 deletions
|
|
@ -5,7 +5,11 @@ import {
|
||||||
CreateProjectParam,
|
CreateProjectParam,
|
||||||
ProjectDetailRes,
|
ProjectDetailRes,
|
||||||
} from '@/types/business';
|
} from '@/types/business';
|
||||||
import { ProjectPaginationParams, CommonCityRes } from '@/types/comm';
|
import {
|
||||||
|
ProjectPaginationParams,
|
||||||
|
CommonCityRes,
|
||||||
|
PaginationParams,
|
||||||
|
} from '@/types/comm';
|
||||||
import { HttpResponse } from './interceptor';
|
import { HttpResponse } from './interceptor';
|
||||||
// import type { RouteRecordNormalized } from 'vue-router';
|
// import type { RouteRecordNormalized } from 'vue-router';
|
||||||
// import { UserState } from '@/store/modules/user/types';
|
// import { UserState } from '@/store/modules/user/types';
|
||||||
|
|
@ -46,3 +50,43 @@ export function updateProject(
|
||||||
export function getProjectCityList(): Promise<CommonCityRes> {
|
export function getProjectCityList(): Promise<CommonCityRes> {
|
||||||
return axios.get(`project/getCityList`);
|
return axios.get(`project/getCityList`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------- 空间管理 -------------
|
||||||
|
// 创建房源
|
||||||
|
export function createRoom(params: any): Promise<HttpResponse> {
|
||||||
|
return axios.post('rooms/createRoom', params);
|
||||||
|
}
|
||||||
|
// 房源列表(分页)
|
||||||
|
export function getRoomList(params: PaginationParams): Promise<HttpResponse> {
|
||||||
|
return axios.post('rooms/getRoomList', params);
|
||||||
|
}
|
||||||
|
// 更新房源
|
||||||
|
export function updateRoom(params: any): Promise<HttpResponse> {
|
||||||
|
return axios.post('rooms/updateRoom', params);
|
||||||
|
}
|
||||||
|
// 删除房源
|
||||||
|
export function delRoom(id: number): Promise<HttpResponse> {
|
||||||
|
return axios.get(`rooms/delRoom?id=${id}`);
|
||||||
|
}
|
||||||
|
// 房源详情
|
||||||
|
export function getRoom(id: number): Promise<HttpResponse> {
|
||||||
|
return axios.get(`rooms/getRoom?id=${id}`);
|
||||||
|
}
|
||||||
|
// 修改房源状态 status:1招商 2保留 3下架 4在租
|
||||||
|
export function changeRoomStatus(params: any): Promise<HttpResponse> {
|
||||||
|
return axios.post('rooms/changeRoomStatus', params);
|
||||||
|
}
|
||||||
|
// 房源获取头部楼栋tags
|
||||||
|
export function getTopBuildingTagsByProjectId(
|
||||||
|
id: number
|
||||||
|
): Promise<HttpResponse> {
|
||||||
|
return axios.get(`roomOver/getTopBuildingTags?project_id=${id}`);
|
||||||
|
}
|
||||||
|
// 房源剖视-数据统计
|
||||||
|
export function getStatisticsData(params: any): Promise<HttpResponse> {
|
||||||
|
return axios.post('roomOver/getStatisticsData', params);
|
||||||
|
}
|
||||||
|
// 房源剖视-房源列表
|
||||||
|
export function getRoomOverList(params: any): Promise<HttpResponse> {
|
||||||
|
return axios.post('roomOver/getRoomList', params);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,3 +42,30 @@ export const VALIDATION_RULES = {
|
||||||
ID_CARD:
|
ID_CARD:
|
||||||
/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
|
/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// 房源状态
|
||||||
|
//状态:1招商 2保留 3下架 4在租
|
||||||
|
export const HOUSE_STATUS = [
|
||||||
|
{ id: 1, text: '招商' },
|
||||||
|
{ id: 2, text: '保留' },
|
||||||
|
{ id: 3, text: '下架' },
|
||||||
|
{ id: 4, text: '在租' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 价格单位选项
|
||||||
|
export const PRICE_UNITS = [
|
||||||
|
{ value: 1, text: '元/㎡*月' },
|
||||||
|
{ value: 2, text: '元/㎡*天' },
|
||||||
|
{ value: 3, text: '元/天' },
|
||||||
|
{ value: 4, text: '元/月' },
|
||||||
|
{ value: 5, text: '元/年' },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
// 价格单位映射表
|
||||||
|
export const PRICE_UNIT_MAP = new Map([
|
||||||
|
[1, '元/㎡*月'],
|
||||||
|
[2, '元/㎡*天'],
|
||||||
|
[3, '元/天'],
|
||||||
|
[4, '元/月'],
|
||||||
|
[5, '元/年'],
|
||||||
|
]);
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ export default {
|
||||||
'content': '内容',
|
'content': '内容',
|
||||||
'table.filter': '筛选',
|
'table.filter': '筛选',
|
||||||
'table.export': '导出',
|
'table.export': '导出',
|
||||||
|
'table.import': '导入',
|
||||||
'watch-detail': '查看详情',
|
'watch-detail': '查看详情',
|
||||||
'menu.business.project': '项目管理',
|
'menu.business.project': '项目管理',
|
||||||
'menu.business.space': '空间管理',
|
'menu.business.space': '空间管理',
|
||||||
|
|
@ -54,6 +55,7 @@ export default {
|
||||||
'menu.business.tenant-list': '租客列表',
|
'menu.business.tenant-list': '租客列表',
|
||||||
'menu.business.tenant.contacts': '租客联系人',
|
'menu.business.tenant.contacts': '租客联系人',
|
||||||
'add-project': '新建项目',
|
'add-project': '新建项目',
|
||||||
|
'edit-project': '编辑项目',
|
||||||
'menu.setting.user': '用户设置',
|
'menu.setting.user': '用户设置',
|
||||||
'menu.setting.user-list': '用户列表',
|
'menu.setting.user-list': '用户列表',
|
||||||
'menu.setting.role': '角色权限',
|
'menu.setting.role': '角色权限',
|
||||||
|
|
@ -123,6 +125,8 @@ export default {
|
||||||
'setting.contract': '合同设置',
|
'setting.contract': '合同设置',
|
||||||
'setting.finance': '财务设置',
|
'setting.finance': '财务设置',
|
||||||
'setting.data-dictionary': '数据字典',
|
'setting.data-dictionary': '数据字典',
|
||||||
|
'add-house': '新建房源',
|
||||||
|
'edit-house': '编辑房源',
|
||||||
|
|
||||||
...localeSettings,
|
...localeSettings,
|
||||||
...localeMessageBox,
|
...localeMessageBox,
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ const BUSINESS: AppRouteRecordRaw = {
|
||||||
component: () =>
|
component: () =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "business-space" */
|
/* webpackChunkName: "business-space" */
|
||||||
'@/views/business/space/house-view.vue'
|
'@/views/business/space/house-view/index.vue'
|
||||||
),
|
),
|
||||||
meta: {
|
meta: {
|
||||||
locale: 'menu.business.house-view',
|
locale: 'menu.business.house-view',
|
||||||
|
|
@ -71,7 +71,7 @@ const BUSINESS: AppRouteRecordRaw = {
|
||||||
component: () =>
|
component: () =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "business-space" */
|
/* webpackChunkName: "business-space" */
|
||||||
'@/views/business/space/house-list.vue'
|
'@/views/business/space/house-list/index.vue'
|
||||||
),
|
),
|
||||||
meta: {
|
meta: {
|
||||||
locale: 'menu.business.house-list',
|
locale: 'menu.business.house-list',
|
||||||
|
|
|
||||||
|
|
@ -114,3 +114,20 @@ export interface ProjectInfo extends BaseEntity {
|
||||||
export type ProjectDetailRes = HttpResponse<{
|
export type ProjectDetailRes = HttpResponse<{
|
||||||
project: ProjectItem;
|
project: ProjectItem;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
// 创建房源参数
|
||||||
|
export interface CreateHouseParam {
|
||||||
|
id?: number; // 编辑时需要
|
||||||
|
projectId: number; // 项目id
|
||||||
|
buildingId: number; // 楼栋id
|
||||||
|
floorId: number; // 楼层id
|
||||||
|
name: string; // 房源名称
|
||||||
|
rentArea: number | string; // 计租面积
|
||||||
|
price: number | string; // 价格
|
||||||
|
priceType: number; // 价格类型
|
||||||
|
area: number | string; // 面积
|
||||||
|
useArea: number | string; // 使用面积
|
||||||
|
status: number; // 状态
|
||||||
|
objectKeys: string[]; // 图片数组
|
||||||
|
tags: number[]; // 标签数组
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<a-drawer
|
<a-drawer
|
||||||
:width="670"
|
:width="670"
|
||||||
:visible="visible"
|
:visible="visible"
|
||||||
:title="isEdit ? '编辑条款设置' : '新增条款设置'"
|
:title="isEdit ? $t('edit-project') : $t('add-project')"
|
||||||
unmount-on-close
|
unmount-on-close
|
||||||
@ok="handleOk"
|
@ok="handleOk"
|
||||||
@cancel="handleCancel"
|
@cancel="handleCancel"
|
||||||
|
|
@ -374,7 +374,7 @@
|
||||||
emit('update:visible', false);
|
emit('update:visible', false);
|
||||||
};
|
};
|
||||||
const handleDragEnd = () => {
|
const handleDragEnd = () => {
|
||||||
console.log('handleDragEnd');
|
// 拖拽结束处理
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getProvinceListData();
|
getProvinceListData();
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button>
|
<a-button>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-download />
|
<icon-export />
|
||||||
</template>
|
</template>
|
||||||
{{ t('table.export') }}
|
{{ t('table.export') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|
@ -184,7 +184,11 @@
|
||||||
!edit
|
!edit
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
edit ? (isLoadingEdit.value = true) : (isLoadingDetail.value = true);
|
if (edit) {
|
||||||
|
isLoadingEdit.value = true;
|
||||||
|
} else {
|
||||||
|
isLoadingDetail.value = true;
|
||||||
|
}
|
||||||
const { code, data } = await getProjectDetail(id);
|
const { code, data } = await getProjectDetail(id);
|
||||||
if (code === 200 && data) {
|
if (code === 200 && data) {
|
||||||
currentProjectInfo.value = data.project;
|
currentProjectInfo.value = data.project;
|
||||||
|
|
@ -194,7 +198,7 @@
|
||||||
};
|
};
|
||||||
const handleWatchHouse = async (id: number) => {
|
const handleWatchHouse = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
console.log(id, 'is id');
|
// TODO: 实现查看房源功能
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 处理错误
|
// 处理错误
|
||||||
}
|
}
|
||||||
|
|
@ -207,7 +211,7 @@
|
||||||
getProjectListData();
|
getProjectListData();
|
||||||
};
|
};
|
||||||
const handleDel = (id: number) => {
|
const handleDel = (id: number) => {
|
||||||
console.log(id, 'iiii');
|
// TODO: 实现删除功能
|
||||||
};
|
};
|
||||||
// 分页事件处理
|
// 分页事件处理
|
||||||
const onPageChange = (page: number) => {
|
const onPageChange = (page: number) => {
|
||||||
|
|
|
||||||
409
src/views/business/space/drawer/house-form-drawer.vue
Normal file
409
src/views/business/space/drawer/house-form-drawer.vue
Normal file
|
|
@ -0,0 +1,409 @@
|
||||||
|
<template>
|
||||||
|
<a-drawer
|
||||||
|
:width="670"
|
||||||
|
:visible="visible"
|
||||||
|
:title="isEdit ? $t('edit-house') : $t('add-house')"
|
||||||
|
unmount-on-close
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
auto-label-width
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="12">
|
||||||
|
<!-- 房源名称 -->
|
||||||
|
<a-form-item label="房源名称" field="name" required>
|
||||||
|
<a-input
|
||||||
|
v-model="form.name"
|
||||||
|
:placeholder="$t('please.enter', { placeholder: '房源名称' })"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<!-- 房源状态 -->
|
||||||
|
<a-form-item label="房源状态" field="status" required>
|
||||||
|
<a-select v-model="form.status" placeholder="请选择房源状态">
|
||||||
|
<a-option
|
||||||
|
v-for="item in HOUSE_STATUS"
|
||||||
|
:key="item.id"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<!-- 所属项目 -->
|
||||||
|
<a-form-item label="所属项目" field="projectId" required>
|
||||||
|
<a-select
|
||||||
|
v-model="form.projectId"
|
||||||
|
placeholder="请选择所属项目"
|
||||||
|
@change="handleProjectChange"
|
||||||
|
>
|
||||||
|
<a-option
|
||||||
|
v-for="item in allProjetList"
|
||||||
|
:key="item.id"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<!-- 所属楼栋/楼层 -->
|
||||||
|
<a-form-item label="所属楼栋/楼层" field="buildingId" required>
|
||||||
|
<a-row :gutter="8">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-select
|
||||||
|
v-model="form.buildingId"
|
||||||
|
placeholder="请选择所属楼栋"
|
||||||
|
:disabled="!form.projectId"
|
||||||
|
@change="handleBuildingChange"
|
||||||
|
>
|
||||||
|
<a-option
|
||||||
|
v-for="item in allBuildingList"
|
||||||
|
:key="item.id"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-select
|
||||||
|
v-model="form.floorId"
|
||||||
|
placeholder="请选择所属楼层"
|
||||||
|
:disabled="!form.buildingId"
|
||||||
|
>
|
||||||
|
<a-option
|
||||||
|
v-for="item in allFloorList"
|
||||||
|
:key="item.id"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="计租面积" field="rentArea" required>
|
||||||
|
<a-input-number
|
||||||
|
v-model="form.rentArea"
|
||||||
|
append="㎡"
|
||||||
|
:placeholder="$t('please.enter', { placeholder: '房源计租面积' })"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="招商价格" field="price">
|
||||||
|
<a-input-number
|
||||||
|
v-model="form.price"
|
||||||
|
:placeholder="
|
||||||
|
$t('please.enter', { placeholder: '房源计租招商价格' })
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<a-select v-model="form.priceType" style="width: 100px">
|
||||||
|
<a-option
|
||||||
|
v-for="item in PRICE_UNITS"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>{{ item.text }}</a-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="建筑面积(可选)" field="area">
|
||||||
|
<a-input
|
||||||
|
v-model="form.area"
|
||||||
|
append="㎡"
|
||||||
|
:placeholder="$t('please.enter', { placeholder: '建筑面积' })"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="使用面积(可选)" field="useArea">
|
||||||
|
<a-input
|
||||||
|
v-model="form.useArea"
|
||||||
|
append="㎡"
|
||||||
|
:placeholder="$t('please.enter', { placeholder: '使用面积' })"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-form-item label="标签(可选)" field="tags">
|
||||||
|
<a-select v-model="form.tags" multiple placeholder="请选择标签">
|
||||||
|
<a-option
|
||||||
|
v-for="item in roomTagsList"
|
||||||
|
:key="item.id"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="房源图片(可选)" field="objectKeys">
|
||||||
|
<ImageUpload
|
||||||
|
v-model:object-keys="form.objectKeys"
|
||||||
|
v-model:signed-put-u-r-l="form.signedPutURL"
|
||||||
|
:upload-type="2"
|
||||||
|
:max-count="5"
|
||||||
|
list-type="picture-card"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<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 { CreateHouseParam } from '@/types/business';
|
||||||
|
import ImageUpload from '@/components/common/ImageUpload.vue';
|
||||||
|
import { HOUSE_STATUS, PRICE_UNITS } from '@/constants';
|
||||||
|
import {
|
||||||
|
getAllProjectIdNameList,
|
||||||
|
getAllBuildingIdNameList,
|
||||||
|
getAllFloorIdNameList,
|
||||||
|
} from '@/api/common';
|
||||||
|
import { getRoomTagList } from '@/api/setting';
|
||||||
|
import { createRoom, updateRoom } from '@/api/business';
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
isEdit: boolean;
|
||||||
|
record?: any | null;
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
(e: 'submitted'): void;
|
||||||
|
}>();
|
||||||
|
const isLoading = ref<boolean>(false);
|
||||||
|
// 表单数据
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const allProjetList = ref<any>([]);
|
||||||
|
const allBuildingList = ref<any>([]);
|
||||||
|
const allFloorList = ref<any>([]);
|
||||||
|
const roomTagsList = ref<any[]>([]);
|
||||||
|
const defaultForm: CreateHouseParam = {
|
||||||
|
projectId: null as any,
|
||||||
|
buildingId: null as any,
|
||||||
|
floorId: null as any,
|
||||||
|
name: '', // 房源名称
|
||||||
|
rentArea: '', // 计租面积
|
||||||
|
price: '', // 价格
|
||||||
|
priceType: 1, // 价格类型
|
||||||
|
area: '', // 面积
|
||||||
|
useArea: '', // 使用面积
|
||||||
|
status: 1, // 状态
|
||||||
|
objectKeys: [], // 图片数组
|
||||||
|
tags: [], // 标签数组
|
||||||
|
};
|
||||||
|
// 扩展表单类型,包含 ImageUpload 需要的 signedPutURL(不提交到后端)
|
||||||
|
const form = reactive<CreateHouseParam & { signedPutURL: string[] }>({
|
||||||
|
...defaultForm,
|
||||||
|
signedPutURL: [],
|
||||||
|
});
|
||||||
|
// 校验规则
|
||||||
|
const rules = reactive({
|
||||||
|
name: [{ required: true, message: '请输入房源名称' }],
|
||||||
|
projectId: [{ required: true, message: '请选择项目' }],
|
||||||
|
buildingId: [{ required: true, message: '请选择楼栋' }],
|
||||||
|
floorId: [{ required: true, message: '请选择楼层' }],
|
||||||
|
status: [{ required: true, message: '请选择房源状态' }],
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
async (val) => {
|
||||||
|
if (val) {
|
||||||
|
if (props.isEdit && props.record) {
|
||||||
|
const record = props.record;
|
||||||
|
// 回显数据 - 使用 Object.assign 批量赋值,处理特殊字段转换
|
||||||
|
Object.assign(form, {
|
||||||
|
id: record.id,
|
||||||
|
projectId: record.projectId,
|
||||||
|
buildingId: record.buildingId,
|
||||||
|
floorId: record.floorId,
|
||||||
|
name: record.name || '',
|
||||||
|
rentArea: record.rentArea || 0,
|
||||||
|
price: record.price || 0,
|
||||||
|
priceType:
|
||||||
|
PRICE_UNITS.find((i) => i.text === record.priceType)?.value ?? 1,
|
||||||
|
area: record.area || 0,
|
||||||
|
useArea: record.useArea || 0,
|
||||||
|
status: record.status || 1,
|
||||||
|
objectKeys: record.objectKeys || record.imageUrls || [],
|
||||||
|
tags: [
|
||||||
|
...new Set(record?.tags?.map((i) => i.id).filter(Boolean) || []),
|
||||||
|
],
|
||||||
|
signedPutURL: record.signedPutURL || [],
|
||||||
|
});
|
||||||
|
// 编辑模式下,需要加载对应的楼栋和楼层列表
|
||||||
|
if (record.projectId) {
|
||||||
|
await getAllBuildingListData(record.projectId);
|
||||||
|
if (record.buildingId) {
|
||||||
|
await getAllFloorListData(record.buildingId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Object.assign(form, { ...defaultForm, signedPutURL: [] });
|
||||||
|
// 清空楼栋和楼层列表
|
||||||
|
allBuildingList.value = [];
|
||||||
|
allFloorList.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
const handleOk = async () => {
|
||||||
|
if (isLoading.value) return;
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
const valid = await formRef.value.validate();
|
||||||
|
if (valid) return;
|
||||||
|
// 手动验证楼层字段(因为楼栋和楼层在同一个form-item中)
|
||||||
|
if (!form.floorId) {
|
||||||
|
formRef.value.setFields({
|
||||||
|
floorId: { status: 'error', message: '请选择楼层' },
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 构建提交参数,排除 signedPutURL(仅用于前端显示)
|
||||||
|
// 将面积、价格相关字段的空值转为0
|
||||||
|
const params: CreateHouseParam = {
|
||||||
|
projectId: form.projectId,
|
||||||
|
buildingId: form.buildingId,
|
||||||
|
floorId: form.floorId,
|
||||||
|
name: form.name,
|
||||||
|
rentArea: form.rentArea || 0,
|
||||||
|
price: form.price || 0,
|
||||||
|
priceType: form.priceType,
|
||||||
|
area: form.area || 0,
|
||||||
|
useArea: form.useArea || 0,
|
||||||
|
status: form.status,
|
||||||
|
objectKeys: form.objectKeys,
|
||||||
|
tags: form.tags,
|
||||||
|
};
|
||||||
|
// 如果是编辑模式,需要包含 id
|
||||||
|
if (props.isEdit && form.id) {
|
||||||
|
params.id = form.id;
|
||||||
|
}
|
||||||
|
// 调用创建/更新房源的 API
|
||||||
|
const res = props.isEdit
|
||||||
|
? await updateRoom(params)
|
||||||
|
: await createRoom(params);
|
||||||
|
if (res?.code === 200) {
|
||||||
|
Message.success(
|
||||||
|
res?.message || (props.isEdit ? '更新成功' : '创建成功')
|
||||||
|
);
|
||||||
|
// 清空表单数据
|
||||||
|
Object.assign(form, { ...defaultForm, signedPutURL: [] });
|
||||||
|
allBuildingList.value = [];
|
||||||
|
allFloorList.value = [];
|
||||||
|
formRef.value?.clearValidate();
|
||||||
|
emit('submitted');
|
||||||
|
emit('update:visible', false);
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
Message.error(err?.message || (props.isEdit ? '更新失败' : '创建失败'));
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
async function getAllProjectListData() {
|
||||||
|
try {
|
||||||
|
const { code, data } = await getAllProjectIdNameList();
|
||||||
|
if (code === 200) {
|
||||||
|
allProjetList.value = data?.projectsList || [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// console.error('获取项目列表失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAllBuildingListData(projectId: number) {
|
||||||
|
try {
|
||||||
|
const { code, data } = await getAllBuildingIdNameList(projectId);
|
||||||
|
if (code === 200) {
|
||||||
|
allBuildingList.value = data?.buildingList || [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// console.error('获取楼栋列表失败:', error);
|
||||||
|
allBuildingList.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAllFloorListData(buildingId: number) {
|
||||||
|
try {
|
||||||
|
const { code, data } = await getAllFloorIdNameList(buildingId);
|
||||||
|
if (code === 200) {
|
||||||
|
allFloorList.value = data?.floorList || [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// console.error('获取楼层列表失败:', error);
|
||||||
|
allFloorList.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 项目选择变化处理
|
||||||
|
const handleProjectChange = async (projectId: number) => {
|
||||||
|
// 清空楼栋和楼层选择
|
||||||
|
form.buildingId = null as any;
|
||||||
|
form.floorId = null as any;
|
||||||
|
allBuildingList.value = [];
|
||||||
|
allFloorList.value = [];
|
||||||
|
// 如果选择了项目,加载楼栋列表
|
||||||
|
if (projectId) {
|
||||||
|
await getAllBuildingListData(projectId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 楼栋选择变化处理
|
||||||
|
const handleBuildingChange = async (buildingId: number) => {
|
||||||
|
// 清空楼层选择
|
||||||
|
form.floorId = null as any;
|
||||||
|
allFloorList.value = [];
|
||||||
|
// 如果选择了楼栋,加载楼层列表
|
||||||
|
if (buildingId) {
|
||||||
|
await getAllFloorListData(buildingId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getAllRoomTagListData = async () => {
|
||||||
|
try {
|
||||||
|
const { code, data } = await getRoomTagList();
|
||||||
|
if (code === 200 && data) {
|
||||||
|
roomTagsList.value = data?.roomTagsList;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始化数据加载
|
||||||
|
getAllProjectListData();
|
||||||
|
getAllRoomTagListData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(label.arco-form-item-label) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
155
src/views/business/space/drawer/house-info-drawer.vue
Normal file
155
src/views/business/space/drawer/house-info-drawer.vue
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
<template>
|
||||||
|
<a-drawer
|
||||||
|
:width="520"
|
||||||
|
:visible="visible"
|
||||||
|
title="房源详情"
|
||||||
|
:footer="false"
|
||||||
|
unmount-on-close
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{{
|
||||||
|
`${detailInfo?.name} -
|
||||||
|
${detailInfo?.projectName}${detailInfo?.buildingName}${detailInfo?.floorName}`
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<div class="mt-[10px]">
|
||||||
|
<a-tag :color="getTagsColor(detailInfo?.status)">{{
|
||||||
|
detailInfo?.statusStr
|
||||||
|
}}</a-tag>
|
||||||
|
<a-tag class="mx-[10px]">{{ detailInfo?.rentArea }}㎡</a-tag>
|
||||||
|
<a-tag>{{ `${detailInfo?.price}${detailInfo?.priceType}` }}</a-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <a-button>
|
||||||
|
<template #icon>
|
||||||
|
<icon-edit />
|
||||||
|
</template>
|
||||||
|
{{ $t('edit') }}
|
||||||
|
</a-button> -->
|
||||||
|
</div>
|
||||||
|
<div class="mt-[20px]">
|
||||||
|
<a-tabs v-model:active-key="currentTab" class="">
|
||||||
|
<a-tab-pane key="1">
|
||||||
|
<template #title> <icon-calendar /> 基本信息 </template>
|
||||||
|
<a-alert type="warning">房源空置 xx 天</a-alert>
|
||||||
|
<a-descriptions bordered class="mt-[10px]" :column="2">
|
||||||
|
<a-descriptions-item label="房源编码" :span="1">
|
||||||
|
{{ detailInfo?.code || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="房源名称" :span="1">
|
||||||
|
{{ detailInfo?.name || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="所属项目" :span="1">
|
||||||
|
{{ detailInfo?.projectName || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="所属楼栋/楼层" :span="1">
|
||||||
|
<span>{{ detailInfo?.buildingName || '-' }}</span>
|
||||||
|
<span>*</span>
|
||||||
|
<span>{{ detailInfo?.floorName || '-' }}</span>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="计租面积" :span="1">
|
||||||
|
{{
|
||||||
|
Number(detailInfo?.rentArea) > 0
|
||||||
|
? `${detailInfo?.rentArea}㎡`
|
||||||
|
: '-'
|
||||||
|
}}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="招商价格" :span="1">
|
||||||
|
{{
|
||||||
|
Number(detailInfo?.price) > 0
|
||||||
|
? `${detailInfo?.rentArea}${detailInfo?.priceType}`
|
||||||
|
: '-'
|
||||||
|
}}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="建筑面积" :span="1">
|
||||||
|
{{ Number(detailInfo?.area) > 0 ? `${detailInfo?.area}㎡` : '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="使用面积" :span="1">
|
||||||
|
{{
|
||||||
|
Number(detailInfo?.useArea) > 0
|
||||||
|
? `${detailInfo?.useArea}㎡`
|
||||||
|
: '-'
|
||||||
|
}}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="标签" :span="2">
|
||||||
|
<template v-if="detailInfo?.tags?.length">
|
||||||
|
<a-tag
|
||||||
|
v-for="item in detailInfo?.tags"
|
||||||
|
:key="item.id || item.name"
|
||||||
|
class="mr-[5px] last:mr-0"
|
||||||
|
>
|
||||||
|
{{ item.name }}</a-tag
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
<template v-else> - </template>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="房源图片" :span="2">
|
||||||
|
<template v-if="detailInfo?.signedPutURL?.length">
|
||||||
|
<img
|
||||||
|
v-for="(item, index) in detailInfo?.signedPutURL"
|
||||||
|
:key="index"
|
||||||
|
:src="item"
|
||||||
|
alt=""
|
||||||
|
class="mt-[5px] last:mr-0 w-[120px] h-[80px] object-contain"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else> - </template>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="2">
|
||||||
|
<template #title> <icon-clock-circle /> 合同信息 </template>
|
||||||
|
合同信息与租客信息 待开发
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="3">
|
||||||
|
<template #title> <icon-user /> 租客信息 </template>
|
||||||
|
合同信息与租客信息 待开发
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</div>
|
||||||
|
</a-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { watch, onMounted, ref } from 'vue';
|
||||||
|
// import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
projectId: number | null;
|
||||||
|
detailInfo: any;
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
}>();
|
||||||
|
const currentTab = ref<string>('1');
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
async (val) => {
|
||||||
|
// 抽屉显示状态变化
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
const getTagsColor = (status) => {
|
||||||
|
const colorMap = {
|
||||||
|
1: 'orangered',
|
||||||
|
2: 'orange',
|
||||||
|
3: 'red',
|
||||||
|
4: 'green',
|
||||||
|
};
|
||||||
|
return colorMap[status];
|
||||||
|
};
|
||||||
|
onMounted(() => {});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(label.arco-form-item-label) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
624
src/views/business/space/drawer/house-list-filter-drawer.vue
Normal file
624
src/views/business/space/drawer/house-list-filter-drawer.vue
Normal file
|
|
@ -0,0 +1,624 @@
|
||||||
|
<template>
|
||||||
|
<a-drawer
|
||||||
|
:width="420"
|
||||||
|
:visible="visible"
|
||||||
|
title="筛选"
|
||||||
|
unmount-on-close
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form :model="filterForm" layout="vertical">
|
||||||
|
<!-- 项目(仅显示) -->
|
||||||
|
<a-form-item label="项目">
|
||||||
|
<span class="w-full bg-[#f2f3f5] h-[30px] px-[5px] leading-[30px]">{{
|
||||||
|
projectName
|
||||||
|
}}</span>
|
||||||
|
<!-- <a-input :value="projectName" disabled /> -->
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 楼栋(多选) -->
|
||||||
|
<a-form-item label="楼栋">
|
||||||
|
<a-select
|
||||||
|
v-model="filterForm.buildingIds"
|
||||||
|
multiple
|
||||||
|
placeholder="请选择楼栋"
|
||||||
|
:max-tag-count="3"
|
||||||
|
>
|
||||||
|
<a-option :value="ALL_BUILDING_ID">所有楼栋</a-option>
|
||||||
|
<a-option
|
||||||
|
v-for="item in buildingList"
|
||||||
|
:key="item.id"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 楼层(多选) -->
|
||||||
|
<a-form-item label="楼层">
|
||||||
|
<a-select
|
||||||
|
v-model="filterForm.floorIds"
|
||||||
|
multiple
|
||||||
|
placeholder="请选择楼层"
|
||||||
|
:max-tag-count="3"
|
||||||
|
>
|
||||||
|
<a-option :value="ALL_FLOOR_ID">所有楼层</a-option>
|
||||||
|
<a-option v-for="item in floorList" :key="item.id" :value="item.id">
|
||||||
|
{{ item.name }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 房源状态(多选) -->
|
||||||
|
<a-form-item label="房源状态">
|
||||||
|
<a-select
|
||||||
|
v-model="filterForm.statusIds"
|
||||||
|
multiple
|
||||||
|
placeholder="请选择房源状态"
|
||||||
|
:max-tag-count="3"
|
||||||
|
>
|
||||||
|
<a-option
|
||||||
|
v-for="item in HOUSE_STATUS"
|
||||||
|
:key="item.id"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 计租面积(滑块) -->
|
||||||
|
<a-form-item label="计租面积(㎡)">
|
||||||
|
<a-slider
|
||||||
|
v-model="filterForm.rentArea"
|
||||||
|
range
|
||||||
|
:min="rentAreaMin"
|
||||||
|
:max="rentAreaMax"
|
||||||
|
:marks="rentAreaMarks"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 标签(多选) -->
|
||||||
|
<a-form-item label="标签">
|
||||||
|
<a-select
|
||||||
|
v-model="filterForm.tagIds"
|
||||||
|
multiple
|
||||||
|
placeholder="请选择标签"
|
||||||
|
:max-tag-count="3"
|
||||||
|
>
|
||||||
|
<a-option v-for="item in tagList" :key="item.id" :value="item.id">
|
||||||
|
{{ item.name }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, computed, onMounted, nextTick } from 'vue';
|
||||||
|
import { HOUSE_STATUS } from '@/constants';
|
||||||
|
import { getRoomTagList } from '@/api/setting';
|
||||||
|
|
||||||
|
// 特殊值用于表示"所有"选项(使用负数避免与真实ID冲突)
|
||||||
|
const ALL_BUILDING_ID = -1;
|
||||||
|
const ALL_FLOOR_ID = -2;
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
roomList?: any[]; // 房源列表数据,用于提取筛选选项
|
||||||
|
projectName?: string; // 项目名称(仅显示)
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
(
|
||||||
|
e: 'confirm',
|
||||||
|
payload: {
|
||||||
|
buildingIds: number[] | null;
|
||||||
|
floorIds: number[] | null;
|
||||||
|
statusIds: number[] | null;
|
||||||
|
rentArea: number[];
|
||||||
|
tagIds: number[] | null;
|
||||||
|
filteredList: any[]; // 筛选后的列表数据
|
||||||
|
}
|
||||||
|
): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 保存的筛选状态
|
||||||
|
const savedFilterState = ref<{
|
||||||
|
buildingIds: number[];
|
||||||
|
floorIds: number[];
|
||||||
|
statusIds: number[];
|
||||||
|
rentArea: number[];
|
||||||
|
tagIds: number[];
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
// 筛选表单数据
|
||||||
|
const filterForm = ref({
|
||||||
|
buildingIds: [] as number[],
|
||||||
|
floorIds: [] as number[],
|
||||||
|
statusIds: [] as number[],
|
||||||
|
rentArea: [0, 0] as number[],
|
||||||
|
tagIds: [] as number[],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 楼栋列表(从房源列表中提取唯一值)
|
||||||
|
const buildingList = computed(() => {
|
||||||
|
if (!props.roomList || props.roomList.length === 0) return [];
|
||||||
|
const buildingMap = new Map();
|
||||||
|
props.roomList.forEach((room) => {
|
||||||
|
if (room.buildingId && room.buildingName) {
|
||||||
|
if (!buildingMap.has(room.buildingId)) {
|
||||||
|
buildingMap.set(room.buildingId, {
|
||||||
|
id: room.buildingId,
|
||||||
|
name: room.buildingName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Array.from(buildingMap.values());
|
||||||
|
});
|
||||||
|
|
||||||
|
// 楼层列表(从房源列表中提取唯一值)
|
||||||
|
const floorList = computed(() => {
|
||||||
|
if (!props.roomList || props.roomList.length === 0) return [];
|
||||||
|
const floorMap = new Map();
|
||||||
|
props.roomList.forEach((room) => {
|
||||||
|
if (room.floorId && room.floorName) {
|
||||||
|
if (!floorMap.has(room.floorId)) {
|
||||||
|
floorMap.set(room.floorId, {
|
||||||
|
id: room.floorId,
|
||||||
|
name: room.floorName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Array.from(floorMap.values());
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计租面积范围
|
||||||
|
const rentAreaMin = computed(() => {
|
||||||
|
if (!props.roomList || props.roomList.length === 0) return 0;
|
||||||
|
const areas = props.roomList
|
||||||
|
.map((room) => Number(room.rentArea))
|
||||||
|
.filter((area) => !isNaN(area) && area > 0);
|
||||||
|
return areas.length > 0 ? Math.min(...areas) : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const rentAreaMax = computed(() => {
|
||||||
|
if (!props.roomList || props.roomList.length === 0) return 100;
|
||||||
|
const areas = props.roomList
|
||||||
|
.map((room) => Number(room.rentArea))
|
||||||
|
.filter((area) => !isNaN(area) && area > 0);
|
||||||
|
return areas.length > 0 ? Math.max(...areas) : 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
const rentAreaMarks = computed(() => {
|
||||||
|
return {
|
||||||
|
[rentAreaMin.value]: `${rentAreaMin.value}㎡`,
|
||||||
|
[rentAreaMax.value]: `${rentAreaMax.value}㎡`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 标签列表
|
||||||
|
const tagList = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 获取标签列表
|
||||||
|
const getTagListData = async () => {
|
||||||
|
try {
|
||||||
|
const { code, data } = await getRoomTagList();
|
||||||
|
if (code === 200 && data) {
|
||||||
|
tagList.value = data?.roomTagsList || [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化筛选表单
|
||||||
|
const initFilterForm = () => {
|
||||||
|
const min = rentAreaMin.value || 0;
|
||||||
|
const max = rentAreaMax.value || 100;
|
||||||
|
|
||||||
|
// 如果有保存的状态,恢复它(但要过滤掉不存在的ID)
|
||||||
|
if (savedFilterState.value) {
|
||||||
|
const allBuildingIds = buildingList.value.map((b) => b.id);
|
||||||
|
const allFloorIds = floorList.value.map((f) => f.id);
|
||||||
|
|
||||||
|
// 如果保存的状态中包含"所有"选项,直接使用
|
||||||
|
let buildingIds = savedFilterState.value.buildingIds;
|
||||||
|
let floorIds = savedFilterState.value.floorIds;
|
||||||
|
|
||||||
|
// 如果保存的状态中不包含"所有"选项,检查是否所有ID都被选中
|
||||||
|
if (!buildingIds.includes(ALL_BUILDING_ID)) {
|
||||||
|
const validBuildingIds = buildingIds.filter((id) =>
|
||||||
|
allBuildingIds.includes(id)
|
||||||
|
);
|
||||||
|
// 如果选中的数量等于总数,则使用"所有"选项
|
||||||
|
if (
|
||||||
|
validBuildingIds.length === allBuildingIds.length &&
|
||||||
|
allBuildingIds.length > 0
|
||||||
|
) {
|
||||||
|
buildingIds = [ALL_BUILDING_ID];
|
||||||
|
} else if (validBuildingIds.length > 0) {
|
||||||
|
buildingIds = validBuildingIds;
|
||||||
|
} else {
|
||||||
|
buildingIds = [ALL_BUILDING_ID];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!floorIds.includes(ALL_FLOOR_ID)) {
|
||||||
|
const validFloorIds = floorIds.filter((id) => allFloorIds.includes(id));
|
||||||
|
// 如果选中的数量等于总数,则使用"所有"选项
|
||||||
|
if (
|
||||||
|
validFloorIds.length === allFloorIds.length &&
|
||||||
|
allFloorIds.length > 0
|
||||||
|
) {
|
||||||
|
floorIds = [ALL_FLOOR_ID];
|
||||||
|
} else if (validFloorIds.length > 0) {
|
||||||
|
floorIds = validFloorIds;
|
||||||
|
} else {
|
||||||
|
floorIds = [ALL_FLOOR_ID];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterForm.value = {
|
||||||
|
buildingIds,
|
||||||
|
floorIds,
|
||||||
|
statusIds: [...savedFilterState.value.statusIds],
|
||||||
|
rentArea: [...savedFilterState.value.rentArea],
|
||||||
|
tagIds: [...savedFilterState.value.tagIds],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 否则初始化,楼栋和楼层默认选中"所有"
|
||||||
|
filterForm.value = {
|
||||||
|
buildingIds: [ALL_BUILDING_ID],
|
||||||
|
floorIds: [ALL_FLOOR_ID],
|
||||||
|
statusIds: [],
|
||||||
|
rentArea: [min, max],
|
||||||
|
tagIds: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 筛选列表数据
|
||||||
|
const filterRoomList = () => {
|
||||||
|
if (!props.roomList || props.roomList.length === 0) return [];
|
||||||
|
|
||||||
|
let filtered = [...props.roomList];
|
||||||
|
|
||||||
|
// 筛选楼栋(如果选择了"所有楼栋",则不过滤)
|
||||||
|
if (
|
||||||
|
filterForm.value.buildingIds.length > 0 &&
|
||||||
|
!filterForm.value.buildingIds.includes(ALL_BUILDING_ID)
|
||||||
|
) {
|
||||||
|
filtered = filtered.filter((room) =>
|
||||||
|
filterForm.value.buildingIds.includes(room.buildingId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选楼层(如果选择了"所有楼层",则不过滤)
|
||||||
|
if (
|
||||||
|
filterForm.value.floorIds.length > 0 &&
|
||||||
|
!filterForm.value.floorIds.includes(ALL_FLOOR_ID)
|
||||||
|
) {
|
||||||
|
filtered = filtered.filter((room) =>
|
||||||
|
filterForm.value.floorIds.includes(room.floorId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选状态
|
||||||
|
if (filterForm.value.statusIds.length > 0) {
|
||||||
|
filtered = filtered.filter((room) =>
|
||||||
|
filterForm.value.statusIds.includes(room.status)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选计租面积
|
||||||
|
const [minArea, maxArea] = filterForm.value.rentArea;
|
||||||
|
filtered = filtered.filter((room) => {
|
||||||
|
const area = Number(room.rentArea);
|
||||||
|
return !isNaN(area) && area >= minArea && area <= maxArea;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 筛选标签
|
||||||
|
if (filterForm.value.tagIds.length > 0) {
|
||||||
|
filtered = filtered.filter((room) => {
|
||||||
|
if (!room.tags || !Array.isArray(room.tags)) return false;
|
||||||
|
const roomTagIds = room.tags.map((tag: any) => tag.id || tag);
|
||||||
|
return filterForm.value.tagIds.some((id) => roomTagIds.includes(id));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
// 处理"所有"选项:如果同时选择了"所有"和具体选项,只保留"所有"
|
||||||
|
let finalBuildingIds = [...filterForm.value.buildingIds];
|
||||||
|
let finalFloorIds = [...filterForm.value.floorIds];
|
||||||
|
|
||||||
|
if (
|
||||||
|
finalBuildingIds.includes(ALL_BUILDING_ID) &&
|
||||||
|
finalBuildingIds.length > 1
|
||||||
|
) {
|
||||||
|
finalBuildingIds = [ALL_BUILDING_ID];
|
||||||
|
}
|
||||||
|
if (finalFloorIds.includes(ALL_FLOOR_ID) && finalFloorIds.length > 1) {
|
||||||
|
finalFloorIds = [ALL_FLOOR_ID];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存当前筛选状态(保存处理后的值)
|
||||||
|
savedFilterState.value = {
|
||||||
|
buildingIds: finalBuildingIds,
|
||||||
|
floorIds: finalFloorIds,
|
||||||
|
statusIds: [...filterForm.value.statusIds],
|
||||||
|
rentArea: [...filterForm.value.rentArea],
|
||||||
|
tagIds: [...filterForm.value.tagIds],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 标准化ID数组:如果选择了"所有"选项,则返回null(表示全部)
|
||||||
|
const normalizeIds = (ids: number[], allOptionId: number) => {
|
||||||
|
if (ids.length === 0) return null;
|
||||||
|
// 如果选择了"所有"选项,则返回null
|
||||||
|
if (ids.includes(allOptionId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用处理后的值进行筛选
|
||||||
|
const originalBuildingIds = filterForm.value.buildingIds;
|
||||||
|
const originalFloorIds = filterForm.value.floorIds;
|
||||||
|
filterForm.value.buildingIds = finalBuildingIds;
|
||||||
|
filterForm.value.floorIds = finalFloorIds;
|
||||||
|
const filteredList = filterRoomList();
|
||||||
|
// 恢复原始值(用于显示)
|
||||||
|
filterForm.value.buildingIds = originalBuildingIds;
|
||||||
|
filterForm.value.floorIds = originalFloorIds;
|
||||||
|
|
||||||
|
emit('confirm', {
|
||||||
|
buildingIds: normalizeIds(finalBuildingIds, ALL_BUILDING_ID),
|
||||||
|
floorIds: normalizeIds(finalFloorIds, ALL_FLOOR_ID),
|
||||||
|
statusIds:
|
||||||
|
filterForm.value.statusIds.length === 0
|
||||||
|
? null
|
||||||
|
: filterForm.value.statusIds,
|
||||||
|
rentArea: filterForm.value.rentArea,
|
||||||
|
tagIds:
|
||||||
|
filterForm.value.tagIds.length === 0 ? null : filterForm.value.tagIds,
|
||||||
|
filteredList, // 筛选后的列表数据
|
||||||
|
});
|
||||||
|
handleCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 用于防止 watch 循环更新的标志
|
||||||
|
const isUpdatingBuilding = ref(false);
|
||||||
|
const isUpdatingFloor = ref(false);
|
||||||
|
|
||||||
|
// 监听楼栋选择变化
|
||||||
|
watch(
|
||||||
|
() => filterForm.value.buildingIds,
|
||||||
|
(newVal, oldVal) => {
|
||||||
|
if (isUpdatingBuilding.value) return;
|
||||||
|
if (!oldVal || oldVal.length === 0) return; // 初始化时跳过
|
||||||
|
|
||||||
|
const isAllSelected = newVal.includes(ALL_BUILDING_ID);
|
||||||
|
const wasAllOnly = oldVal.length === 1 && oldVal[0] === ALL_BUILDING_ID;
|
||||||
|
const hasSpecificSelected = newVal.some((id) => id !== ALL_BUILDING_ID);
|
||||||
|
const specificIds = newVal.filter((id) => id !== ALL_BUILDING_ID);
|
||||||
|
const allBuildingIds = buildingList.value.map((b) => b.id);
|
||||||
|
|
||||||
|
// 情况1:用户选择了"所有"选项(从非"所有"状态切换到包含"所有")
|
||||||
|
// 判断:旧值不包含"所有"或包含多个值,新值包含"所有"且有其他选项
|
||||||
|
if (isAllSelected && hasSpecificSelected && !wasAllOnly) {
|
||||||
|
isUpdatingBuilding.value = true;
|
||||||
|
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||||
|
nextTick(() => {
|
||||||
|
isUpdatingBuilding.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况2:用户从"所有"切换到选择具体选项(新值不包含"所有",但旧值只有"所有")
|
||||||
|
if (!isAllSelected && wasAllOnly && hasSpecificSelected) {
|
||||||
|
// v-model已经处理了,不需要额外操作,允许用户选择
|
||||||
|
// 检查是否选中的数量等于总数
|
||||||
|
if (
|
||||||
|
specificIds.length === allBuildingIds.length &&
|
||||||
|
allBuildingIds.every((id) => specificIds.includes(id))
|
||||||
|
) {
|
||||||
|
// 选中的数量等于总数,自动切换为"所有"
|
||||||
|
isUpdatingBuilding.value = true;
|
||||||
|
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||||
|
nextTick(() => {
|
||||||
|
isUpdatingBuilding.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况3:用户选择了具体选项,但当前值还包含"所有"(可能是从"所有"状态添加了具体选项)
|
||||||
|
if (hasSpecificSelected && isAllSelected && wasAllOnly) {
|
||||||
|
// 移除"所有"选项,只保留具体选项
|
||||||
|
isUpdatingBuilding.value = true;
|
||||||
|
filterForm.value.buildingIds = specificIds;
|
||||||
|
nextTick(() => {
|
||||||
|
isUpdatingBuilding.value = false;
|
||||||
|
// 检查是否选中的数量等于总数
|
||||||
|
if (
|
||||||
|
specificIds.length === allBuildingIds.length &&
|
||||||
|
allBuildingIds.every((id) => specificIds.includes(id))
|
||||||
|
) {
|
||||||
|
isUpdatingBuilding.value = true;
|
||||||
|
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||||
|
nextTick(() => {
|
||||||
|
isUpdatingBuilding.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况4:用户一个个勾选具体选项,当选中数量等于总数时,自动切换为"所有"
|
||||||
|
if (
|
||||||
|
!isAllSelected &&
|
||||||
|
hasSpecificSelected &&
|
||||||
|
specificIds.length === allBuildingIds.length
|
||||||
|
) {
|
||||||
|
// 检查是否所有选项都被选中
|
||||||
|
if (allBuildingIds.every((id) => specificIds.includes(id))) {
|
||||||
|
isUpdatingBuilding.value = true;
|
||||||
|
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||||
|
nextTick(() => {
|
||||||
|
isUpdatingBuilding.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听楼层选择变化
|
||||||
|
watch(
|
||||||
|
() => filterForm.value.floorIds,
|
||||||
|
(newVal, oldVal) => {
|
||||||
|
if (isUpdatingFloor.value) return;
|
||||||
|
if (!oldVal || oldVal.length === 0) return; // 初始化时跳过
|
||||||
|
|
||||||
|
const isAllSelected = newVal.includes(ALL_FLOOR_ID);
|
||||||
|
const wasAllOnly = oldVal.length === 1 && oldVal[0] === ALL_FLOOR_ID;
|
||||||
|
const hasSpecificSelected = newVal.some((id) => id !== ALL_FLOOR_ID);
|
||||||
|
const specificIds = newVal.filter((id) => id !== ALL_FLOOR_ID);
|
||||||
|
const allFloorIds = floorList.value.map((f) => f.id);
|
||||||
|
|
||||||
|
// 情况1:用户选择了"所有"选项(从非"所有"状态切换到包含"所有")
|
||||||
|
// 判断:旧值不包含"所有"或包含多个值,新值包含"所有"且有其他选项
|
||||||
|
if (isAllSelected && hasSpecificSelected && !wasAllOnly) {
|
||||||
|
isUpdatingFloor.value = true;
|
||||||
|
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||||
|
nextTick(() => {
|
||||||
|
isUpdatingFloor.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况2:用户从"所有"切换到选择具体选项(新值不包含"所有",但旧值只有"所有")
|
||||||
|
if (!isAllSelected && wasAllOnly && hasSpecificSelected) {
|
||||||
|
// v-model已经处理了,不需要额外操作,允许用户选择
|
||||||
|
// 检查是否选中的数量等于总数
|
||||||
|
if (
|
||||||
|
specificIds.length === allFloorIds.length &&
|
||||||
|
allFloorIds.every((id) => specificIds.includes(id))
|
||||||
|
) {
|
||||||
|
// 选中的数量等于总数,自动切换为"所有"
|
||||||
|
isUpdatingFloor.value = true;
|
||||||
|
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||||
|
nextTick(() => {
|
||||||
|
isUpdatingFloor.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况3:用户选择了具体选项,但当前值还包含"所有"(可能是从"所有"状态添加了具体选项)
|
||||||
|
if (hasSpecificSelected && isAllSelected && wasAllOnly) {
|
||||||
|
// 移除"所有"选项,只保留具体选项
|
||||||
|
isUpdatingFloor.value = true;
|
||||||
|
filterForm.value.floorIds = specificIds;
|
||||||
|
nextTick(() => {
|
||||||
|
isUpdatingFloor.value = false;
|
||||||
|
// 检查是否选中的数量等于总数
|
||||||
|
if (
|
||||||
|
specificIds.length === allFloorIds.length &&
|
||||||
|
allFloorIds.every((id) => specificIds.includes(id))
|
||||||
|
) {
|
||||||
|
isUpdatingFloor.value = true;
|
||||||
|
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||||
|
nextTick(() => {
|
||||||
|
isUpdatingFloor.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况4:用户一个个勾选具体选项,当选中数量等于总数时,自动切换为"所有"
|
||||||
|
if (
|
||||||
|
!isAllSelected &&
|
||||||
|
hasSpecificSelected &&
|
||||||
|
specificIds.length === allFloorIds.length
|
||||||
|
) {
|
||||||
|
// 检查是否所有选项都被选中
|
||||||
|
if (allFloorIds.every((id) => specificIds.includes(id))) {
|
||||||
|
isUpdatingFloor.value = true;
|
||||||
|
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||||
|
nextTick(() => {
|
||||||
|
isUpdatingFloor.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听抽屉打开,初始化数据
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
// 延迟初始化,确保 buildingList 和 floorList 已计算
|
||||||
|
setTimeout(() => {
|
||||||
|
initFilterForm();
|
||||||
|
}, 0);
|
||||||
|
if (tagList.value.length === 0) {
|
||||||
|
getTagListData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听楼栋和楼层列表变化,如果当前没有保存状态且列表为空,则默认选中"所有"
|
||||||
|
watch(
|
||||||
|
[buildingList, floorList],
|
||||||
|
() => {
|
||||||
|
if (props.visible && !savedFilterState.value) {
|
||||||
|
// 如果楼栋列表为空,默认选中"所有楼栋"
|
||||||
|
if (filterForm.value.buildingIds.length === 0) {
|
||||||
|
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||||
|
}
|
||||||
|
// 如果楼层列表为空,默认选中"所有楼层"
|
||||||
|
if (filterForm.value.floorIds.length === 0) {
|
||||||
|
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getTagListData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(label.arco-form-item-label) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-value {
|
||||||
|
margin-top: 8px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-slider-mark) {
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>house-list</div>
|
|
||||||
</template>
|
|
||||||
338
src/views/business/space/house-list/index.vue
Normal file
338
src/views/business/space/house-list/index.vue
Normal file
|
|
@ -0,0 +1,338 @@
|
||||||
|
<template>
|
||||||
|
<div class="business_project_wrap">
|
||||||
|
<Breadcrumb :items="['menu.business', 'menu.business.house-list']" />
|
||||||
|
<a-card class="general-card full_height">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title_between">
|
||||||
|
<p class="title">{{ t('menu.business.house-list') }}</p>
|
||||||
|
<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 @click="handleFilter">
|
||||||
|
<template #icon>
|
||||||
|
<icon-filter />
|
||||||
|
</template>
|
||||||
|
{{ t('table.filter') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button>
|
||||||
|
<template #icon>
|
||||||
|
<icon-export />
|
||||||
|
</template>
|
||||||
|
{{ t('table.export') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button>
|
||||||
|
<template #icon>
|
||||||
|
<icon-download />
|
||||||
|
</template>
|
||||||
|
{{ t('table.import') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" @click="handleAddHouse">
|
||||||
|
<template #icon>
|
||||||
|
<icon-plus />
|
||||||
|
</template>
|
||||||
|
{{ t('add-house') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 使用新的 DataTable 组件 -->
|
||||||
|
<DataTable
|
||||||
|
:data="renderData"
|
||||||
|
:columns="projectColumn"
|
||||||
|
:loading="loading"
|
||||||
|
:total="total"
|
||||||
|
:current="current"
|
||||||
|
:page-size="pageSize"
|
||||||
|
@page-change="onPageChange"
|
||||||
|
@page-size-change="onPageSizeChange"
|
||||||
|
>
|
||||||
|
<template #tags="{ record }">
|
||||||
|
<template v-if="record.tags?.length">
|
||||||
|
<a-tag
|
||||||
|
v-for="item in record.tags"
|
||||||
|
:key="item.id"
|
||||||
|
class="mr-[5px] first:mr-0"
|
||||||
|
>
|
||||||
|
{{ item.name }}</a-tag
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
<span v-else> - </span>
|
||||||
|
</template>
|
||||||
|
<template #status="{ record }">
|
||||||
|
<a-tag :color="getTagsColor(record?.status)">{{
|
||||||
|
record?.statusStr
|
||||||
|
}}</a-tag>
|
||||||
|
</template>
|
||||||
|
<template #operate="{ record }">
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="handleDetail(record.id)">查看</a-button>
|
||||||
|
<a-popover trigger="click" position="bottom">
|
||||||
|
<a-button>{{ $t('more') }}</a-button>
|
||||||
|
<template #content>
|
||||||
|
<a-space direction="vertical">
|
||||||
|
<a-button @click="handleEditProject(record.id)">
|
||||||
|
编辑房源
|
||||||
|
</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:ok-text="$t('cancel')"
|
||||||
|
:cancel-text="$t('delete')"
|
||||||
|
:ok-button-props="{ type: 'outline' }"
|
||||||
|
:cancel-button-props="{
|
||||||
|
type: 'primary',
|
||||||
|
status: 'danger',
|
||||||
|
}"
|
||||||
|
content="确定删除该房源?"
|
||||||
|
type="error"
|
||||||
|
@cancel="handleDel(record?.id)"
|
||||||
|
>
|
||||||
|
<a-button status="danger">删除项目</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</a-card>
|
||||||
|
<houseFormDrawer
|
||||||
|
v-model:visible="showDrawer"
|
||||||
|
:is-edit="isEdit"
|
||||||
|
:record="currentRecordInfo"
|
||||||
|
@submitted="getHouseListData"
|
||||||
|
/>
|
||||||
|
<houseInfoDrawer
|
||||||
|
v-model:visible="showProjectInfoDrawer"
|
||||||
|
:project-id="currentProjectId"
|
||||||
|
:detail-info="currentRecordInfo"
|
||||||
|
/>
|
||||||
|
<houseListFilterDrawer
|
||||||
|
v-model:visible="showListFilterDrawer"
|
||||||
|
:room-list="projectList"
|
||||||
|
:project-name="currentProjectName"
|
||||||
|
@confirm="handleFilterConfirm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import useLoading from '@/hooks/loading';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import type { TableColumnData } from '@arco-design/web-vue';
|
||||||
|
import useDataTable from '@/composables/useDataTable';
|
||||||
|
import { getRoomList, getRoom, delRoom } from '@/api/business';
|
||||||
|
import { ProjectPaginationParams } from '@/types/comm';
|
||||||
|
import houseFormDrawer from '../drawer/house-form-drawer.vue';
|
||||||
|
import houseInfoDrawer from '../drawer/house-info-drawer.vue';
|
||||||
|
import houseListFilterDrawer from '../drawer/house-list-filter-drawer.vue';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { loading } = useLoading(false);
|
||||||
|
const showDrawer = ref<boolean>(false);
|
||||||
|
const showProjectInfoDrawer = ref<boolean>(false);
|
||||||
|
const showListFilterDrawer = ref<boolean>(false);
|
||||||
|
const currentProjectId = ref<number | null>(null);
|
||||||
|
const isLoadingEdit = ref<boolean>(false);
|
||||||
|
const isLoadingDetail = ref<boolean>(false);
|
||||||
|
const isEdit = ref<boolean>(false);
|
||||||
|
const currentRecordInfo = ref<any | null>(null);
|
||||||
|
const projectList = ref<any[]>([]);
|
||||||
|
const currentProjectName = ref<string>(''); // 当前项目名称
|
||||||
|
const {
|
||||||
|
data: renderData,
|
||||||
|
columns: projectColumn,
|
||||||
|
total,
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
setData,
|
||||||
|
setColumns,
|
||||||
|
setTotal,
|
||||||
|
setCurrent,
|
||||||
|
setPageSize,
|
||||||
|
setLoading,
|
||||||
|
} = useDataTable({
|
||||||
|
initialPage: 1,
|
||||||
|
initialPageSize: 10,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通用空值处理函数
|
||||||
|
const renderEmpty = (value: any) => value ?? '-';
|
||||||
|
|
||||||
|
// 列配置
|
||||||
|
const columns: TableColumnData[] = [
|
||||||
|
{
|
||||||
|
title: '房源名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
render: ({ record }) => renderEmpty(record.name),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属项目',
|
||||||
|
dataIndex: 'projectName',
|
||||||
|
key: 'projectName',
|
||||||
|
render: ({ record }) => renderEmpty(record.projectName),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属楼栋',
|
||||||
|
dataIndex: 'buildingName',
|
||||||
|
key: 'buildingName',
|
||||||
|
render: ({ record }) => renderEmpty(record.buildingName),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '楼层',
|
||||||
|
dataIndex: 'floorName',
|
||||||
|
key: 'floorName',
|
||||||
|
render: ({ record }) => renderEmpty(record.floorName),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '计租面积(㎡)',
|
||||||
|
dataIndex: 'rentArea',
|
||||||
|
key: 'rentArea',
|
||||||
|
render: ({ record }) => renderEmpty(record.rentArea),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '租金(实时)',
|
||||||
|
dataIndex: 'price',
|
||||||
|
key: 'price',
|
||||||
|
render: ({ record }) => (Number(record.price) <= 0 ? '-' : record?.price),
|
||||||
|
},
|
||||||
|
{ title: '标签', dataIndex: 'tags', key: 'tags', slotName: 'tags' },
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'statusStr',
|
||||||
|
key: 'statusStr',
|
||||||
|
slotName: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '合同',
|
||||||
|
dataIndex: 'contractStatusStr',
|
||||||
|
key: 'contractStatusStr',
|
||||||
|
render: ({ record }) => renderEmpty(record.contractStatusStr),
|
||||||
|
},
|
||||||
|
{ title: '操作', key: 'operate', slotName: 'operate', width: 200 },
|
||||||
|
];
|
||||||
|
const queryParams = ref<ProjectPaginationParams>({
|
||||||
|
page: current.value,
|
||||||
|
page_size: pageSize.value,
|
||||||
|
keyword: '',
|
||||||
|
});
|
||||||
|
const getHouseListData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const { code, data } = await getRoomList(queryParams.value);
|
||||||
|
if (code === 200 && data) {
|
||||||
|
const { list, page } = data as {
|
||||||
|
list?: any[];
|
||||||
|
page?: { total?: number };
|
||||||
|
};
|
||||||
|
projectList.value = Array.isArray(list) ? list : [];
|
||||||
|
// 提取项目名称(从第一条数据中获取,假设所有房源属于同一项目)
|
||||||
|
if (projectList.value.length > 0 && projectList.value[0].projectName) {
|
||||||
|
currentProjectName.value = projectList.value[0].projectName;
|
||||||
|
}
|
||||||
|
setData(projectList.value);
|
||||||
|
setTotal(page?.total ? page.total : 0);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
const getRoomData = async (id: number, edit: boolean) => {
|
||||||
|
if (currentRecordInfo.value && currentRecordInfo.value?.id === id && !edit)
|
||||||
|
return;
|
||||||
|
if (edit) {
|
||||||
|
isLoadingEdit.value = true;
|
||||||
|
} else {
|
||||||
|
isLoadingDetail.value = true;
|
||||||
|
}
|
||||||
|
const { code, data } = await getRoom(id);
|
||||||
|
if (code === 200 && data) {
|
||||||
|
currentRecordInfo.value = (data as { room?: any }).room || null;
|
||||||
|
}
|
||||||
|
isLoadingEdit.value = false;
|
||||||
|
isLoadingDetail.value = false;
|
||||||
|
};
|
||||||
|
const handleFilterConfirm = (payload: {
|
||||||
|
buildingIds: number[] | null;
|
||||||
|
floorIds: number[] | null;
|
||||||
|
statusIds: number[] | null;
|
||||||
|
rentArea: number[];
|
||||||
|
tagIds: number[] | null;
|
||||||
|
filteredList: any[]; // 筛选后的列表数据
|
||||||
|
}) => {
|
||||||
|
// 更新查询参数(用于后端筛选)
|
||||||
|
(queryParams.value as any).building_ids = payload.buildingIds;
|
||||||
|
(queryParams.value as any).floor_ids = payload.floorIds;
|
||||||
|
(queryParams.value as any).status_ids = payload.statusIds;
|
||||||
|
(queryParams.value as any).rent_area_min = payload.rentArea?.[0];
|
||||||
|
(queryParams.value as any).rent_area_max = payload.rentArea?.[1];
|
||||||
|
(queryParams.value as any).tag_ids = payload.tagIds;
|
||||||
|
|
||||||
|
// 使用筛选后的列表数据更新表格
|
||||||
|
if (payload.filteredList) {
|
||||||
|
setData(payload.filteredList);
|
||||||
|
setTotal(payload.filteredList.length);
|
||||||
|
} else {
|
||||||
|
// 如果没有筛选数据,重新加载列表数据
|
||||||
|
getHouseListData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleDel = async (id: number) => {
|
||||||
|
const { code, message } = await delRoom(id);
|
||||||
|
if (code === 200) {
|
||||||
|
Message.success(message || '删除成功');
|
||||||
|
// 重新加载列表数据
|
||||||
|
await getHouseListData();
|
||||||
|
} else {
|
||||||
|
Message.error(message || '删除失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 分页事件处理
|
||||||
|
const onPageChange = (page: number) => {
|
||||||
|
setCurrent(page);
|
||||||
|
current.value = page;
|
||||||
|
};
|
||||||
|
const onPageSizeChange = (size: number) => {
|
||||||
|
setPageSize(size);
|
||||||
|
pageSize.value = size;
|
||||||
|
};
|
||||||
|
const handleAddHouse = () => {
|
||||||
|
isEdit.value = false;
|
||||||
|
currentRecordInfo.value = null;
|
||||||
|
showDrawer.value = true;
|
||||||
|
};
|
||||||
|
const handleEditProject = async (id: number) => {
|
||||||
|
if (isLoadingEdit.value) return;
|
||||||
|
isEdit.value = true;
|
||||||
|
await getRoomData(id, true);
|
||||||
|
showDrawer.value = true;
|
||||||
|
};
|
||||||
|
const handleDetail = async (id: number) => {
|
||||||
|
if (isLoadingDetail.value) return;
|
||||||
|
await getRoomData(id, false);
|
||||||
|
showProjectInfoDrawer.value = true;
|
||||||
|
};
|
||||||
|
const handleSearch = () => {
|
||||||
|
getHouseListData();
|
||||||
|
};
|
||||||
|
const handleFilter = () => {
|
||||||
|
showListFilterDrawer.value = true;
|
||||||
|
};
|
||||||
|
const getTagsColor = (status) => {
|
||||||
|
const colorMap = {
|
||||||
|
1: 'orangered',
|
||||||
|
2: 'orange',
|
||||||
|
3: 'red',
|
||||||
|
4: 'green',
|
||||||
|
};
|
||||||
|
return colorMap[status];
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
setColumns(columns);
|
||||||
|
getHouseListData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -199,20 +199,10 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
||||||
import type { FormInstance } from '@arco-design/web-vue';
|
import type { FormInstance } from '@arco-design/web-vue';
|
||||||
const priceUnits = [
|
import { PRICE_UNITS, PRICE_UNIT_MAP } from '@/constants';
|
||||||
{ value: 1, text: '元/㎡*月' },
|
|
||||||
{ value: 2, text: '元/㎡*天' },
|
const priceUnits = PRICE_UNITS;
|
||||||
{ value: 3, text: '元/天' },
|
const priceUnitMap = PRICE_UNIT_MAP;
|
||||||
{ value: 4, text: '元/月' },
|
|
||||||
{ value: 5, text: '元/年' },
|
|
||||||
];
|
|
||||||
const priceUnitMap = new Map([
|
|
||||||
[1, '元/㎡*月'],
|
|
||||||
[2, '元/㎡*天'],
|
|
||||||
[3, '元/天'],
|
|
||||||
[4, '元/月'],
|
|
||||||
[5, '元/年'],
|
|
||||||
]);
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
record?: any | null;
|
record?: any | null;
|
||||||
|
|
@ -259,7 +249,7 @@
|
||||||
emit('update:visible', false);
|
emit('update:visible', false);
|
||||||
emit('submitted', form);
|
emit('submitted', form);
|
||||||
} catch {
|
} catch {
|
||||||
console.warn('❌ 校验未通过');
|
// 校验未通过
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 部门列表
|
|
||||||
async function getAllProjectListData() {
|
async function getAllProjectListData() {
|
||||||
try {
|
try {
|
||||||
const { code, data } = await getAllProjectIdNameList();
|
const { code, data } = await getAllProjectIdNameList();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue