feat: 租客管理功能开发
This commit is contained in:
@@ -98,3 +98,41 @@ export function getRoomOverList(params: any): Promise<HttpResponse> {
|
|||||||
export function getRoomsGraphList(params: any): Promise<HttpResponse> {
|
export function getRoomsGraphList(params: any): Promise<HttpResponse> {
|
||||||
return axios.post('roomsGraph/getRoomList', params);
|
return axios.post('roomsGraph/getRoomList', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------- 租客管理 -------------
|
||||||
|
//创建租客
|
||||||
|
export function createTenant(params: any): Promise<HttpResponse> {
|
||||||
|
return axios.post('tenants/createTenant', params);
|
||||||
|
}
|
||||||
|
// 租客列表(分页)
|
||||||
|
export function getTenantList(params: PaginationParams): Promise<HttpResponse> {
|
||||||
|
return axios.post('tenants/getTenantList', params);
|
||||||
|
}
|
||||||
|
// 更新租客信息
|
||||||
|
export function updateTenant(params: any): Promise<HttpResponse> {
|
||||||
|
return axios.post('tenants/updateTenant', params);
|
||||||
|
}
|
||||||
|
// 删除租客
|
||||||
|
export function delTenant(id: number): Promise<HttpResponse> {
|
||||||
|
return axios.get(`tenants/delTenant?id=${id}`);
|
||||||
|
}
|
||||||
|
// 租客详情
|
||||||
|
export function getTenantDetail(id: number): Promise<HttpResponse> {
|
||||||
|
return axios.get(`tenants/getTenant?id=${id}`);
|
||||||
|
}
|
||||||
|
// 租客归档
|
||||||
|
export function filingTenant(params: any): Promise<HttpResponse> {
|
||||||
|
return axios.post('tenants/filingTenant', params);
|
||||||
|
}
|
||||||
|
// 租客下拉列表(不分页)
|
||||||
|
export function getTenantAllList(
|
||||||
|
params: PaginationParams
|
||||||
|
): Promise<HttpResponse> {
|
||||||
|
return axios.post('tenants/getTenantAllList', params);
|
||||||
|
}
|
||||||
|
// 租客归档列表(分页)
|
||||||
|
export function getTenantFilingList(
|
||||||
|
params: PaginationParams
|
||||||
|
): Promise<HttpResponse> {
|
||||||
|
return axios.post('tenants/getTenantFilingList', params);
|
||||||
|
}
|
||||||
|
|||||||
@@ -69,3 +69,130 @@ export const PRICE_UNIT_MAP = new Map([
|
|||||||
[4, '元/月'],
|
[4, '元/月'],
|
||||||
[5, '元/年'],
|
[5, '元/年'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 租客来源
|
||||||
|
export const TENANT_SOURCES = [
|
||||||
|
{ value: 1, text: '中介' },
|
||||||
|
{ value: 2, text: '招商获客' },
|
||||||
|
{ value: 3, text: '线下来访' },
|
||||||
|
{ value: 4, text: '客户转介' },
|
||||||
|
{ value: 5, text: '其他转介' },
|
||||||
|
];
|
||||||
|
// 租客映射表
|
||||||
|
export const TENANT_SOURCE_MAP = new Map([
|
||||||
|
[1, '中介'],
|
||||||
|
[2, '招商获客'],
|
||||||
|
[3, '线下来访'],
|
||||||
|
[4, '客户转介'],
|
||||||
|
[5, '其他转介'],
|
||||||
|
]);
|
||||||
|
// 租客类型
|
||||||
|
export const TENANT_TYPES = [
|
||||||
|
{ value: 1, text: '企业' },
|
||||||
|
{ value: 2, text: '个人' },
|
||||||
|
{ value: 3, text: '非营利组织' },
|
||||||
|
{ value: 4, text: '其他' },
|
||||||
|
];
|
||||||
|
// 租客映射表
|
||||||
|
export const TENANT_TYPE_MAP = new Map([
|
||||||
|
[1, '企业'],
|
||||||
|
[2, '个人'],
|
||||||
|
[3, '非营利组织'],
|
||||||
|
[4, '其他'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 租客状态
|
||||||
|
// 状态:1正常 2欠费 3即将到期 4已到期 5可归档 6已归档
|
||||||
|
export const TENANT_STATUS = [
|
||||||
|
{ value: 1, text: '正常' },
|
||||||
|
{ value: 2, text: '欠费' },
|
||||||
|
{ value: 3, text: '即将到期' },
|
||||||
|
{ value: 4, text: '已到期' },
|
||||||
|
{ value: 5, text: '可归档' },
|
||||||
|
{ value: 6, text: '已归档' },
|
||||||
|
];
|
||||||
|
// 租客状态映射表
|
||||||
|
export const TENANT_STATUS_MAP = new Map([
|
||||||
|
[1, '正常'],
|
||||||
|
[2, '欠费'],
|
||||||
|
[3, '即将到期'],
|
||||||
|
[4, '已到期'],
|
||||||
|
[5, '可归档'],
|
||||||
|
[6, '已归档'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 企业规模
|
||||||
|
export const COMPANY_SCALES = [
|
||||||
|
{ value: 1, text: '小微企业' },
|
||||||
|
{ value: 2, text: '中型企业' },
|
||||||
|
{ value: 3, text: '大型企业' },
|
||||||
|
{ value: 4, text: '集团/上市公司' },
|
||||||
|
];
|
||||||
|
// 企业规模映射表
|
||||||
|
export const COMPANY_SCALE_MAP = new Map([
|
||||||
|
[1, '小微企业'],
|
||||||
|
[2, '中型企业'],
|
||||||
|
[3, '大型企业'],
|
||||||
|
[4, '集团/上市公司'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 行业分类
|
||||||
|
export const INDUSTRY_CATEGORIES = [
|
||||||
|
{ value: 1, text: '科技/互联网' },
|
||||||
|
{ value: 2, text: '金融/保险' },
|
||||||
|
{ value: 3, text: '专业服务(法律/咨询/会计)' },
|
||||||
|
{ value: 4, text: '医疗/健康' },
|
||||||
|
{ value: 5, text: '教育培训' },
|
||||||
|
{ value: 6, text: '零售/消费' },
|
||||||
|
{ value: 7, text: '制造业' },
|
||||||
|
{ value: 8, text: '文化/传媒' },
|
||||||
|
{ value: 9, text: '房地产/建筑' },
|
||||||
|
{ value: 10, text: '其他' },
|
||||||
|
];
|
||||||
|
// 行业分类映射表
|
||||||
|
export const INDUSTRY_CATEGORY_MAP = new Map([
|
||||||
|
[1, '科技/互联网'],
|
||||||
|
[2, '金融/保险'],
|
||||||
|
[3, '专业服务(法律/咨询/会计)'],
|
||||||
|
[4, '医疗/健康'],
|
||||||
|
[5, '教育培训'],
|
||||||
|
[6, '零售/消费'],
|
||||||
|
[7, '制造业'],
|
||||||
|
[8, '文化/传媒'],
|
||||||
|
[9, '房地产/建筑'],
|
||||||
|
[10, '其他'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 发票类型
|
||||||
|
export const INVOICE_TYPES = [
|
||||||
|
{ value: 1, text: '增值税专用发票' },
|
||||||
|
{ value: 2, text: '增值税电子专用发票' },
|
||||||
|
{ value: 3, text: '增值税普通发票' },
|
||||||
|
{ value: 4, text: '增值税电子普通发票' },
|
||||||
|
{ value: 5, text: '全面数字化电子发票' },
|
||||||
|
];
|
||||||
|
// 发票类型映射表
|
||||||
|
export const INVOICE_TYPE_MAP = new Map([
|
||||||
|
[1, '增值税专用发票'],
|
||||||
|
[2, '增值税电子专用发票'],
|
||||||
|
[3, '增值税普通发票'],
|
||||||
|
[4, '增值税电子普通发票'],
|
||||||
|
[5, '全面数字化电子发票'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 租客归档原因
|
||||||
|
export const TENANT_FILING_TYPES = [
|
||||||
|
{ value: 1, text: '到期退租(自然到期)' },
|
||||||
|
{ value: 2, text: '提前退租(协商终止)' },
|
||||||
|
{ value: 3, text: '非正常终止(违约或被清退)' },
|
||||||
|
{ value: 4, text: '签约未履行(签约后未生效)' },
|
||||||
|
{ value: 5, text: '其他原因' },
|
||||||
|
];
|
||||||
|
// 租客归档原因映射表
|
||||||
|
export const TENANT_FILING_TYPE_MAP = new Map([
|
||||||
|
[1, '到期退租(自然到期)'],
|
||||||
|
[2, '提前退租(协商终止)'],
|
||||||
|
[3, '非正常终止(违约或被清退)'],
|
||||||
|
[4, '签约未履行(签约后未生效)'],
|
||||||
|
[5, '其他原因'],
|
||||||
|
]);
|
||||||
|
|||||||
+3
-1
@@ -50,7 +50,7 @@ export default {
|
|||||||
'menu.business.contract-template': '合同模板',
|
'menu.business.contract-template': '合同模板',
|
||||||
'menu.business.tenant': '租客管理',
|
'menu.business.tenant': '租客管理',
|
||||||
'menu.business.tenant-list': '租客列表',
|
'menu.business.tenant-list': '租客列表',
|
||||||
'menu.business.tenant.contacts': '租客联系人',
|
'menu.business.tenant-file': '租客归档',
|
||||||
'add-project': '新建项目',
|
'add-project': '新建项目',
|
||||||
'edit-project': '编辑项目',
|
'edit-project': '编辑项目',
|
||||||
'menu.setting.user': '用户设置',
|
'menu.setting.user': '用户设置',
|
||||||
@@ -124,6 +124,8 @@ export default {
|
|||||||
'setting.data-dictionary': '数据字典',
|
'setting.data-dictionary': '数据字典',
|
||||||
'add-house': '新建房源',
|
'add-house': '新建房源',
|
||||||
'edit-house': '编辑房源',
|
'edit-house': '编辑房源',
|
||||||
|
'add-tenant': '新建租客',
|
||||||
|
'edit-tenant': '编辑租客',
|
||||||
|
|
||||||
...localeSettings,
|
...localeSettings,
|
||||||
...localeMessageBox,
|
...localeMessageBox,
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ const BUSINESS: AppRouteRecordRaw = {
|
|||||||
component: () =>
|
component: () =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "business-tenant" */
|
/* webpackChunkName: "business-tenant" */
|
||||||
'@/views/business/tenant/list.vue'
|
'@/views/business/tenant/list/index.vue'
|
||||||
),
|
),
|
||||||
meta: {
|
meta: {
|
||||||
locale: 'menu.business.tenant-list',
|
locale: 'menu.business.tenant-list',
|
||||||
@@ -194,15 +194,15 @@ const BUSINESS: AppRouteRecordRaw = {
|
|||||||
},
|
},
|
||||||
// 租客联系人
|
// 租客联系人
|
||||||
{
|
{
|
||||||
path: 'tenant-contacts',
|
path: 'tenant-file',
|
||||||
name: 'business-tenant-contacts',
|
name: 'business-tenant-file',
|
||||||
component: () =>
|
component: () =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "business-tenant" */
|
/* webpackChunkName: "business-tenant" */
|
||||||
'@/views/business/tenant/contacts.vue'
|
'@/views/business/tenant/file/index.vue'
|
||||||
),
|
),
|
||||||
meta: {
|
meta: {
|
||||||
locale: 'menu.business.tenant.contacts',
|
locale: 'menu.business.tenant-file',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
roles: ['*'],
|
roles: ['*'],
|
||||||
icon: 'icon-font-colors',
|
icon: 'icon-font-colors',
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>tenant-contacts</div>
|
|
||||||
</template>
|
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
<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="租客类型">
|
||||||
|
<a-select v-model="filterForm.tenantType" placeholder="请选择租客类型">
|
||||||
|
<a-option value="">所有类型</a-option>
|
||||||
|
<a-option
|
||||||
|
v-for="item in TENANT_TYPES"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 租客来源(单选) -->
|
||||||
|
<a-form-item label="租客来源">
|
||||||
|
<a-select
|
||||||
|
v-model="filterForm.tenantSource"
|
||||||
|
placeholder="请选择租客来源"
|
||||||
|
>
|
||||||
|
<a-option value="">所有来源</a-option>
|
||||||
|
<a-option
|
||||||
|
v-for="item in TENANT_SOURCES"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 归档原因(多选) -->
|
||||||
|
<a-form-item label="归档原因">
|
||||||
|
<a-select
|
||||||
|
v-model="filterForm.filingTypeIds"
|
||||||
|
multiple
|
||||||
|
placeholder="请选择归档原因"
|
||||||
|
:max-tag-count="3"
|
||||||
|
>
|
||||||
|
<a-option
|
||||||
|
v-for="item in TENANT_FILING_TYPES"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 信用评分(范围滑块) -->
|
||||||
|
<a-form-item label="信用评分">
|
||||||
|
<a-slider
|
||||||
|
v-model="filterForm.creditScore"
|
||||||
|
range
|
||||||
|
:min="creditScoreMin"
|
||||||
|
:max="creditScoreMax"
|
||||||
|
:marks="creditScoreMarks"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, computed } from 'vue';
|
||||||
|
import {
|
||||||
|
TENANT_TYPES,
|
||||||
|
TENANT_SOURCES,
|
||||||
|
TENANT_FILING_TYPES,
|
||||||
|
} from '@/constants';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
tenantList?: any[]; // 租客列表数据,用于提取筛选选项
|
||||||
|
savedFilterState?: {
|
||||||
|
tenantType: number | null;
|
||||||
|
tenantSource: number | null;
|
||||||
|
filingTypeIds: number[];
|
||||||
|
creditScore: number[];
|
||||||
|
} | null; // 保存的筛选状态
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
(
|
||||||
|
e: 'confirm',
|
||||||
|
payload: {
|
||||||
|
tenantType: number | null;
|
||||||
|
tenantSource: number | null;
|
||||||
|
filingTypeIds: number[];
|
||||||
|
creditScore: number[];
|
||||||
|
filteredList: any[]; // 筛选后的列表数据
|
||||||
|
}
|
||||||
|
): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 筛选表单数据
|
||||||
|
const filterForm = ref({
|
||||||
|
tenantType: '' as number | string,
|
||||||
|
tenantSource: '' as number | string,
|
||||||
|
filingTypeIds: [] as number[],
|
||||||
|
creditScore: [0, 100] as number[],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 信用评分范围
|
||||||
|
const creditScoreMin = computed(() => {
|
||||||
|
if (!props.tenantList || props.tenantList.length === 0) return 0;
|
||||||
|
const scores = props.tenantList
|
||||||
|
.map((tenant) => Number(tenant.creditScore))
|
||||||
|
.filter((score) => !isNaN(score) && score >= 0);
|
||||||
|
return scores.length > 0 ? Math.min(...scores) : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const creditScoreMax = computed(() => {
|
||||||
|
if (!props.tenantList || props.tenantList.length === 0) return 100;
|
||||||
|
const scores = props.tenantList
|
||||||
|
.map((tenant) => Number(tenant.creditScore))
|
||||||
|
.filter((score) => !isNaN(score) && score >= 0);
|
||||||
|
return scores.length > 0 ? Math.max(...scores) : 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
const creditScoreMarks = computed(() => {
|
||||||
|
return {
|
||||||
|
[creditScoreMin.value]: `${creditScoreMin.value}`,
|
||||||
|
[creditScoreMax.value]: `${creditScoreMax.value}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化筛选表单
|
||||||
|
const initFilterForm = () => {
|
||||||
|
const min = creditScoreMin.value || 0;
|
||||||
|
const max = creditScoreMax.value || 100;
|
||||||
|
|
||||||
|
// 如果有保存的筛选状态,恢复它
|
||||||
|
if (props.savedFilterState) {
|
||||||
|
filterForm.value = {
|
||||||
|
tenantType:
|
||||||
|
props.savedFilterState.tenantType === null
|
||||||
|
? ''
|
||||||
|
: props.savedFilterState.tenantType,
|
||||||
|
tenantSource:
|
||||||
|
props.savedFilterState.tenantSource === null
|
||||||
|
? ''
|
||||||
|
: props.savedFilterState.tenantSource,
|
||||||
|
filingTypeIds: [...props.savedFilterState.filingTypeIds],
|
||||||
|
creditScore:
|
||||||
|
props.savedFilterState.creditScore.length === 2
|
||||||
|
? [...props.savedFilterState.creditScore]
|
||||||
|
: [min, max],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 否则初始化默认值
|
||||||
|
filterForm.value = {
|
||||||
|
tenantType: '',
|
||||||
|
tenantSource: '',
|
||||||
|
filingTypeIds: [],
|
||||||
|
creditScore: [min, max],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 筛选列表数据
|
||||||
|
const filterTenantList = () => {
|
||||||
|
if (!props.tenantList || props.tenantList.length === 0) return [];
|
||||||
|
|
||||||
|
let filtered = [...props.tenantList];
|
||||||
|
|
||||||
|
// 筛选租客类型
|
||||||
|
if (
|
||||||
|
filterForm.value.tenantType !== '' &&
|
||||||
|
filterForm.value.tenantType !== null &&
|
||||||
|
filterForm.value.tenantType !== undefined &&
|
||||||
|
typeof filterForm.value.tenantType === 'number'
|
||||||
|
) {
|
||||||
|
filtered = filtered.filter(
|
||||||
|
(tenant) => tenant.type === filterForm.value.tenantType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选租客来源
|
||||||
|
if (
|
||||||
|
filterForm.value.tenantSource !== '' &&
|
||||||
|
filterForm.value.tenantSource !== null &&
|
||||||
|
filterForm.value.tenantSource !== undefined &&
|
||||||
|
typeof filterForm.value.tenantSource === 'number'
|
||||||
|
) {
|
||||||
|
filtered = filtered.filter(
|
||||||
|
(tenant) => tenant.source === filterForm.value.tenantSource
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选归档原因
|
||||||
|
if (filterForm.value.filingTypeIds.length > 0) {
|
||||||
|
filtered = filtered.filter((tenant) =>
|
||||||
|
filterForm.value.filingTypeIds.includes(tenant.filingType)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选信用评分
|
||||||
|
const [minScore, maxScore] = filterForm.value.creditScore;
|
||||||
|
filtered = filtered.filter((tenant) => {
|
||||||
|
const score = Number(tenant.creditScore);
|
||||||
|
return !isNaN(score) && score >= minScore && score <= maxScore;
|
||||||
|
});
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
const filteredList = filterTenantList();
|
||||||
|
|
||||||
|
// 确保 undefined 值被转换为 null
|
||||||
|
const getTenantType = () => {
|
||||||
|
const value = filterForm.value.tenantType;
|
||||||
|
if (
|
||||||
|
value === '' ||
|
||||||
|
value === null ||
|
||||||
|
value === undefined ||
|
||||||
|
typeof value !== 'number'
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTenantSource = () => {
|
||||||
|
const value = filterForm.value.tenantSource;
|
||||||
|
if (
|
||||||
|
value === '' ||
|
||||||
|
value === null ||
|
||||||
|
value === undefined ||
|
||||||
|
typeof value !== 'number'
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
emit('confirm', {
|
||||||
|
tenantType: getTenantType(),
|
||||||
|
tenantSource: getTenantSource(),
|
||||||
|
filingTypeIds: filterForm.value.filingTypeIds,
|
||||||
|
creditScore: filterForm.value.creditScore,
|
||||||
|
filteredList,
|
||||||
|
});
|
||||||
|
handleCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听抽屉打开,初始化数据
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
// 延迟初始化,确保 creditScoreMin 和 creditScoreMax 已计算
|
||||||
|
setTimeout(() => {
|
||||||
|
initFilterForm();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听保存的筛选状态变化,更新表单
|
||||||
|
watch(
|
||||||
|
() => props.savedFilterState,
|
||||||
|
() => {
|
||||||
|
if (props.visible) {
|
||||||
|
// 延迟更新,确保 creditScoreMin 和 creditScoreMax 已计算
|
||||||
|
setTimeout(() => {
|
||||||
|
initFilterForm();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(label.arco-form-item-label) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-slider-mark) {
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,421 @@
|
|||||||
|
<template>
|
||||||
|
<a-drawer
|
||||||
|
:width="670"
|
||||||
|
:visible="visible"
|
||||||
|
:title="isEdit ? '编辑租客' : '新建租客'"
|
||||||
|
unmount-on-close
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
auto-label-width
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<p class="text-[18px] my-[10px] text-black">基本信息</p>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="租客名称" field="name" required>
|
||||||
|
<a-input v-model="form.name" placeholder="请输入租客名称" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="租客类型" field="type" required>
|
||||||
|
<a-select v-model="form.type" placeholder="请选择租客类型">
|
||||||
|
<a-option
|
||||||
|
v-for="item in TENANT_TYPES"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="租客来源" field="source">
|
||||||
|
<a-select v-model="form.source" placeholder="请选择租客来源">
|
||||||
|
<a-option
|
||||||
|
v-for="item in TENANT_SOURCES"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="行业分类" field="industry">
|
||||||
|
<a-select v-model="form.industry" placeholder="请选择租客行业分类">
|
||||||
|
<a-option
|
||||||
|
v-for="item in INDUSTRY_CATEGORIES"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="企业规模" field="enterprise">
|
||||||
|
<a-select v-model="form.enterprise" placeholder="请选择企业规模">
|
||||||
|
<a-option
|
||||||
|
v-for="item in COMPANY_SCALES"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="备注" field="remark">
|
||||||
|
<a-input v-model="form.remark" placeholder="请输入租客备注" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<!-- 联系人信息 -->
|
||||||
|
<p class="text-[18px] my-[10px] text-black">联系人信息</p>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="联系人姓名" field="contactName" required>
|
||||||
|
<a-input
|
||||||
|
v-model="form.contactName"
|
||||||
|
placeholder="请输入联系人姓名"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="联系人电话" field="contactTel" required>
|
||||||
|
<a-input v-model="form.contactTel" placeholder="请输入联系人电话" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item
|
||||||
|
label="联系人邮箱(接收通知单、发票)"
|
||||||
|
field="contactEmail"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model="form.contactEmail"
|
||||||
|
placeholder="请输入联系人邮箱"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="联系人备注" field="contactRemark">
|
||||||
|
<a-input
|
||||||
|
v-model="form.contactRemark"
|
||||||
|
placeholder="请输入联系人其他信息"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<!-- 开票信息 -->
|
||||||
|
<p class="text-[18px] my-[10px] text-black">开票信息</p>
|
||||||
|
<a-row :gutter="24">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="发票类型" field="invoiceType">
|
||||||
|
<a-select v-model="form.invoiceType" placeholder="请选择发票类型">
|
||||||
|
<a-option
|
||||||
|
v-for="item in INVOICE_TYPES"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item
|
||||||
|
label="纳税人识别号(统一社会信用代码)"
|
||||||
|
field="taxpayerIdentityNumber"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model="form.taxpayerIdentityNumber"
|
||||||
|
placeholder="请输入纳税人识别号"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="基本开户账号" field="account">
|
||||||
|
<a-input v-model="form.account" placeholder="请输入基本开户账号" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="开户银行名称" field="bankName">
|
||||||
|
<a-input v-model="form.bankName" placeholder="请输入开户银行名称" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="注册固定电话" field="registerPhone">
|
||||||
|
<a-input
|
||||||
|
v-model="form.registerPhone"
|
||||||
|
placeholder="请输入注册固定电话"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="注册场所地址" field="registerAddress">
|
||||||
|
<a-input
|
||||||
|
v-model="form.registerAddress"
|
||||||
|
placeholder="请输入注册场所地址"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, nextTick } from 'vue';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import {
|
||||||
|
TENANT_TYPES,
|
||||||
|
TENANT_SOURCES,
|
||||||
|
INDUSTRY_CATEGORIES,
|
||||||
|
COMPANY_SCALES,
|
||||||
|
INVOICE_TYPES,
|
||||||
|
VALIDATION_RULES,
|
||||||
|
} from '@/constants';
|
||||||
|
import { createTenant, updateTenant } from '@/api/business';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
isEdit?: boolean;
|
||||||
|
record?: any; // 编辑时的记录数据
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
(e: 'submitted'): void; // 提交成功后的回调
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const isLoading = ref<boolean>(false);
|
||||||
|
const formRef = ref();
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = ref({
|
||||||
|
name: '',
|
||||||
|
type: undefined as number | undefined,
|
||||||
|
source: undefined as number | undefined,
|
||||||
|
industry: undefined as number | undefined,
|
||||||
|
enterprise: undefined as number | undefined,
|
||||||
|
remark: '',
|
||||||
|
contactName: '',
|
||||||
|
contactTel: '',
|
||||||
|
contactEmail: '',
|
||||||
|
contactRemark: '',
|
||||||
|
invoiceType: undefined as number | undefined,
|
||||||
|
taxpayerIdentityNumber: '',
|
||||||
|
account: '',
|
||||||
|
bankName: '',
|
||||||
|
registerPhone: '',
|
||||||
|
registerAddress: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = {
|
||||||
|
name: [{ required: true, message: '请输入租客名称' }],
|
||||||
|
type: [{ required: true, message: '请选择租客类型' }],
|
||||||
|
contactName: [{ required: true, message: '请输入联系人姓名' }],
|
||||||
|
contactTel: [
|
||||||
|
{ required: true, message: '请输入联系人电话' },
|
||||||
|
{ match: VALIDATION_RULES.PHONE, message: '请输入正确的手机号码' },
|
||||||
|
],
|
||||||
|
contactEmail: [
|
||||||
|
{ match: VALIDATION_RULES.EMAIL, message: '请输入正确的邮箱地址' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化表单
|
||||||
|
const initForm = () => {
|
||||||
|
if (props.isEdit && props.record) {
|
||||||
|
// 编辑模式,填充数据
|
||||||
|
form.value = {
|
||||||
|
name: props.record.name || '',
|
||||||
|
type:
|
||||||
|
props.record.type !== undefined && props.record.type !== null
|
||||||
|
? props.record.type
|
||||||
|
: undefined,
|
||||||
|
source:
|
||||||
|
props.record.source !== undefined && props.record.source !== null
|
||||||
|
? props.record.source
|
||||||
|
: undefined,
|
||||||
|
industry:
|
||||||
|
props.record.industry !== undefined && props.record.industry !== null
|
||||||
|
? props.record.industry
|
||||||
|
: undefined,
|
||||||
|
enterprise:
|
||||||
|
props.record.enterprise !== undefined &&
|
||||||
|
props.record.enterprise !== null
|
||||||
|
? props.record.enterprise
|
||||||
|
: undefined,
|
||||||
|
remark: props.record.remark || '',
|
||||||
|
contactName: props.record.contactName || '',
|
||||||
|
contactTel: props.record.contactTel || '',
|
||||||
|
contactEmail: props.record.contactEmail || '',
|
||||||
|
contactRemark: props.record.contactRemark || '',
|
||||||
|
invoiceType:
|
||||||
|
props.record.invoiceType !== undefined &&
|
||||||
|
props.record.invoiceType !== null
|
||||||
|
? props.record.invoiceType
|
||||||
|
: undefined,
|
||||||
|
taxpayerIdentityNumber: props.record.taxpayerIdentityNumber || '',
|
||||||
|
account: props.record.account || '',
|
||||||
|
bankName: props.record.bankName || '',
|
||||||
|
registerPhone: props.record.registerPhone || '',
|
||||||
|
registerAddress: props.record.registerAddress || '',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 新建模式,重置表单
|
||||||
|
form.value = {
|
||||||
|
name: '',
|
||||||
|
type: undefined,
|
||||||
|
source: undefined,
|
||||||
|
industry: undefined,
|
||||||
|
enterprise: undefined,
|
||||||
|
remark: '',
|
||||||
|
contactName: '',
|
||||||
|
contactTel: '',
|
||||||
|
contactEmail: '',
|
||||||
|
contactRemark: '',
|
||||||
|
invoiceType: undefined,
|
||||||
|
taxpayerIdentityNumber: '',
|
||||||
|
account: '',
|
||||||
|
bankName: '',
|
||||||
|
registerPhone: '',
|
||||||
|
registerAddress: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理提交
|
||||||
|
const handleOk = async () => {
|
||||||
|
if (isLoading.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
const valid = await formRef.value.validate();
|
||||||
|
if (valid) return;
|
||||||
|
|
||||||
|
// 构建提交数据,按照接口要求传递所有字段
|
||||||
|
const params: any = {
|
||||||
|
name: form.value.name || '',
|
||||||
|
type: form.value.type,
|
||||||
|
remark: form.value.remark || '',
|
||||||
|
contactName: form.value.contactName || '',
|
||||||
|
contactTel: form.value.contactTel || '',
|
||||||
|
contactEmail: form.value.contactEmail || '',
|
||||||
|
contactRemark: form.value.contactRemark || '',
|
||||||
|
taxpayerIdentityNumber: form.value.taxpayerIdentityNumber || '',
|
||||||
|
account: form.value.account || '',
|
||||||
|
bankName: form.value.bankName || '',
|
||||||
|
registerPhone: form.value.registerPhone || '',
|
||||||
|
registerAddress: form.value.registerAddress || '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 可选数字字段,只有选择了才传递
|
||||||
|
if (form.value.source !== undefined && form.value.source !== null) {
|
||||||
|
params.source = form.value.source;
|
||||||
|
}
|
||||||
|
if (form.value.industry !== undefined && form.value.industry !== null) {
|
||||||
|
params.industry = form.value.industry;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
form.value.enterprise !== undefined &&
|
||||||
|
form.value.enterprise !== null
|
||||||
|
) {
|
||||||
|
params.enterprise = form.value.enterprise;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
form.value.invoiceType !== undefined &&
|
||||||
|
form.value.invoiceType !== null
|
||||||
|
) {
|
||||||
|
params.invoiceType = form.value.invoiceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
let response;
|
||||||
|
if (props.isEdit && props.record?.id) {
|
||||||
|
// 编辑模式
|
||||||
|
params.id = props.record.id;
|
||||||
|
response = await updateTenant(params);
|
||||||
|
} else {
|
||||||
|
// 新建模式
|
||||||
|
response = await createTenant(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
Message.success(
|
||||||
|
response.message || (props.isEdit ? '编辑成功' : '创建成功')
|
||||||
|
);
|
||||||
|
emit('submitted');
|
||||||
|
handleCancel();
|
||||||
|
} else {
|
||||||
|
Message.error(
|
||||||
|
response.message || (props.isEdit ? '编辑失败' : '创建失败')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
Message.error(error.message || (props.isEdit ? '编辑失败' : '创建失败'));
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化表单的辅助函数
|
||||||
|
const doInitForm = () => {
|
||||||
|
if (props.isEdit && props.record) {
|
||||||
|
// 编辑模式,确保有数据再初始化
|
||||||
|
initForm();
|
||||||
|
} else if (!props.isEdit) {
|
||||||
|
// 新建模式,直接初始化
|
||||||
|
initForm();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 同时监听 visible、isEdit 和 record,确保在条件满足时初始化
|
||||||
|
watch(
|
||||||
|
() => [props.visible, props.isEdit, props.record],
|
||||||
|
([visible, isEdit, record]) => {
|
||||||
|
if (visible) {
|
||||||
|
isLoading.value = false;
|
||||||
|
if (isEdit && record) {
|
||||||
|
// 编辑模式,有数据时初始化
|
||||||
|
nextTick(() => {
|
||||||
|
doInitForm();
|
||||||
|
});
|
||||||
|
} else if (!isEdit) {
|
||||||
|
// 新建模式,直接初始化
|
||||||
|
nextTick(() => {
|
||||||
|
doInitForm();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 关闭时重置表单
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(.arco-divider-text) {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
<template>
|
||||||
|
<a-drawer
|
||||||
|
:width="1000"
|
||||||
|
:visible="visible"
|
||||||
|
:footer="false"
|
||||||
|
unmount-on-close
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div class="flex items-center justify-between w-full pr-[20px]">
|
||||||
|
<span>租客详情</span>
|
||||||
|
<a-button type="text" @click="handleCancel">
|
||||||
|
<template #icon>
|
||||||
|
<icon-close />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 标题区域 -->
|
||||||
|
<div class="tenant-detail-header">
|
||||||
|
<div class="flex items-start justify-between mb-[16px]">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h2 class="text-[20px] font-bold mb-[12px]">{{
|
||||||
|
detailInfo?.name || '-'
|
||||||
|
}}</h2>
|
||||||
|
<div class="flex items-center gap-[8px] flex-wrap">
|
||||||
|
<a-tag :color="getTypeColor(detailInfo?.type)">
|
||||||
|
{{ detailInfo?.typeName || '-' }}
|
||||||
|
</a-tag>
|
||||||
|
<a-tag color="green"> {{ contractCount }} 合同执行 </a-tag>
|
||||||
|
<a-tag color="red"> {{ overdueBillCount }} 账单逾期 </a-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-[12px]">
|
||||||
|
<span class="text-[12px] text-[#86909c]">
|
||||||
|
{{ formatUpdateTime(detailInfo?.updatedAt) }} 更新
|
||||||
|
</span>
|
||||||
|
<a-button @click="handleEdit">
|
||||||
|
<template #icon>
|
||||||
|
<icon-edit />
|
||||||
|
</template>
|
||||||
|
编辑
|
||||||
|
</a-button>
|
||||||
|
<a-popover trigger="click" position="bottom">
|
||||||
|
<a-button>更多</a-button>
|
||||||
|
<template #content>
|
||||||
|
<a-space direction="vertical">
|
||||||
|
<a-button>归档租客</a-button>
|
||||||
|
<a-button status="danger">删除租客</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 标签页 -->
|
||||||
|
<a-tabs v-model:active-key="currentTab" class="tenant-detail-tabs">
|
||||||
|
<template #extra>
|
||||||
|
<!-- 信用评分 -->
|
||||||
|
<div class="flex items-center gap-[12px]">
|
||||||
|
<span class="text-[14px] font-medium">信用评分</span>
|
||||||
|
<a-progress
|
||||||
|
:percent="Number(detailInfo?.creditScore) || 0"
|
||||||
|
:color="
|
||||||
|
getCreditScoreColor(detailInfo?.status, detailInfo?.creditScore)
|
||||||
|
"
|
||||||
|
:show-text="false"
|
||||||
|
:style="{ width: '120px' }"
|
||||||
|
/>
|
||||||
|
<span class="text-[16px] font-bold">{{
|
||||||
|
detailInfo?.creditScore || 0
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-tab-pane key="data" title="资料">
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<div class="mb-[24px]">
|
||||||
|
<p class="text-[16px] font-medium mb-[16px]">基本信息</p>
|
||||||
|
<a-descriptions :column="2" bordered>
|
||||||
|
<a-descriptions-item label="租客名称">
|
||||||
|
{{ detailInfo?.name || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="租客类型">
|
||||||
|
{{ detailInfo?.typeName || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="租客来源">
|
||||||
|
{{ detailInfo?.sourceName || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="行业分类">
|
||||||
|
{{ detailInfo?.industryName || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="企业规模">
|
||||||
|
{{ detailInfo?.enterpriseName || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="备注">
|
||||||
|
{{ detailInfo?.remark || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 联系人信息 -->
|
||||||
|
<div class="mb-[24px]">
|
||||||
|
<p class="text-[16px] font-medium mb-[16px]">联系人信息</p>
|
||||||
|
<a-descriptions :column="2" bordered>
|
||||||
|
<a-descriptions-item label="联系人姓名">
|
||||||
|
{{ detailInfo?.contactName || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="联系人电话">
|
||||||
|
{{ detailInfo?.contactTel || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="联系人邮箱(接收通知单、发票)">
|
||||||
|
{{ detailInfo?.contactEmail || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="联系人备注">
|
||||||
|
{{ detailInfo?.contactRemark || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 开票信息 -->
|
||||||
|
<div class="mb-[24px]">
|
||||||
|
<p class="text-[16px] font-medium mb-[16px]">开票信息</p>
|
||||||
|
<a-descriptions :column="2" bordered>
|
||||||
|
<a-descriptions-item label="发票类型">
|
||||||
|
{{ detailInfo?.invoiceTypeName || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="纳税人识别号(统一社会信用代码)">
|
||||||
|
{{ detailInfo?.taxpayerIdentityNumber || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="基本开户账号">
|
||||||
|
{{ detailInfo?.account || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="开户银行名称">
|
||||||
|
{{ detailInfo?.bankName || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="注册固定电话">
|
||||||
|
{{ detailInfo?.registerPhone || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="注册场所地址">
|
||||||
|
{{ detailInfo?.registerAddress || '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 备忘录 -->
|
||||||
|
<div>
|
||||||
|
<p class="text-[16px] font-medium mb-[16px]">备忘录</p>
|
||||||
|
<a-textarea
|
||||||
|
v-model="memoContent"
|
||||||
|
placeholder="可输入租客备忘或重要信息"
|
||||||
|
:auto-size="{ minRows: 6, maxRows: 10 }"
|
||||||
|
class="mb-[12px]"
|
||||||
|
/>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<a-button type="primary" :disabled="true">保存</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="contract" title="合同">
|
||||||
|
<div class="text-center py-[40px] text-[#86909c]">合同功能待开发</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="bill" title="账单">
|
||||||
|
<div class="text-center py-[40px] text-[#86909c]">账单功能待开发</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="file" title="文件">
|
||||||
|
<div class="text-center py-[40px] text-[#86909c]">文件功能待开发</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</a-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, computed } from 'vue';
|
||||||
|
import { TENANT_TYPE_MAP } from '@/constants';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
detailInfo: any; // 租客详情数据
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
(e: 'edit', id: number): void; // 编辑事件
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const currentTab = ref<string>('data');
|
||||||
|
const memoContent = ref<string>('');
|
||||||
|
|
||||||
|
// 合同执行数量(暂时写死,后续从接口获取)
|
||||||
|
const contractCount = computed(() => {
|
||||||
|
// TODO: 从接口获取合同执行数量
|
||||||
|
// 暂时从 detailInfo 中获取,如果没有则显示 0
|
||||||
|
return props.detailInfo?.contractCount || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 账单逾期数量(暂时写死,后续从接口获取)
|
||||||
|
const overdueBillCount = computed(() => {
|
||||||
|
// TODO: 从接口获取账单逾期数量
|
||||||
|
// 暂时从 detailInfo 中获取,如果没有则显示 0
|
||||||
|
return props.detailInfo?.overdueBillCount || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取租客类型颜色
|
||||||
|
const getTypeColor = (type: number) => {
|
||||||
|
const colorMap: Record<number, string> = {
|
||||||
|
1: 'red', // 企业
|
||||||
|
2: 'blue', // 个人
|
||||||
|
3: 'green', // 非营利组织
|
||||||
|
4: 'orange', // 其他
|
||||||
|
};
|
||||||
|
return colorMap[type] || 'default';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据状态获取信用评分进度条颜色
|
||||||
|
const getCreditScoreColor = (status: number, creditScore: number) => {
|
||||||
|
// 状态:1正常 2欠费 3即将到期 4已到期 5可归档 6已归档
|
||||||
|
// 欠费(状态2)和已到期(状态4)显示红色
|
||||||
|
if (status === 2 || status === 4) {
|
||||||
|
return 'red';
|
||||||
|
}
|
||||||
|
// 正常(状态1)显示绿色
|
||||||
|
if (status === 1) {
|
||||||
|
return 'green';
|
||||||
|
}
|
||||||
|
// 即将到期(状态3)显示橙色
|
||||||
|
if (status === 3) {
|
||||||
|
return 'orange';
|
||||||
|
}
|
||||||
|
// 可归档(状态5)和已归档(状态6)显示青色
|
||||||
|
if (status === 5 || status === 6) {
|
||||||
|
return 'cyan';
|
||||||
|
}
|
||||||
|
// 默认根据分数判断:低分(<60)显示红色,中分(60-80)显示橙色,高分(>80)显示绿色
|
||||||
|
const score = Number(creditScore) || 0;
|
||||||
|
if (score < 60) {
|
||||||
|
return 'red';
|
||||||
|
} else if (score < 80) {
|
||||||
|
return 'orange';
|
||||||
|
} else {
|
||||||
|
return 'green';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化更新时间
|
||||||
|
const formatUpdateTime = (time: string) => {
|
||||||
|
if (!time) return '-';
|
||||||
|
// 将 "2025-07-17 14:21:37" 格式化为 "2025-06-09 11:37:23"
|
||||||
|
return time;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = () => {
|
||||||
|
if (props.detailInfo?.id) {
|
||||||
|
emit('edit', props.detailInfo.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听 visible 变化,重置备忘录内容
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
// 打开时重置备忘录(暂时不保存,所以每次打开都重置)
|
||||||
|
memoContent.value = '';
|
||||||
|
currentTab.value = 'data';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tenant-detail-header {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenant-detail-tabs {
|
||||||
|
:deep(.arco-tabs-content) {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tabs-nav) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-descriptions-item-label) {
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-descriptions-item-value) {
|
||||||
|
color: #1d2129;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
<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="租客类型">
|
||||||
|
<a-select v-model="filterForm.tenantType" placeholder="请选择租客类型">
|
||||||
|
<a-option value="">所有类型</a-option>
|
||||||
|
<a-option
|
||||||
|
v-for="item in TENANT_TYPES"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 租客来源(单选) -->
|
||||||
|
<a-form-item label="租客来源">
|
||||||
|
<a-select
|
||||||
|
v-model="filterForm.tenantSource"
|
||||||
|
placeholder="请选择租客来源"
|
||||||
|
>
|
||||||
|
<a-option value="">所有来源</a-option>
|
||||||
|
<a-option
|
||||||
|
v-for="item in TENANT_SOURCES"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</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 TENANT_STATUS"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 信用评分(范围滑块) -->
|
||||||
|
<a-form-item label="信用评分">
|
||||||
|
<a-slider
|
||||||
|
v-model="filterForm.creditScore"
|
||||||
|
range
|
||||||
|
:min="creditScoreMin"
|
||||||
|
:max="creditScoreMax"
|
||||||
|
:marks="creditScoreMarks"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, computed } from 'vue';
|
||||||
|
import { TENANT_TYPES, TENANT_SOURCES, TENANT_STATUS } from '@/constants';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
tenantList?: any[]; // 租客列表数据,用于提取筛选选项
|
||||||
|
savedFilterState?: {
|
||||||
|
tenantType: number | null;
|
||||||
|
tenantSource: number | null;
|
||||||
|
statusIds: number[];
|
||||||
|
creditScore: number[];
|
||||||
|
} | null; // 保存的筛选状态
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
(
|
||||||
|
e: 'confirm',
|
||||||
|
payload: {
|
||||||
|
tenantType: number | null;
|
||||||
|
tenantSource: number | null;
|
||||||
|
statusIds: number[];
|
||||||
|
creditScore: number[];
|
||||||
|
filteredList: any[]; // 筛选后的列表数据
|
||||||
|
}
|
||||||
|
): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 筛选表单数据
|
||||||
|
const filterForm = ref({
|
||||||
|
tenantType: '' as number | string,
|
||||||
|
tenantSource: '' as number | string,
|
||||||
|
statusIds: [] as number[],
|
||||||
|
creditScore: [0, 100] as number[],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 信用评分范围
|
||||||
|
const creditScoreMin = computed(() => {
|
||||||
|
if (!props.tenantList || props.tenantList.length === 0) return 0;
|
||||||
|
const scores = props.tenantList
|
||||||
|
.map((tenant) => Number(tenant.creditScore))
|
||||||
|
.filter((score) => !isNaN(score) && score >= 0);
|
||||||
|
return scores.length > 0 ? Math.min(...scores) : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const creditScoreMax = computed(() => {
|
||||||
|
if (!props.tenantList || props.tenantList.length === 0) return 100;
|
||||||
|
const scores = props.tenantList
|
||||||
|
.map((tenant) => Number(tenant.creditScore))
|
||||||
|
.filter((score) => !isNaN(score) && score >= 0);
|
||||||
|
return scores.length > 0 ? Math.max(...scores) : 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
const creditScoreMarks = computed(() => {
|
||||||
|
return {
|
||||||
|
[creditScoreMin.value]: `${creditScoreMin.value}`,
|
||||||
|
[creditScoreMax.value]: `${creditScoreMax.value}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化筛选表单
|
||||||
|
const initFilterForm = () => {
|
||||||
|
const min = creditScoreMin.value || 0;
|
||||||
|
const max = creditScoreMax.value || 100;
|
||||||
|
|
||||||
|
// 如果有保存的筛选状态,恢复它
|
||||||
|
if (props.savedFilterState) {
|
||||||
|
filterForm.value = {
|
||||||
|
tenantType:
|
||||||
|
props.savedFilterState.tenantType === null
|
||||||
|
? ''
|
||||||
|
: props.savedFilterState.tenantType,
|
||||||
|
tenantSource:
|
||||||
|
props.savedFilterState.tenantSource === null
|
||||||
|
? ''
|
||||||
|
: props.savedFilterState.tenantSource,
|
||||||
|
statusIds: [...props.savedFilterState.statusIds],
|
||||||
|
creditScore:
|
||||||
|
props.savedFilterState.creditScore.length === 2
|
||||||
|
? [...props.savedFilterState.creditScore]
|
||||||
|
: [min, max],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 否则初始化默认值
|
||||||
|
filterForm.value = {
|
||||||
|
tenantType: '',
|
||||||
|
tenantSource: '',
|
||||||
|
statusIds: [],
|
||||||
|
creditScore: [min, max],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 筛选列表数据
|
||||||
|
const filterTenantList = () => {
|
||||||
|
if (!props.tenantList || props.tenantList.length === 0) return [];
|
||||||
|
|
||||||
|
let filtered = [...props.tenantList];
|
||||||
|
|
||||||
|
// 筛选租客类型
|
||||||
|
if (
|
||||||
|
filterForm.value.tenantType !== '' &&
|
||||||
|
filterForm.value.tenantType !== null &&
|
||||||
|
filterForm.value.tenantType !== undefined &&
|
||||||
|
typeof filterForm.value.tenantType === 'number'
|
||||||
|
) {
|
||||||
|
filtered = filtered.filter(
|
||||||
|
(tenant) => tenant.type === filterForm.value.tenantType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选租客来源
|
||||||
|
if (
|
||||||
|
filterForm.value.tenantSource !== '' &&
|
||||||
|
filterForm.value.tenantSource !== null &&
|
||||||
|
filterForm.value.tenantSource !== undefined &&
|
||||||
|
typeof filterForm.value.tenantSource === 'number'
|
||||||
|
) {
|
||||||
|
filtered = filtered.filter(
|
||||||
|
(tenant) => tenant.source === filterForm.value.tenantSource
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选状态
|
||||||
|
if (filterForm.value.statusIds.length > 0) {
|
||||||
|
filtered = filtered.filter((tenant) =>
|
||||||
|
filterForm.value.statusIds.includes(tenant.status)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选信用评分
|
||||||
|
const [minScore, maxScore] = filterForm.value.creditScore;
|
||||||
|
filtered = filtered.filter((tenant) => {
|
||||||
|
const score = Number(tenant.creditScore);
|
||||||
|
return !isNaN(score) && score >= minScore && score <= maxScore;
|
||||||
|
});
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
const filteredList = filterTenantList();
|
||||||
|
|
||||||
|
// 确保空字符串或非数字值被转换为 null,只传递数字类型
|
||||||
|
const getTenantType = () => {
|
||||||
|
const value = filterForm.value.tenantType;
|
||||||
|
if (
|
||||||
|
value === '' ||
|
||||||
|
value === null ||
|
||||||
|
value === undefined ||
|
||||||
|
typeof value !== 'number'
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTenantSource = () => {
|
||||||
|
const value = filterForm.value.tenantSource;
|
||||||
|
if (
|
||||||
|
value === '' ||
|
||||||
|
value === null ||
|
||||||
|
value === undefined ||
|
||||||
|
typeof value !== 'number'
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
emit('confirm', {
|
||||||
|
tenantType: getTenantType(),
|
||||||
|
tenantSource: getTenantSource(),
|
||||||
|
statusIds: filterForm.value.statusIds,
|
||||||
|
creditScore: filterForm.value.creditScore,
|
||||||
|
filteredList,
|
||||||
|
});
|
||||||
|
handleCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听抽屉打开,初始化数据
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
// 延迟初始化,确保 creditScoreMin 和 creditScoreMax 已计算
|
||||||
|
setTimeout(() => {
|
||||||
|
initFilterForm();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听保存的筛选状态变化,更新表单
|
||||||
|
watch(
|
||||||
|
() => props.savedFilterState,
|
||||||
|
() => {
|
||||||
|
if (props.visible) {
|
||||||
|
// 延迟更新,确保 creditScoreMin 和 creditScoreMax 已计算
|
||||||
|
setTimeout(() => {
|
||||||
|
initFilterForm();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(label.arco-form-item-label) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-slider-mark) {
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Breadcrumb :items="['menu.business', 'menu.business.tenant-file']" />
|
||||||
|
<a-card class="general-card full_height">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title_between">
|
||||||
|
<p class="title">{{ t('menu.business.tenant-file') }}</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"
|
||||||
|
@press-enter="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-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 使用新的 DataTable 组件 -->
|
||||||
|
<DataTable
|
||||||
|
:data="renderData"
|
||||||
|
:columns="tenantColumn"
|
||||||
|
:loading="loading"
|
||||||
|
:total="total"
|
||||||
|
:current="current"
|
||||||
|
:page-size="pageSize"
|
||||||
|
@page-change="onPageChange"
|
||||||
|
@page-size-change="onPageSizeChange"
|
||||||
|
>
|
||||||
|
<template #filingType="{ record }">
|
||||||
|
<a-tag :color="getFilingTypeColor(record?.filingType)">
|
||||||
|
{{ record?.filingTypeName || '-' }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<template #creditScore="{ record }">
|
||||||
|
<a-progress
|
||||||
|
:show-text="false"
|
||||||
|
:percent="Number(record.creditScore) || 0"
|
||||||
|
:color="getCreditScoreColor(record?.creditScore)"
|
||||||
|
:style="{ width: '120px' }"
|
||||||
|
/>
|
||||||
|
<span class="ml-[10px]">{{ record.creditScore }}</span>
|
||||||
|
</template>
|
||||||
|
<template #operate="{ record }">
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="handleDetail(record.id)">查看</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<!-- 筛选抽屉 -->
|
||||||
|
<tenant-filing-filter-drawer
|
||||||
|
v-model:visible="showListFilterDrawer"
|
||||||
|
:tenant-list="tenantList"
|
||||||
|
:saved-filter-state="savedFilterState"
|
||||||
|
@confirm="handleFilterConfirm"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 租客详情抽屉 -->
|
||||||
|
<tenant-info-drawer
|
||||||
|
v-model:visible="showTenantInfoDrawer"
|
||||||
|
:detail-info="currentRecordInfo"
|
||||||
|
/>
|
||||||
|
</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 { getTenantFilingList, getTenantDetail } from '@/api/business';
|
||||||
|
import { ProjectPaginationParams } from '@/types/comm';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import { TENANT_FILING_TYPE_MAP } from '@/constants';
|
||||||
|
import TenantFilingFilterDrawer from '../drawer/tenant-filing-filter-drawer.vue';
|
||||||
|
import TenantInfoDrawer from '../drawer/tenant-info-drawer.vue';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { loading } = useLoading(false);
|
||||||
|
const showTenantInfoDrawer = ref<boolean>(false);
|
||||||
|
const showListFilterDrawer = ref<boolean>(false);
|
||||||
|
const isLoadingDetail = ref<boolean>(false);
|
||||||
|
const currentRecordInfo = ref<any | null>(null);
|
||||||
|
const tenantList = ref<any[]>([]);
|
||||||
|
// 保存的筛选状态
|
||||||
|
const savedFilterState = ref<{
|
||||||
|
tenantType: number | null;
|
||||||
|
tenantSource: number | null;
|
||||||
|
filingTypeIds: number[];
|
||||||
|
creditScore: number[];
|
||||||
|
} | null>(null);
|
||||||
|
const {
|
||||||
|
data: renderData,
|
||||||
|
columns: tenantColumn,
|
||||||
|
total,
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
setData,
|
||||||
|
setColumns,
|
||||||
|
setTotal,
|
||||||
|
setCurrent,
|
||||||
|
setPageSize,
|
||||||
|
setLoading,
|
||||||
|
} = useDataTable({
|
||||||
|
initialPage: 1,
|
||||||
|
initialPageSize: 10,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通用空值处理函数
|
||||||
|
const renderEmpty = (value: any) => (value && value != '' ? value : '-');
|
||||||
|
|
||||||
|
// 列配置
|
||||||
|
const columns: TableColumnData[] = [
|
||||||
|
{
|
||||||
|
title: '租客名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
render: ({ record }) => renderEmpty(record.name),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '租客类型',
|
||||||
|
dataIndex: 'typeName',
|
||||||
|
key: 'typeName',
|
||||||
|
render: ({ record }) => renderEmpty(record.typeName),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '联系人',
|
||||||
|
dataIndex: 'contactName',
|
||||||
|
key: 'contactName',
|
||||||
|
render: ({ record }) => renderEmpty(record.contactName),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '联系人电话',
|
||||||
|
dataIndex: 'contactTel',
|
||||||
|
key: 'contactTel',
|
||||||
|
render: ({ record }) => renderEmpty(record.contactTel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '联系人邮箱',
|
||||||
|
dataIndex: 'contactEmail',
|
||||||
|
key: 'contactEmail',
|
||||||
|
render: ({ record }) => renderEmpty(record.contactEmail),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '租客来源',
|
||||||
|
dataIndex: 'sourceName',
|
||||||
|
key: 'sourceName',
|
||||||
|
render: ({ record }) => renderEmpty(record.sourceName),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '归档原因',
|
||||||
|
dataIndex: 'filingTypeName',
|
||||||
|
key: 'filingTypeName',
|
||||||
|
slotName: 'filingType',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '信用评分',
|
||||||
|
dataIndex: 'creditScore',
|
||||||
|
key: 'creditScore',
|
||||||
|
slotName: 'creditScore',
|
||||||
|
},
|
||||||
|
{ title: '操作', key: 'operate', slotName: 'operate', width: 200 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const queryParams = ref<ProjectPaginationParams>({
|
||||||
|
page: current.value,
|
||||||
|
page_size: pageSize.value,
|
||||||
|
keyword: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const getTenantFilingListData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const { code, data } = await getTenantFilingList(queryParams.value);
|
||||||
|
if (code === 200 && data) {
|
||||||
|
const { list, page } = data as {
|
||||||
|
list?: any[];
|
||||||
|
page?: { total?: number };
|
||||||
|
};
|
||||||
|
tenantList.value = Array.isArray(list) ? list : [];
|
||||||
|
setData(tenantList.value);
|
||||||
|
setTotal(page?.total ? page.total : 0);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTenantData = async (id: number) => {
|
||||||
|
if (currentRecordInfo.value && currentRecordInfo.value?.id === id) return;
|
||||||
|
isLoadingDetail.value = true;
|
||||||
|
try {
|
||||||
|
const { code, data } = await getTenantDetail(id);
|
||||||
|
if (code === 200 && data) {
|
||||||
|
// 根据接口返回结构,数据在 data.detail 中
|
||||||
|
const tenantData = (data as { detail?: any }).detail;
|
||||||
|
if (tenantData) {
|
||||||
|
currentRecordInfo.value = tenantData;
|
||||||
|
} else {
|
||||||
|
currentRecordInfo.value = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentRecordInfo.value = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取租客详情失败:', error);
|
||||||
|
currentRecordInfo.value = null;
|
||||||
|
} finally {
|
||||||
|
isLoadingDetail.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilterConfirm = (payload: {
|
||||||
|
tenantType: number | null;
|
||||||
|
tenantSource: number | null;
|
||||||
|
filingTypeIds: number[];
|
||||||
|
creditScore: number[];
|
||||||
|
filteredList: any[]; // 筛选后的列表数据
|
||||||
|
}) => {
|
||||||
|
// 保存筛选状态
|
||||||
|
savedFilterState.value = {
|
||||||
|
tenantType: payload.tenantType,
|
||||||
|
tenantSource: payload.tenantSource,
|
||||||
|
filingTypeIds: [...payload.filingTypeIds],
|
||||||
|
creditScore: [...payload.creditScore],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新查询参数(用于后端筛选)
|
||||||
|
// keyword 已经在 queryParams 中,由搜索框输入
|
||||||
|
// 只有当值不为 null 或 undefined 时才设置,否则删除该字段
|
||||||
|
if (payload.tenantType !== null && payload.tenantType !== undefined) {
|
||||||
|
(queryParams.value as any).type = payload.tenantType;
|
||||||
|
} else {
|
||||||
|
delete (queryParams.value as any).type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.tenantSource !== null && payload.tenantSource !== undefined) {
|
||||||
|
(queryParams.value as any).source = payload.tenantSource;
|
||||||
|
} else {
|
||||||
|
delete (queryParams.value as any).source;
|
||||||
|
}
|
||||||
|
|
||||||
|
(queryParams.value as any).filingType =
|
||||||
|
payload.filingTypeIds.length > 0 ? payload.filingTypeIds : undefined;
|
||||||
|
(queryParams.value as any).creditScoreMin = payload.creditScore?.[0];
|
||||||
|
(queryParams.value as any).creditScoreMax = payload.creditScore?.[1];
|
||||||
|
|
||||||
|
// 重置到第一页
|
||||||
|
setCurrent(1);
|
||||||
|
current.value = 1;
|
||||||
|
queryParams.value.page = 1;
|
||||||
|
|
||||||
|
// 重新加载列表数据
|
||||||
|
getTenantFilingListData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分页事件处理
|
||||||
|
const onPageChange = (page: number) => {
|
||||||
|
setCurrent(page);
|
||||||
|
current.value = page;
|
||||||
|
queryParams.value.page = page;
|
||||||
|
getTenantFilingListData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPageSizeChange = (size: number) => {
|
||||||
|
setPageSize(size);
|
||||||
|
pageSize.value = size;
|
||||||
|
queryParams.value.page_size = size;
|
||||||
|
// 切换页容量时重置到第一页
|
||||||
|
setCurrent(1);
|
||||||
|
current.value = 1;
|
||||||
|
queryParams.value.page = 1;
|
||||||
|
getTenantFilingListData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDetail = async (id: number) => {
|
||||||
|
if (isLoadingDetail.value) return;
|
||||||
|
// 先清空,确保能触发更新
|
||||||
|
currentRecordInfo.value = null;
|
||||||
|
await getTenantData(id);
|
||||||
|
// 确保数据已加载后再打开抽屉
|
||||||
|
if (currentRecordInfo.value) {
|
||||||
|
showTenantInfoDrawer.value = true;
|
||||||
|
} else {
|
||||||
|
Message.error('获取租客详情失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
// 搜索时重置到第一页
|
||||||
|
setCurrent(1);
|
||||||
|
current.value = 1;
|
||||||
|
queryParams.value.page = 1;
|
||||||
|
getTenantFilingListData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilter = () => {
|
||||||
|
showListFilterDrawer.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取归档原因颜色
|
||||||
|
const getFilingTypeColor = (filingType: number) => {
|
||||||
|
const colorMap: Record<number, string> = {
|
||||||
|
1: 'blue', // 到期退租
|
||||||
|
2: 'green', // 提前退租
|
||||||
|
3: 'red', // 非正常终止
|
||||||
|
4: 'orange', // 签约未履行
|
||||||
|
5: 'cyan', // 其他原因
|
||||||
|
};
|
||||||
|
return colorMap[filingType] || 'default';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据信用评分获取进度条颜色
|
||||||
|
const getCreditScoreColor = (creditScore: number) => {
|
||||||
|
const score = Number(creditScore) || 0;
|
||||||
|
if (score < 60) {
|
||||||
|
return 'red';
|
||||||
|
} else if (score < 80) {
|
||||||
|
return 'orange';
|
||||||
|
} else {
|
||||||
|
return 'green';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setColumns(columns);
|
||||||
|
getTenantFilingListData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>tenant-list</div>
|
|
||||||
</template>
|
|
||||||
@@ -0,0 +1,523 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Breadcrumb :items="['menu.business', 'menu.business.tenant-list']" />
|
||||||
|
<a-card class="general-card full_height">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-title_between">
|
||||||
|
<p class="title">{{ t('menu.business.tenant-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"
|
||||||
|
@press-enter="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="handleAddTenant">
|
||||||
|
<template #icon>
|
||||||
|
<icon-plus />
|
||||||
|
</template>
|
||||||
|
{{ t('add-tenant') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 使用新的 DataTable 组件 -->
|
||||||
|
<DataTable
|
||||||
|
:data="renderData"
|
||||||
|
:columns="tenantColumn"
|
||||||
|
:loading="loading"
|
||||||
|
:total="total"
|
||||||
|
:current="current"
|
||||||
|
:page-size="pageSize"
|
||||||
|
@page-change="onPageChange"
|
||||||
|
@page-size-change="onPageSizeChange"
|
||||||
|
>
|
||||||
|
<template #status="{ record }">
|
||||||
|
<a-tag :color="getTagsColor(record?.status)">{{
|
||||||
|
record?.statusStr
|
||||||
|
}}</a-tag>
|
||||||
|
</template>
|
||||||
|
<template #creditScore="{ record }">
|
||||||
|
<a-progress
|
||||||
|
:show-text="false"
|
||||||
|
:percent="Number(record.creditScore) || 0"
|
||||||
|
:color="getCreditScoreColor(record?.status, record?.creditScore)"
|
||||||
|
:style="{ width: '120px' }"
|
||||||
|
/>
|
||||||
|
<span class="ml-[10px]">{{ record.creditScore }}</span>
|
||||||
|
</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="handleEditTenant(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-button @click="handleFilingTenant(record.id)">
|
||||||
|
归档租客
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<!-- 筛选抽屉 -->
|
||||||
|
<tenant-list-filter-drawer
|
||||||
|
v-model:visible="showListFilterDrawer"
|
||||||
|
:tenant-list="tenantList"
|
||||||
|
:saved-filter-state="savedFilterState"
|
||||||
|
@confirm="handleFilterConfirm"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 租客表单抽屉 -->
|
||||||
|
<tenant-form-drawer
|
||||||
|
v-model:visible="showDrawer"
|
||||||
|
:is-edit="isEdit"
|
||||||
|
:record="currentRecordInfo"
|
||||||
|
@submitted="handleFormSubmitted"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 租客详情抽屉 -->
|
||||||
|
<tenant-info-drawer
|
||||||
|
v-model:visible="showTenantInfoDrawer"
|
||||||
|
:detail-info="currentRecordInfo"
|
||||||
|
@edit="handleEditFromDetail"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 归档租客对话框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="showFilingModal"
|
||||||
|
title="归档租客"
|
||||||
|
@ok="handleFilingConfirm"
|
||||||
|
@cancel="handleFilingCancel"
|
||||||
|
>
|
||||||
|
<a-form layout="vertical">
|
||||||
|
<a-form-item label="请选择归档原因" required>
|
||||||
|
<a-select v-model="filingType" placeholder="请选择归档原因">
|
||||||
|
<a-option
|
||||||
|
v-for="item in TENANT_FILING_TYPES"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</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 {
|
||||||
|
getTenantList,
|
||||||
|
getTenantDetail,
|
||||||
|
delTenant,
|
||||||
|
filingTenant,
|
||||||
|
} from '@/api/business';
|
||||||
|
import { ProjectPaginationParams } from '@/types/comm';
|
||||||
|
import { Message, Modal } from '@arco-design/web-vue';
|
||||||
|
import { TENANT_FILING_TYPES } from '@/constants';
|
||||||
|
import TenantListFilterDrawer from '../drawer/tenant-list-filter-drawer.vue';
|
||||||
|
import TenantFormDrawer from '../drawer/tenant-form-drawer.vue';
|
||||||
|
import TenantInfoDrawer from '../drawer/tenant-info-drawer.vue';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { loading } = useLoading(false);
|
||||||
|
const showDrawer = ref<boolean>(false);
|
||||||
|
const showTenantInfoDrawer = ref<boolean>(false);
|
||||||
|
const showListFilterDrawer = ref<boolean>(false);
|
||||||
|
const showFilingModal = ref<boolean>(false);
|
||||||
|
const currentTenantId = ref<number | null>(null);
|
||||||
|
const filingType = ref<number | undefined>(undefined);
|
||||||
|
// 保存的筛选状态
|
||||||
|
const savedFilterState = ref<{
|
||||||
|
tenantType: number | null;
|
||||||
|
tenantSource: number | null;
|
||||||
|
statusIds: number[];
|
||||||
|
creditScore: 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 tenantList = ref<any[]>([]);
|
||||||
|
const {
|
||||||
|
data: renderData,
|
||||||
|
columns: tenantColumn,
|
||||||
|
total,
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
setData,
|
||||||
|
setColumns,
|
||||||
|
setTotal,
|
||||||
|
setCurrent,
|
||||||
|
setPageSize,
|
||||||
|
setLoading,
|
||||||
|
} = useDataTable({
|
||||||
|
initialPage: 1,
|
||||||
|
initialPageSize: 10,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通用空值处理函数
|
||||||
|
const renderEmpty = (value: any) => (value && value != '' ? value : '-');
|
||||||
|
|
||||||
|
// 列配置
|
||||||
|
const columns: TableColumnData[] = [
|
||||||
|
{
|
||||||
|
title: '租客名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
render: ({ record }) => renderEmpty(record.name),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '租客类型',
|
||||||
|
dataIndex: 'typeName',
|
||||||
|
key: 'typeName',
|
||||||
|
render: ({ record }) => renderEmpty(record.typeName),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '联系人',
|
||||||
|
dataIndex: 'contactName',
|
||||||
|
key: 'contactName',
|
||||||
|
render: ({ record }) => renderEmpty(record.contactName),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '联系人电话',
|
||||||
|
dataIndex: 'contactTel',
|
||||||
|
key: 'contactTel',
|
||||||
|
render: ({ record }) => renderEmpty(record.contactTel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '联系人邮箱',
|
||||||
|
dataIndex: 'contactEmail',
|
||||||
|
key: 'contactEmail',
|
||||||
|
render: ({ record }) => renderEmpty(record.contactEmail),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '租客来源',
|
||||||
|
dataIndex: 'sourceName',
|
||||||
|
key: 'sourceName',
|
||||||
|
render: ({ record }) => renderEmpty(record.sourceName),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '租客状态',
|
||||||
|
dataIndex: 'statusStr',
|
||||||
|
key: 'statusStr',
|
||||||
|
slotName: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '信用评分',
|
||||||
|
dataIndex: 'creditScore',
|
||||||
|
key: 'creditScore',
|
||||||
|
slotName: 'creditScore',
|
||||||
|
},
|
||||||
|
{ title: '操作', key: 'operate', slotName: 'operate', width: 200 },
|
||||||
|
];
|
||||||
|
const queryParams = ref<ProjectPaginationParams>({
|
||||||
|
page: current.value,
|
||||||
|
page_size: pageSize.value,
|
||||||
|
keyword: '',
|
||||||
|
});
|
||||||
|
const getTenantListData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const { code, data } = await getTenantList(queryParams.value);
|
||||||
|
if (code === 200 && data) {
|
||||||
|
const { list, page } = data as {
|
||||||
|
list?: any[];
|
||||||
|
page?: { total?: number };
|
||||||
|
};
|
||||||
|
tenantList.value = Array.isArray(list) ? list : [];
|
||||||
|
setData(tenantList.value);
|
||||||
|
setTotal(page?.total ? page.total : 0);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
const getTenantData = async (id: number, edit: boolean) => {
|
||||||
|
if (currentRecordInfo.value && currentRecordInfo.value?.id === id && !edit)
|
||||||
|
return;
|
||||||
|
if (edit) {
|
||||||
|
isLoadingEdit.value = true;
|
||||||
|
} else {
|
||||||
|
isLoadingDetail.value = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { code, data } = await getTenantDetail(id);
|
||||||
|
if (code === 200 && data) {
|
||||||
|
// 根据接口返回结构,数据在 data.detail 中
|
||||||
|
const tenantData = (data as { detail?: any }).detail;
|
||||||
|
if (tenantData) {
|
||||||
|
currentRecordInfo.value = tenantData;
|
||||||
|
} else {
|
||||||
|
currentRecordInfo.value = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentRecordInfo.value = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取租客详情失败:', error);
|
||||||
|
currentRecordInfo.value = null;
|
||||||
|
} finally {
|
||||||
|
isLoadingEdit.value = false;
|
||||||
|
isLoadingDetail.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleFilterConfirm = (payload: {
|
||||||
|
tenantType: number | null;
|
||||||
|
tenantSource: number | null;
|
||||||
|
statusIds: number[];
|
||||||
|
creditScore: number[];
|
||||||
|
filteredList: any[]; // 筛选后的列表数据
|
||||||
|
}) => {
|
||||||
|
// 保存筛选状态
|
||||||
|
savedFilterState.value = {
|
||||||
|
tenantType: payload.tenantType,
|
||||||
|
tenantSource: payload.tenantSource,
|
||||||
|
statusIds: [...payload.statusIds],
|
||||||
|
creditScore: [...payload.creditScore],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新查询参数(用于后端筛选)
|
||||||
|
// keyword 已经在 queryParams 中,由搜索框输入
|
||||||
|
// 只有当值不为 null 或 undefined 时才设置,否则删除该字段
|
||||||
|
if (payload.tenantType !== null && payload.tenantType !== undefined) {
|
||||||
|
(queryParams.value as any).type = payload.tenantType;
|
||||||
|
} else {
|
||||||
|
delete (queryParams.value as any).type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.tenantSource !== null && payload.tenantSource !== undefined) {
|
||||||
|
(queryParams.value as any).source = payload.tenantSource;
|
||||||
|
} else {
|
||||||
|
delete (queryParams.value as any).source;
|
||||||
|
}
|
||||||
|
|
||||||
|
(queryParams.value as any).status =
|
||||||
|
payload.statusIds.length > 0 ? payload.statusIds : undefined;
|
||||||
|
(queryParams.value as any).creditScoreMin = payload.creditScore?.[0];
|
||||||
|
(queryParams.value as any).creditScoreMax = payload.creditScore?.[1];
|
||||||
|
|
||||||
|
// 重置到第一页
|
||||||
|
setCurrent(1);
|
||||||
|
current.value = 1;
|
||||||
|
queryParams.value.page = 1;
|
||||||
|
|
||||||
|
// 重新加载列表数据
|
||||||
|
getTenantListData();
|
||||||
|
};
|
||||||
|
const handleDel = async (id: number) => {
|
||||||
|
const { code, message } = await delTenant(id);
|
||||||
|
if (code === 200) {
|
||||||
|
Message.success(message || '删除成功');
|
||||||
|
// 重新加载列表数据
|
||||||
|
await getTenantListData();
|
||||||
|
} else {
|
||||||
|
Message.error(message || '删除失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 分页事件处理
|
||||||
|
const onPageChange = (page: number) => {
|
||||||
|
setCurrent(page);
|
||||||
|
current.value = page;
|
||||||
|
queryParams.value.page = page;
|
||||||
|
getTenantListData();
|
||||||
|
};
|
||||||
|
const onPageSizeChange = (size: number) => {
|
||||||
|
setPageSize(size);
|
||||||
|
pageSize.value = size;
|
||||||
|
queryParams.value.page_size = size;
|
||||||
|
// 切换页容量时重置到第一页
|
||||||
|
setCurrent(1);
|
||||||
|
current.value = 1;
|
||||||
|
queryParams.value.page = 1;
|
||||||
|
getTenantListData();
|
||||||
|
};
|
||||||
|
const handleAddTenant = () => {
|
||||||
|
isEdit.value = false;
|
||||||
|
currentRecordInfo.value = null;
|
||||||
|
showDrawer.value = true;
|
||||||
|
};
|
||||||
|
const handleEditTenant = async (id: number) => {
|
||||||
|
if (isLoadingEdit.value) return;
|
||||||
|
isEdit.value = true;
|
||||||
|
currentRecordInfo.value = null; // 先清空,确保能触发更新
|
||||||
|
// 先获取数据,再打开抽屉
|
||||||
|
await getTenantData(id, true);
|
||||||
|
// 确保数据已加载后再打开抽屉
|
||||||
|
if (currentRecordInfo.value) {
|
||||||
|
showDrawer.value = true;
|
||||||
|
} else {
|
||||||
|
Message.error('获取租客详情失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleFormSubmitted = () => {
|
||||||
|
// 表单提交成功后,重新加载列表数据
|
||||||
|
getTenantListData();
|
||||||
|
};
|
||||||
|
const handleDetail = async (id: number) => {
|
||||||
|
if (isLoadingDetail.value) return;
|
||||||
|
// 先清空,确保能触发更新
|
||||||
|
currentRecordInfo.value = null;
|
||||||
|
await getTenantData(id, false);
|
||||||
|
// 确保数据已加载后再打开抽屉
|
||||||
|
if (currentRecordInfo.value) {
|
||||||
|
showTenantInfoDrawer.value = true;
|
||||||
|
} else {
|
||||||
|
Message.error('获取租客详情失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从详情页点击编辑
|
||||||
|
const handleEditFromDetail = (id: number) => {
|
||||||
|
showTenantInfoDrawer.value = false;
|
||||||
|
handleEditTenant(id);
|
||||||
|
};
|
||||||
|
const handleSearch = () => {
|
||||||
|
// 搜索时重置到第一页
|
||||||
|
setCurrent(1);
|
||||||
|
current.value = 1;
|
||||||
|
queryParams.value.page = 1;
|
||||||
|
getTenantListData();
|
||||||
|
};
|
||||||
|
const handleFilter = () => {
|
||||||
|
showListFilterDrawer.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 归档租客
|
||||||
|
const handleFilingTenant = (id: number) => {
|
||||||
|
currentTenantId.value = id;
|
||||||
|
filingType.value = undefined;
|
||||||
|
showFilingModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认归档
|
||||||
|
const handleFilingConfirm = async () => {
|
||||||
|
if (!filingType.value) {
|
||||||
|
Message.warning('请选择归档原因');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!currentTenantId.value) {
|
||||||
|
Message.error('租客ID不存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { code, message } = await filingTenant({
|
||||||
|
id: currentTenantId.value,
|
||||||
|
filingType: filingType.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (code === 200) {
|
||||||
|
Message.success(message || '归档成功');
|
||||||
|
showFilingModal.value = false;
|
||||||
|
filingType.value = undefined;
|
||||||
|
currentTenantId.value = null;
|
||||||
|
// 重新加载列表数据
|
||||||
|
await getTenantListData();
|
||||||
|
} else {
|
||||||
|
Message.error(message || '归档失败');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
Message.error(error.message || '归档失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消归档
|
||||||
|
const handleFilingCancel = () => {
|
||||||
|
showFilingModal.value = false;
|
||||||
|
filingType.value = undefined;
|
||||||
|
currentTenantId.value = null;
|
||||||
|
};
|
||||||
|
const getTagsColor = (status) => {
|
||||||
|
// "status": 1, //状态:1正常 2欠费 3即将到期 4已到期 5可归档 6已归档
|
||||||
|
const colorMap = {
|
||||||
|
1: 'green',
|
||||||
|
2: 'red',
|
||||||
|
3: 'orange',
|
||||||
|
4: 'red',
|
||||||
|
5: 'cyan',
|
||||||
|
6: 'cyan',
|
||||||
|
};
|
||||||
|
return colorMap[status];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据状态获取信用评分进度条颜色
|
||||||
|
const getCreditScoreColor = (status, creditScore) => {
|
||||||
|
// 状态:1正常 2欠费 3即将到期 4已到期 5可归档 6已归档
|
||||||
|
// 欠费(状态2)和已到期(状态4)显示红色
|
||||||
|
if (status === 2 || status === 4) {
|
||||||
|
return 'red';
|
||||||
|
}
|
||||||
|
// 正常(状态1)显示绿色
|
||||||
|
if (status === 1) {
|
||||||
|
return 'green';
|
||||||
|
}
|
||||||
|
// 即将到期(状态3)显示橙色
|
||||||
|
if (status === 3) {
|
||||||
|
return 'orange';
|
||||||
|
}
|
||||||
|
// 可归档(状态5)和已归档(状态6)显示青色
|
||||||
|
if (status === 5 || status === 6) {
|
||||||
|
return 'cyan';
|
||||||
|
}
|
||||||
|
// 默认根据分数判断:低分(<60)显示红色,中分(60-80)显示橙色,高分(>80)显示绿色
|
||||||
|
const score = Number(creditScore) || 0;
|
||||||
|
if (score < 60) {
|
||||||
|
return 'red';
|
||||||
|
} else if (score < 80) {
|
||||||
|
return 'orange';
|
||||||
|
} else {
|
||||||
|
return 'green';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
setColumns(columns);
|
||||||
|
getTenantListData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user