feat: 项目管理列表筛选

This commit is contained in:
Zzz1zz 2025-08-11 23:28:10 +08:00
parent 52102d2e72
commit 194e0faa72
7 changed files with 324 additions and 12 deletions

View file

@ -53,6 +53,7 @@ module.exports = {
'@typescript-eslint/no-unused-vars': 1, '@typescript-eslint/no-unused-vars': 1,
'@typescript-eslint/no-empty-function': 1, '@typescript-eslint/no-empty-function': 1,
'@typescript-eslint/no-explicit-any': 0, // 改为警告 '@typescript-eslint/no-explicit-any': 0, // 改为警告
'no-unused-expressions': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 2 : 1, // 生产环境禁用console 'no-console': process.env.NODE_ENV === 'production' ? 2 : 1, // 生产环境禁用console
'import/extensions': [ 'import/extensions': [
2, 2,

View file

@ -5,7 +5,7 @@ import {
CreateProjectParam, CreateProjectParam,
ProjectDetailRes, ProjectDetailRes,
} from '@/types/business'; } from '@/types/business';
import { ProjectPaginationParams } from '@/types/comm'; import { ProjectPaginationParams, CommonCityRes } 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';
@ -42,3 +42,7 @@ export function updateProject(
): Promise<HttpResponse> { ): Promise<HttpResponse> {
return axios.post('project/updateProject', params); return axios.post('project/updateProject', params);
} }
// 全部项目的城市列表
export function getProjectCityList(): Promise<CommonCityRes> {
return axios.get(`project/getCityList`);
}

View file

@ -20,6 +20,16 @@ export interface AllProjectsItem {
name: string; // 项目名称 name: string; // 项目名称
buildings: BuildingItem[]; buildings: BuildingItem[];
} }
export interface RoomItem {
id?: number;
projectId: number;
name: string;
buildingName: string;
rentArea: number;
RentAreaStr: string;
status: number;
statusStr: string;
}
export interface ProjectItem extends BaseEntity { export interface ProjectItem extends BaseEntity {
systemTenantId?: number; systemTenantId?: number;
name: string; // 项目名称 name: string; // 项目名称
@ -33,6 +43,7 @@ export interface ProjectItem extends BaseEntity {
isDelete: DeleteStatus; isDelete: DeleteStatus;
status: UserStatus; status: UserStatus;
imageUrls: string[]; imageUrls: string[];
signedPutURL: string[];
typeName: string; // 类型名称 typeName: string; // 类型名称
district: string; district: string;
province: string; province: string;
@ -44,7 +55,13 @@ export interface ProjectItem extends BaseEntity {
rentRate: number; rentRate: number;
RoomAreaStr: string; // 列表显示的计租面积 RoomAreaStr: string; // 列表显示的计租面积
RentAreaStr: string; // 列表显示的招商面积 RentAreaStr: string; // 列表显示的招商面积
keepAreaStr?: string; // 列表显示的招商面积
RentRateStr: string; // 列表显示的出租率 RentRateStr: string; // 列表显示的出租率
buildings?: BuildingItem[]; // 楼栋数据
rooms?: RoomItem[];
roomsNum?: number;
roomsRentNum?: number;
roomsUnRentNum?: number;
} }
export type AllProjectListRes = HttpResponse<{ export type AllProjectListRes = HttpResponse<{
@ -95,5 +112,5 @@ export interface ProjectInfo extends BaseEntity {
} }
export type ProjectDetailRes = HttpResponse<{ export type ProjectDetailRes = HttpResponse<{
project: ProjectInfo; project: ProjectItem;
}>; }>;

View file

@ -43,7 +43,7 @@ export interface CommonProvinceItem {
code: string; // 编码 code: string; // 编码
} }
export interface CommonCityItem extends CommonProvinceItem { export interface CommonCityItem extends CommonProvinceItem {
ProvinceId: number; // 所属省份id ProvinceId?: number; // 所属省份id
} }
export interface CommonDistrictItem extends CommonProvinceItem { export interface CommonDistrictItem extends CommonProvinceItem {
CityId: number; // 所属城市id CityId: number; // 所属城市id

View file

@ -0,0 +1,46 @@
<template>
<a-drawer
:width="1000"
:visible="visible"
title="项目详情"
:footer="false"
unmount-on-close
@cancel="handleCancel"
>
项目详情
</a-drawer>
</template>
<script lang="ts" setup>
import { watch, onMounted } from 'vue';
// import { Message } from '@arco-design/web-vue';
// import { projectTypeList } from '@/utils/constant';
import { ProjectItem } from '@/types/business';
const props = defineProps<{
visible: boolean;
projectId: number | null;
detailInfo: ProjectItem | null;
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
}>();
watch(
() => props.visible,
async (val) => {
console.log(val, 'val');
},
{ immediate: true }
);
const handleCancel = () => {
emit('update:visible', false);
};
onMounted(() => {});
</script>
<style lang="less" scoped>
:deep(label.arco-form-item-label) {
width: 100% !important;
}
</style>

View file

@ -0,0 +1,187 @@
<template>
<a-drawer
:width="420"
:visible="visible"
title="筛选"
unmount-on-close
@ok="handleOk"
@cancel="handleCancel"
>
<p class="label">项目类型</p>
<ul class="list">
<li
v-for="item in projectTypeList"
:key="item.value"
:class="['item', { isCheck: projectTypeChecked.includes(item.value) }]"
@click="handleCheckTypeIds(item.value)"
>
{{ item.text }}
</li>
</ul>
<p class="label">项目位置</p>
<ul class="list">
<li
v-for="item in projectCityList"
:key="item.id"
:class="[
'item',
{
isCheck:
typeof item.id === 'number'
? projectCityChecked.includes(item.id)
: false,
},
]"
@click="handleCheckCityIds(item.id)"
>
{{ item.name }}
</li>
</ul>
<p class="label">出租率</p>
<div class="slider">
<a-slider
v-model="rentRate"
:min="0"
:max="100"
range
:marks="marks"
></a-slider>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from 'vue';
import { getProjectCityList } from '@/api/business';
import { CommonCityItem } from '@/types/comm';
const props = defineProps<{
visible: boolean;
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(
e: 'confirm',
payload: {
typeIds: number[] | null;
cityIds: number[] | null;
rentRate: number | number[];
}
): void;
}>();
const projectTypeChecked = ref<number[]>([0]);
const projectCityChecked = ref<number[]>([0]);
const rentRate = ref<number | number[]>(0);
const projectTypeList = [
{ text: '全部', value: 0 },
{ text: '写字楼', value: 1 },
{ text: '园区', value: 2 },
{ text: '商业综合体', value: 3 },
];
const marks = {
0: '0%',
100: '100%',
};
const projectCityList = ref<CommonCityItem[]>([]);
const getProjectCityListData = async () => {
const { code, data } = await getProjectCityList();
if (code === 200 && data) {
projectCityList.value = Array.isArray(data.cityList) ? data.cityList : [];
projectCityList.value.unshift({ id: 0, code: '0', name: '全部' });
}
};
const handleCheckTypeIds = (id: number) => {
if (typeof id !== 'number') return;
if (id === 0) {
projectTypeChecked.value = [0];
} else {
if (projectTypeChecked.value.includes(0)) {
projectTypeChecked.value = [];
}
if (projectTypeChecked.value.includes(id)) {
projectTypeChecked.value = projectTypeChecked.value.filter(
(i) => i !== id
);
} else {
projectTypeChecked.value.push(id);
}
}
};
const handleCheckCityIds = (id: any) => {
if (typeof id !== 'number') return;
if (id === 0) {
// 点击 "全部" => 清空并只保留 0
projectCityChecked.value = [0];
} else {
// 如果当前包含 "全部",先去掉
if (projectCityChecked.value.includes(0)) {
projectCityChecked.value = [];
}
if (projectCityChecked.value.includes(id)) {
projectCityChecked.value = projectCityChecked.value.filter(
(i) => i !== id
);
} else {
projectCityChecked.value.push(id);
}
}
};
const handleCancel = () => {
emit('update:visible', false);
};
const normalizeIds = (ids: number[]) =>
ids.length === 1 && ids[0] === 0 ? null : ids;
const handleOk = () => {
emit('confirm', {
typeIds: normalizeIds(projectTypeChecked.value),
cityIds: normalizeIds(projectCityChecked.value),
rentRate: rentRate.value,
});
handleCancel();
};
watch(
() => props.visible,
async (val) => {
if (val && projectCityList.value.length === 0) {
getProjectCityListData();
}
},
{ immediate: true }
);
onMounted(() => {});
</script>
<style lang="less" scoped>
:deep(label.arco-form-item-label) {
width: 100% !important;
}
.label {
margin-top: 20px;
font-weight: bold;
font-size: 14px;
}
.list {
display: flex;
align-items: center;
flex-wrap: wrap;
.item {
width: 100px;
height: 32px;
line-height: 32px;
margin-right: 12px;
margin-top: 12px;
text-align: center;
background-color: #efefef;
border-radius: 2px;
cursor: pointer;
&.isCheck {
background-color: rgba(232, 243, 255, 1);
color: rgba(22, 93, 255, 1);
}
}
}
</style>

View file

@ -13,7 +13,7 @@
:placeholder="t('please.enter', { placeholder: t('content') })" :placeholder="t('please.enter', { placeholder: t('content') })"
@search="handleSearch" @search="handleSearch"
/> />
<a-button> <a-button @click="handleFilter">
<template #icon> <template #icon>
<icon-filter /> <icon-filter />
</template> </template>
@ -60,14 +60,30 @@
</template> </template>
<template #operate="{ record }"> <template #operate="{ record }">
<a-space> <a-space>
<a-button>查看</a-button> <a-button
:loading="isLoadingDetail"
@click="handleDetail(record.id)"
>查看</a-button
>
<a-button <a-button
:loading="isLoadingEdit" :loading="isLoadingEdit"
type="primary" type="primary"
@click="handleEditProject(record.id)" @click="handleEditProject(record.id)"
>编辑</a-button >编辑</a-button
> >
<a-button>更多</a-button> <a-popover trigger="click" position="bottom">
<a-button>{{ $t('more') }}</a-button>
<template #content>
<a-space direction="vertical">
<a-button @click="handleWatchHouse(record.id)">
查看房源
</a-button>
<a-button status="danger" @click="handleDel(record.id)">
删除项目
</a-button>
</a-space>
</template>
</a-popover>
</a-space> </a-space>
</template> </template>
</DataTable> </DataTable>
@ -76,6 +92,16 @@
v-model:visible="showDrawer" v-model:visible="showDrawer"
:is-edit="isEdit" :is-edit="isEdit"
:record="currentProjectInfo" :record="currentProjectInfo"
@submitted="getProjectListData"
/>
<projectInfoDrawer
v-model:visible="showProjectInfoDrawer"
:project-id="currentProjectId"
:detail-info="currentProjectInfo"
/>
<projectListFilterDrawer
v-model:visible="showListFilterDrawer"
@confirm="handleFilterConfirm"
/> />
</div> </div>
</template> </template>
@ -86,17 +112,23 @@
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import type { TableColumnData } from '@arco-design/web-vue'; import type { TableColumnData } from '@arco-design/web-vue';
import useDataTable from '@/composables/useDataTable'; import useDataTable from '@/composables/useDataTable';
import { ProjectItem, ProjectInfo } from '@/types/business'; import { ProjectItem } from '@/types/business';
import { getProjectList, getProjectDetail } from '@/api/business'; import { getProjectList, getProjectDetail } from '@/api/business';
import { ProjectPaginationParams, ProjectType } from '@/types/comm'; import { ProjectPaginationParams, ProjectType } from '@/types/comm';
import projectFormDrawer from './drawer/project-form-drawer.vue'; import projectFormDrawer from './drawer/project-form-drawer.vue';
import projectInfoDrawer from './drawer/project-info-drawer.vue';
import projectListFilterDrawer from './drawer/project-list-filter-drawer.vue';
const { t } = useI18n(); const { t } = useI18n();
const { loading } = useLoading(false); const { loading } = useLoading(false);
const showDrawer = ref<boolean>(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 isLoadingEdit = ref<boolean>(false);
const isLoadingDetail = ref<boolean>(false);
const isEdit = ref<boolean>(false); const isEdit = ref<boolean>(false);
const currentProjectInfo = ref<ProjectInfo | null>(null); const currentProjectInfo = ref<ProjectItem | null>(null);
const projectList = ref<ProjectItem[]>([]); const projectList = ref<ProjectItem[]>([]);
const { const {
data: renderData, data: renderData,
@ -152,15 +184,33 @@
} }
setLoading(false); setLoading(false);
}; };
const getProjectDetailData = async (id: number) => { const getProjectDetailData = async (id: number, edit: boolean) => {
isLoadingEdit.value = true; if (currentProjectInfo.value && currentProjectInfo.value?.id === id) return;
edit ? (isLoadingEdit.value = true) : (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;
} }
isLoadingEdit.value = false; isLoadingEdit.value = false;
isLoadingDetail.value = false;
};
const handleWatchHouse = async (id: number) => {
try {
console.log(id, 'is id');
} catch (error) {
// 处理错误
}
};
const handleFilterConfirm = ({ typeIds, cityIds, rentRate }) => {
queryParams.value.type = typeIds;
queryParams.value.province_ids = cityIds;
queryParams.value.rent_rate_min = rentRate[0];
queryParams.value.rent_rate_max = rentRate[1];
getProjectListData();
};
const handleDel = (id: number) => {
console.log(id, 'iiii');
}; };
// 分页事件处理 // 分页事件处理
const onPageChange = (page: number) => { const onPageChange = (page: number) => {
setCurrent(page); setCurrent(page);
@ -177,12 +227,19 @@
}; };
const handleEditProject = async (id: number) => { const handleEditProject = async (id: number) => {
isEdit.value = true; isEdit.value = true;
await getProjectDetailData(id); await getProjectDetailData(id, true);
showDrawer.value = true; showDrawer.value = true;
}; };
const handleDetail = async (id: number) => {
await getProjectDetailData(id, false);
showProjectInfoDrawer.value = true;
};
const handleSearch = () => { const handleSearch = () => {
getProjectListData(); getProjectListData();
}; };
const handleFilter = () => {
showListFilterDrawer.value = true;
};
const getProjectTypeColor = (type: ProjectType) => { const getProjectTypeColor = (type: ProjectType) => {
const typeMap = { 1: 'green', 2: 'red', 3: 'orange' }; const typeMap = { 1: 'green', 2: 'red', 3: 'orange' };
return typeMap[type]; return typeMap[type];