feat: 系统配置项目设置

This commit is contained in:
Zzz1zz 2025-07-19 01:34:33 +08:00
parent bcecfc6846
commit 08700f48da
3 changed files with 387 additions and 1 deletions

View file

@ -143,6 +143,10 @@ export interface UpdateUserRoleParam {
export type SafeDefaultPage = 1 | 2 | 3; // 默认登录页:1工作台 2项目管理 3房源剖视 export type SafeDefaultPage = 1 | 2 | 3; // 默认登录页:1工作台 2项目管理 3房源剖视
export type SafeLoginTimeType = 1 | 2 | 3 | 4; // 登录状态保存时间:1:7天, 2:1天, 3:12小时, 4:1小时 export type SafeLoginTimeType = 1 | 2 | 3 | 4; // 登录状态保存时间:1:7天, 2:1天, 3:12小时, 4:1小时
export type SafeMoreLogin = 1 | 2; // 多点登录支持:1支持 2不支持 export type SafeMoreLogin = 1 | 2; // 多点登录支持:1支持 2不支持
export type DefaultPriceType = 1 | 2; // 默认单价:1 元/㎡*月 , 2 元/㎡*天
export type AreaAccuracyType = 1 | 2 | 3; // 面积精度:1:两位数,2:1位数,3:整数
export type ExpireSoonType = 1 | 2 | 3; // 房源即将到期设置:1:30天,2:60天,3:90天
export type CumulativeDayType = 1 | 2 | 3 | 4; // 累计出租率天数类型:1:按本年累计天数计算(从1月1日至今),2:按 本年整年天数 计算(本年:365天),3:按 本月累计天数 计算(从本月1日至今),4:按 本月整月天数 计算(本月:31天)
export interface SafeParams { export interface SafeParams {
default_url_typee: SafeDefaultPage; default_url_typee: SafeDefaultPage;
@ -158,6 +162,32 @@ export interface SafeInfo {
createdAt?: string; createdAt?: string;
updatedAt?: string; updatedAt?: string;
} }
export interface SettingProjectParam {
default_price_type: DefaultPriceType;
area_accuracy_type: AreaAccuracyType;
expire_soon_type: ExpireSoonType;
rent_rate_type: number; // 出租率:1:在租面积 / 总可出租面积 * 100 = 出租率
rent_area_type: string; // 在租面积定义:1:合同起始日期,2:合同免租期,3:合同装修期,4:房源保留 (ps:1,2 以逗号隔开)
rentable_area_type: string; // 总可出租面积定义:1:房源招商,2:房源保留,3:房源已到期,4:房源下架 (ps:1,2 以逗号隔开)
cumulative_rent_rate_type: number; // 累计出租率:1:∑(在租面积 × 在租天数) / ∑(可出租面积 × 可出租天数) = 累计出租率
cumulative_day_type: CumulativeDayType;
}
export interface projectSettingInfo {
id: number;
systemTenantId: number; // 租户id
defaultPriceType: DefaultPriceType;
areaAccuracyType: AreaAccuracyType;
expireSoonType: ExpireSoonType;
rentRateType: number; // 出租率:1:在租面积 / 总可出租面积 * 100 = 出租率
rentAreaType: string; // 在租面积定义:1:合同起始日期,2:合同免租期,3:合同装修期,4:房源保留 (ps:1,2 以逗号隔开)
rentableAreaType: string; // 总可出租面积定义:1:房源招商,2:房源保留,3:房源已到期,4:房源下架 (ps:1,2 以逗号隔开)
cumulativeRentRateType: number; // 累计出租率:1:∑(在租面积 × 在租天数) / ∑(可出租面积 × 可出租天数) = 累计出租率
cumulativeDayType: CumulativeDayType;
createdAt?: string;
updatedAt?: string;
}
// ==================== 响应类型别名 ==================== // ==================== 响应类型别名 ====================
export type DepartmentFormRes = HttpResponse<DepartmentIdData>; export type DepartmentFormRes = HttpResponse<DepartmentIdData>;
export type DepartmentList = DepartmentItem[]; export type DepartmentList = DepartmentItem[];
@ -173,6 +203,9 @@ export type RoleListRes = HttpResponse<RoleList>;
export type PermissionRes = HttpResponse<UserPermissionItem>; export type PermissionRes = HttpResponse<UserPermissionItem>;
export type RolePermissionRes = HttpResponse<UserPermissionItemList>; export type RolePermissionRes = HttpResponse<UserPermissionItemList>;
export type SafeInfoRes = HttpResponse<{ safeSetting: SafeInfo }>; export type SafeInfoRes = HttpResponse<{ safeSetting: SafeInfo }>;
export type SettingProjectRes = HttpResponse<{
projectSetting: projectSettingInfo;
}>;
// ==================== 部门相关 API ==================== // ==================== 部门相关 API ====================
export function createDepartment( export function createDepartment(
@ -291,3 +324,13 @@ export function getSafeSettingInfo(): Promise<SafeInfoRes> {
export function saveSafeSetting(params: SafeParams): Promise<HttpResponse> { export function saveSafeSetting(params: SafeParams): Promise<HttpResponse> {
return axios.post('setting/saveSafeSetting', params); return axios.post('setting/saveSafeSetting', params);
} }
// 项目设置信息
export function getProjectSetting(): Promise<SettingProjectRes> {
return axios.get('setting/getProjectSetting');
}
// 项目设置信息保存
export function saveProjectSetting(
params: SettingProjectParam
): Promise<HttpResponse> {
return axios.post('setting/saveProjectSetting', params);
}

View file

@ -0,0 +1,59 @@
const projectSetting = [
{ id: 1, text: '项目与房源' },
{ id: 2, text: '出租率' },
{ id: 3, text: '累计出租率' },
];
const projectHouse = [
{
title: '默认单价',
options: [
{ type: 1, label: '元/㎡*月' },
{ type: 2, label: '元/㎡*天' },
],
},
{
title: '面积精度',
options: [
{ type: 1, label: '2位数' },
{ type: 2, label: '1位数' },
{ type: 3, label: '整数' },
],
},
{
title: '房源即将到期设置',
options: [
{ type: 1, label: '30' },
{ type: 2, label: '60' },
{ type: 3, label: '90' },
],
},
];
// 在租面积
const inRentedAreaOptions = [
{ value: '1', label: '合同起始日期' },
{ value: '2', label: '合同免租期' },
{ value: '3', label: '合同装修期' },
{ value: '4', label: '房源保留' },
];
// 可租面积
const availableAreaOptions = [
{ value: '1', label: '房源招商' },
{ value: '2', label: '房源保留' },
{ value: '3', label: '房源保留' },
{ value: '4', label: '房源下架' },
];
// 天数定义
const dayeDfinition = [
{ value: 1, label: '按本年累计天数计算(从1月1日至今)' },
{ value: 2, label: '按本年整年天数 计算(本年:365天)' },
{ value: 3, label: '按本月累计天数 计算(从本月1日至今)' },
{ value: 4, label: '按本月整月天数 计算(本月:31天)' },
];
export {
projectSetting,
projectHouse,
inRentedAreaOptions,
availableAreaOptions,
dayeDfinition,
};

View file

@ -1,3 +1,287 @@
<template> <template>
<div> Data Dictionary </div> <div class="system-setting-general">
<Breadcrumb
:items="['menu.setting', 'menu.setting.system', 'setting.general']"
/>
<a-card class="full_height" :title="t('setting.project')">
<a-row :gutter="12">
<a-col :span="6">
<ul class="project_setting_list">
<li
v-for="item in projectSetting"
:key="item.id"
:class="[
'project_setting_item',
{ isChoose: projectSettingId === item.id },
]"
@click="chooseProjectSetting(item.id)"
>
{{ item.text }}
</li>
</ul>
</a-col>
<a-col :span="18">
<a-card v-if="projectSettingInfoData" :title="cardTitle">
<template #extra>
<a-button type="primary" @click="handleSaveSafe">保存</a-button>
</template> </template>
<ul v-if="projectSettingId === 1" class="project_house_list">
<li
v-for="(item, index) in projectHouse"
:key="index"
class="project_house_item"
>
<h4>{{ item.title }}</h4>
<ul class="options">
<li
v-for="(subItem, subIndex) in item.options"
:key="subIndex"
:class="[
'subItem',
{ isActive: isActiveOption(index, subItem.type) },
]"
@click="onOptionClick(index, subItem.type)"
>
{{ subItem.label }}</li
>
</ul>
</li>
</ul>
<div v-if="projectSettingId === 2" class="in_rented_area">
<h3 class="title">出租率(瞬时出租率)</h3>
<p class="tips">用于衡量某一时刻资产的出租情况。</p>
<p class="desc"
>在租面积 / (可租面积 + 在租面积) * 100% = 出租率</p
>
<h4 class="title_item">在租面积定义</h4>
<a-checkbox-group
v-model="rentAreaTypeArr"
:options="inRentedAreaOptions"
/>
<h4 class="title_item">可租面积定义</h4>
<a-checkbox-group
v-model="rentableAreaTypeArr"
:options="availableAreaOptions"
/>
</div>
<div v-if="projectSettingId === 3" class="cumulative_leasing_rate">
<h3 class="title">累计出租率(时空加权出租率)</h3>
<p class="tips"
>在选定时间段内,项目中房源所出租天数与总面积总天数的比值。用于衡量项目整体的出租效率。</p
>
<p class="desc"
>∑(在租面积 × 在租天数) / [ (可租面积 + 在租面积) × 天数定义 ] =
累计出租率
</p>
<h4 class="title_item">天数定义</h4>
<a-radio-group
v-model="projectSettingInfoData.cumulativeDayType"
direction="vertical"
>
<a-radio
v-for="(item, index) in dayeDfinition"
:key="item.value"
:value="item.value"
>
<a-popover v-if="index === 0">
{{ item.label }}<icon-question-circle />
<template #content>
<p
>按本年整年天数计算,适用年度指标达成率(预测+实际),反映全年“达标率”基准用于预测。</p
>
</template>
</a-popover>
<template v-else>
{{ item.label }}
</template>
</a-radio>
</a-radio-group>
</div>
</a-card>
</a-col>
</a-row>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import {
getProjectSetting,
projectSettingInfo,
saveProjectSetting,
} from '@/api/setting';
import { Message } from '@arco-design/web-vue';
import {
projectSetting,
projectHouse,
inRentedAreaOptions,
availableAreaOptions,
dayeDfinition,
} from './contrant';
const { t } = useI18n();
const projectSettingId = ref<number>(1);
const projectSettingInfoData = ref<projectSettingInfo | null>(null);
const rentAreaTypeArr = ref<string[] | null>(null);
const rentableAreaTypeArr = ref<string[] | null>(null);
const getProjectSettingData = async () => {
try {
const { code, data } = await getProjectSetting();
if (code === 200 && data) {
projectSettingInfoData.value = data?.projectSetting;
const { rentAreaType, rentableAreaType } = projectSettingInfoData.value;
rentAreaTypeArr.value = rentAreaType ? rentAreaType.split(',') : [];
rentableAreaTypeArr.value = rentableAreaType
? rentableAreaType.split(',')
: [];
}
} catch (error) {
console.error(error);
}
};
const chooseProjectSetting = (id: number) => {
if (id === projectSettingId.value) return;
projectSettingId.value = id;
};
const isActiveOption = (itemIndex: number, type: number) => {
if (!projectSettingInfoData.value) return false;
if (itemIndex === 0)
return projectSettingInfoData.value.defaultPriceType === type;
if (itemIndex === 1)
return projectSettingInfoData.value.areaAccuracyType === type;
if (itemIndex === 2)
return projectSettingInfoData.value.expireSoonType === type;
return false;
};
const onOptionClick = (itemIndex: number, type: any) => {
if (!projectSettingInfoData.value) return;
if (itemIndex === 0) projectSettingInfoData.value.defaultPriceType = type;
if (itemIndex === 1) projectSettingInfoData.value.areaAccuracyType = type;
if (itemIndex === 2) projectSettingInfoData.value.expireSoonType = type;
};
const cardTitle = computed(() => {
switch (projectSettingId.value) {
case 1:
return '项目与房源';
case 2:
return '出租率';
case 3:
return '累计出租率';
default:
return '项目与房源';
}
});
const handleSaveSafe = async () => {
try {
if (projectSettingInfoData.value) {
const params = {
default_price_type: projectSettingInfoData.value.defaultPriceType,
area_accuracy_type: projectSettingInfoData.value.areaAccuracyType,
expire_soon_type: projectSettingInfoData.value.expireSoonType,
rent_rate_type: 1,
rent_area_type: rentAreaTypeArr.value
? rentAreaTypeArr.value.join(',')
: '',
rentable_area_type: rentableAreaTypeArr.value
? rentableAreaTypeArr.value.join(',')
: '',
cumulative_rent_rate_type: 1,
cumulative_day_type: projectSettingInfoData.value.cumulativeDayType,
};
const { code, data, message } = await saveProjectSetting(params);
if (code === 200 && data) {
Message.success(message);
getProjectSettingData();
}
}
} catch (error) {
console.error(error);
}
};
onMounted(() => {
getProjectSettingData();
});
</script>
<style lang="less" scoped>
.system-setting-general {
.project_setting_list {
border-radius: 6px;
border: 1px solid rgba(239, 239, 239, 1);
padding: 14px 0;
padding-bottom: 200px;
.project_setting_item {
padding: 0 20px;
height: 42px;
line-height: 42px;
color: #000;
background-color: #fff;
cursor: pointer;
&.isChoose {
background-color: #efefef;
}
}
}
.project_house_list {
.project_house_item {
margin-top: 20px;
&:first-child {
margin-top: 0;
}
.options {
display: flex;
align-items: center;
background-color: #f2f3f5;
width: 430px;
height: 36px;
margin-top: 10px;
padding: 0 3px;
.subItem {
flex: 1;
text-align: center;
padding: 4px 0;
cursor: pointer;
&.isActive {
background-color: #fff;
}
}
}
}
}
.in_rented_area,
.cumulative_leasing_rate {
.tips {
color: rgba(187, 187, 187, 1);
font-size: 12px;
margin-top: 4px;
}
.desc {
background-color: #eaf3fe;
border-radius: 2px;
background-color: rgba(232, 243, 255, 1);
color: rgba(29, 33, 41, 1);
font-size: 14px;
text-align: left;
border: 0px solid rgba(121, 121, 121, 1);
padding: 12px;
margin-top: 12px;
}
.title_item {
margin-top: 24px;
}
}
}
</style>