Compare commits
4
Commits
39ef0b7785
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adf2a2fa36 | ||
|
|
f7a710d6d2 | ||
|
|
f582098dc6 | ||
|
|
a53cd0e8b8 |
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"tailwindCSS.experimental.configFile": "src/assets/style/index.css",
|
||||
"tailwindCSS.includeLanguages": {
|
||||
"vue": "html",
|
||||
"vue-html": "html"
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"other": true,
|
||||
"comments": false,
|
||||
"strings": true
|
||||
},
|
||||
"css.validate": true,
|
||||
"tailwindCSS.emmetCompletions": true
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
SettingContractNumbeRes,
|
||||
CreateCodeSettingParam,
|
||||
DisableUserParam,
|
||||
CreateTermSettingParam,
|
||||
SettingContractTermListRes,
|
||||
SettingContractTermInfo,
|
||||
SettingContractTemplateListRes,
|
||||
CreateTemplateSettingParam,
|
||||
ContractWordFormParam,
|
||||
ContractWordSettingListRes,
|
||||
SettingContractFileInfoRes,
|
||||
} from '@/types/setting';
|
||||
import { PaginationParams } from '@/types/comm';
|
||||
import { HttpResponse } from './interceptor';
|
||||
|
||||
// ==================== 合同设置 API (contractSetting 路由) ====================
|
||||
// 合同设置-合同编号设置-全部启用列表
|
||||
export function getAllCodeSettingList(): Promise<SettingContractNumbeRes> {
|
||||
return axios.get(`contractSetting/getAllCodeSettingList`);
|
||||
}
|
||||
|
||||
// 合同设置-合同编号设置-新增
|
||||
export function createCodeSetting(
|
||||
params: CreateCodeSettingParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/createCodeSetting`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同编号设置-编辑
|
||||
export function updateCodeSetting(
|
||||
params: CreateCodeSettingParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/updateCodeSetting`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同编号设置-删除
|
||||
export function delCodeSetting(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`contractSetting/delCodeSetting?id=${id}`);
|
||||
}
|
||||
|
||||
// 合同设置-合同编号设置-单个详情
|
||||
export function getCodeSettingFromId(
|
||||
id: number
|
||||
): Promise<SettingContractNumbeRes> {
|
||||
return axios.get(`contractSetting/getCodeSetting?id=${id}`);
|
||||
}
|
||||
|
||||
// 合同设置-合同编号设置-启用/禁用
|
||||
export function disableCodeSetting(
|
||||
params: DisableUserParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/disableCodeSetting`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同编号设置-列表(带分页)
|
||||
export function getCodeSettingList(
|
||||
params: PaginationParams
|
||||
): Promise<SettingContractNumbeRes> {
|
||||
return axios.post(`contractSetting/getCodeSettingList`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同条款设置-全部启用列表
|
||||
export function getAllTermsSettingList(): Promise<any> {
|
||||
return axios.get(`contractSetting/getAllTermsSettingList`);
|
||||
}
|
||||
|
||||
// 合同设置-合同条款设置-新增
|
||||
export function createTermsSetting(
|
||||
params: CreateTermSettingParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/createTermsSetting`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同条款设置-编辑
|
||||
export function updateTermsSetting(
|
||||
params: CreateTermSettingParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/updateTermsSetting`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同条款设置-删除
|
||||
export function delTermsSetting(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`contractSetting/delTermsSetting?id=${id}`);
|
||||
}
|
||||
|
||||
// 合同设置-合同条款设置-单个条款配置详情
|
||||
export function getTermsSettingFromId(
|
||||
id: number
|
||||
): Promise<SettingContractTermInfo> {
|
||||
return axios.get(`contractSetting/getTermsSetting?id=${id}`);
|
||||
}
|
||||
|
||||
// 合同设置-合同条款设置-单个条款数据详情
|
||||
export function getTermsDataFromId(params: any): Promise<any> {
|
||||
return axios.post(`contractSetting/getTerm`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同条款设置-启用/禁用
|
||||
export function disableTermsSetting(
|
||||
params: DisableUserParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/disableTermsSetting`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同条款设置-列表(带分页)
|
||||
export function getTermsSettingList(
|
||||
params: PaginationParams
|
||||
): Promise<SettingContractTermListRes> {
|
||||
return axios.post(`contractSetting/getTermsSettingList`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同模板管理-全部启用列表
|
||||
export function getAllTemplateSettingList(): Promise<SettingContractNumbeRes> {
|
||||
return axios.get(`contractSetting/getAllTemplateSettingList`);
|
||||
}
|
||||
|
||||
// 合同设置-合同模板管理-新增
|
||||
export function createTemplateSetting(
|
||||
params: CreateTemplateSettingParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/createTemplateSetting`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同模板管理-编辑
|
||||
export function updateTemplateSetting(params: any): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/saveTemplateSetting`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同模板管理-删除
|
||||
export function delTemplateSetting(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`contractSetting/delTemplateSetting?id=${id}`);
|
||||
}
|
||||
|
||||
// 合同设置-合同模板管理-单个详情
|
||||
export function getTemplateSettingFromId(id: number): Promise<any> {
|
||||
return axios.get(`contractSetting/getTemplateSetting?id=${id}`);
|
||||
}
|
||||
|
||||
// 合同设置-合同模板管理-启用/禁用
|
||||
export function disableTemplateSetting(
|
||||
params: DisableUserParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/disableTemplateSetting`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同模板管理-列表(带分页)
|
||||
export function getTemplateSettingList(
|
||||
params: PaginationParams
|
||||
): Promise<SettingContractTemplateListRes> {
|
||||
return axios.post(`contractSetting/getTemplateSettingList`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同文档模板管理-全部启用列表
|
||||
export function getAllWordSettingList(): Promise<ContractWordSettingListRes> {
|
||||
return axios.get(`contractSetting/getAllWordSettingList`);
|
||||
}
|
||||
|
||||
// 合同设置-合同文档模板管理-新增
|
||||
export function createWordSetting(
|
||||
params: ContractWordFormParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/createWordSetting`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同文档模板管理-编辑(更新)
|
||||
export function updateWordSetting(
|
||||
params: ContractWordFormParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/updateWordSetting`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同文档模板管理-删除
|
||||
export function delWordSetting(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`contractSetting/delWordSetting?id=${id}`);
|
||||
}
|
||||
|
||||
// 合同设置-合同文档模板管理-单个详情
|
||||
export function getWordSettingFromId(
|
||||
id: number
|
||||
): Promise<SettingContractFileInfoRes> {
|
||||
return axios.get(`contractSetting/getWordSetting?id=${id}`);
|
||||
}
|
||||
|
||||
// 合同设置-合同文档模板管理-启用/禁用
|
||||
export function disableWordSetting(
|
||||
params: DisableUserParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/disableWordSetting`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同文档模板管理-列表(带分页)
|
||||
export function getWordSettingList(
|
||||
params: PaginationParams
|
||||
): Promise<ContractWordSettingListRes> {
|
||||
return axios.post(`contractSetting/getWordSettingList`, params);
|
||||
}
|
||||
|
||||
// 获取合同占位符列表
|
||||
export function getContractPlaceholderList(params: any): Promise<any> {
|
||||
// type 类型:1租赁,2物业
|
||||
return axios.post(`contractSetting/getPlaceholderList`, params);
|
||||
}
|
||||
|
||||
// ==================== 合同管理 API (contracts 路由) ====================
|
||||
// 合同管理-头部数据列表
|
||||
export function getContractsListTopData(type: number): Promise<any> {
|
||||
return axios.post(`contracts/getListTopData`, type);
|
||||
}
|
||||
// 合同管理-列表
|
||||
export interface ContractPaginationParams extends PaginationParams {
|
||||
type?: number;
|
||||
}
|
||||
export function getContractsList(
|
||||
params: ContractPaginationParams
|
||||
): Promise<any> {
|
||||
return axios.post(`contracts/getList`, params);
|
||||
}
|
||||
// 合同管理-草稿合同列表
|
||||
export function getDraftList(type: number): Promise<any> {
|
||||
return axios.get(`contracts/getDraftList?type=${type}`);
|
||||
}
|
||||
// 合同管理-编辑合同详情
|
||||
export function getEditDetail(id: number): Promise<any> {
|
||||
return axios.get(`contracts/getEditDetail?id=${id}`);
|
||||
}
|
||||
// 合同管理-编辑合同标签详情
|
||||
export function getEditTagDetail(params: any): Promise<any> {
|
||||
return axios.post(`contracts/getEditTagDetail`, params);
|
||||
}
|
||||
// 合同管理-合同详情
|
||||
export function getContractDetail(id: number): Promise<any> {
|
||||
return axios.get(`contracts/getDetail?id=${id}`);
|
||||
}
|
||||
// 合同管理-标签详情
|
||||
export function getTagDetail(params: any): Promise<any> {
|
||||
return axios.post(`contracts/getTagDetail`, params);
|
||||
}
|
||||
// 合同管理-保存合同
|
||||
export function saveContracts(params: any): Promise<any> {
|
||||
return axios.post(`contracts/save`, params);
|
||||
}
|
||||
// 合同管理-禁用/启用合同
|
||||
export function disableContracts(params: any): Promise<any> {
|
||||
return axios.post(`contracts/disable`, params);
|
||||
}
|
||||
// 合同管理-删除合同
|
||||
export function delContract(id: number): Promise<any> {
|
||||
return axios.get(`contracts/del?id=${id}`);
|
||||
}
|
||||
// 合同管理-提交审批
|
||||
export function approvalContract(id: number): Promise<any> {
|
||||
return axios.get(`contracts/approval?id=${id}`);
|
||||
}
|
||||
// 合同管理-归档
|
||||
export function filingContract(id: number): Promise<any> {
|
||||
return axios.get(`contracts/filing?id=${id}`);
|
||||
}
|
||||
// 合同管理-生成合同
|
||||
export function createContracts(params: any): Promise<any> {
|
||||
return axios.post(`contracts/createWord`, params);
|
||||
}
|
||||
// 合同管理-合同文件上传
|
||||
export function uploadFileContracts(params: any): Promise<any> {
|
||||
return axios.post(`contracts/uploadFile`, params);
|
||||
}
|
||||
// 合同管理-合同文件更新
|
||||
export function updateFileContracts(params: any): Promise<any> {
|
||||
return axios.post(`contracts/updateFile`, params);
|
||||
}
|
||||
// 合同管理-取消审批
|
||||
export function cancelApprovalContract(id: number): Promise<any> {
|
||||
return axios.get(`contracts/cancelApproval?id=${id}`);
|
||||
}
|
||||
// 合同归档-列表
|
||||
export function getFilingContractList(params: any): Promise<any> {
|
||||
return axios.post(`contracts/getFilingList`, params);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import type { TableData } from '@arco-design/web-vue/es/table/interface';
|
||||
|
||||
export interface ContentDataRecord {
|
||||
x: string;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export function queryContentData() {
|
||||
return axios.get<ContentDataRecord[]>('/api/content-data');
|
||||
}
|
||||
|
||||
export interface PopularRecord {
|
||||
key: number;
|
||||
clickNumber: string;
|
||||
title: string;
|
||||
increases: number;
|
||||
}
|
||||
|
||||
export function queryPopularList(params: { type: string }) {
|
||||
return axios.get<TableData[]>('/api/popular/list', { params });
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
DepartmentFormParams,
|
||||
DepartmentFormRes,
|
||||
DepartmentListRes,
|
||||
DepartmentDetailRes,
|
||||
} from '@/types/setting';
|
||||
import { HttpResponse } from './interceptor';
|
||||
|
||||
// ==================== 部门相关 API ====================
|
||||
export function createDepartment(
|
||||
params: DepartmentFormParams
|
||||
): Promise<DepartmentFormRes> {
|
||||
return axios.post('/department/createDepartment', params);
|
||||
}
|
||||
|
||||
export function updateDepartment(
|
||||
params: DepartmentFormParams
|
||||
): Promise<DepartmentFormRes> {
|
||||
return axios.post('/department/updateDepartment', params);
|
||||
}
|
||||
|
||||
export function getDepartmentList(): Promise<DepartmentListRes> {
|
||||
return axios.get('/department/getDepartmentList');
|
||||
}
|
||||
|
||||
export function getDepartmentDetail(id: number): Promise<DepartmentDetailRes> {
|
||||
return axios.get(`/department/getDepartment?id=${id}`);
|
||||
}
|
||||
|
||||
export function departmentSetUp(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/department/setUp?id=${id}`);
|
||||
}
|
||||
|
||||
export function departmentSetDown(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/department/setDown?id=${id}`);
|
||||
}
|
||||
|
||||
export function delDepartment(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/department/delDepartment?id=${id}`);
|
||||
}
|
||||
|
||||
// 保存部门项目数据
|
||||
export function saveDataPermission(params: any): Promise<HttpResponse> {
|
||||
return axios.post('/department/saveDataPermission', params);
|
||||
}
|
||||
|
||||
// 获取部门项目数据
|
||||
export function getDataPermission(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/department/getDataPermission?id=${id}`);
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
SettingProjectTypeListRes,
|
||||
SettingProjectTypeRes,
|
||||
SettingProjectProvinceRes,
|
||||
DictionaryForm,
|
||||
CheckProjectParam,
|
||||
SettingRoomTagsRes,
|
||||
SettingRoomTagRes,
|
||||
SettingTenantDictionaryRes,
|
||||
} from '@/types/setting';
|
||||
import { TenantDictionaryType } from '@/types/comm';
|
||||
import { HttpResponse } from './interceptor';
|
||||
|
||||
// ==================== 数据字典 API (dictionary 路由) ====================
|
||||
// 数据字典-项目管理-全部项目类型列表
|
||||
export function getAllProjectTypeList(): Promise<SettingProjectTypeListRes> {
|
||||
return axios.get('dictionary/getAllProjectTypeList');
|
||||
}
|
||||
|
||||
// 数据字典-项目管理-单个项目类型信息
|
||||
export function getProjectTypeById(id: number): Promise<SettingProjectTypeRes> {
|
||||
return axios.get(`dictionary/getProjectType?id=${id}`);
|
||||
}
|
||||
|
||||
// 数据字典-项目管理-项目地址列表
|
||||
export function getProvinceList(): Promise<SettingProjectProvinceRes> {
|
||||
return axios.get(`dictionary/getProvinceList`);
|
||||
}
|
||||
|
||||
// 数据字典-项目管理-创建项目类型
|
||||
export function createProjectType(
|
||||
params: DictionaryForm
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/createProjectType`, params);
|
||||
}
|
||||
|
||||
// 数据字典-项目管理-更新项目类型信息
|
||||
export function updateProjectType(
|
||||
params: DictionaryForm
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/updateProjectType`, params);
|
||||
}
|
||||
|
||||
// 数据字典-项目管理-删除项目类型
|
||||
export function delProjectType(id: number): Promise<SettingProjectProvinceRes> {
|
||||
return axios.get(`dictionary/delProjectType?id=${id}`);
|
||||
}
|
||||
|
||||
// 数据字典-项目管理-启用/禁用项目类型(单个)
|
||||
export function checkProjectType(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkProjectType`, params);
|
||||
}
|
||||
|
||||
// 数据字典-项目管理-启用/禁用项目类型(全选)
|
||||
export function checkAllProjectType(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkAllProjectType`, params);
|
||||
}
|
||||
|
||||
// 数据字典-项目管理-启用/禁用省份(单个)
|
||||
export function checkProvince(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkProvince`, params);
|
||||
}
|
||||
|
||||
// 数据字典-项目管理-启用/禁用省份(全选)
|
||||
export function checkAllProvince(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkAllProvince`, params);
|
||||
}
|
||||
|
||||
// 数据字典-空间管理-全部房源标签列表
|
||||
export function getRoomTagList(): Promise<SettingRoomTagsRes> {
|
||||
return axios.get(`dictionary/getRoomTagList`);
|
||||
}
|
||||
|
||||
// 数据字典-项目管理-单个房源标签信息
|
||||
export function getRoomTagById(id: number): Promise<SettingRoomTagRes> {
|
||||
return axios.get(`dictionary/getRoomTag?id=${id}`);
|
||||
}
|
||||
|
||||
// 数据字典-空间管理-创建房源标签
|
||||
export function createRoomTag(params: DictionaryForm): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/createRoomTag`, params);
|
||||
}
|
||||
|
||||
// 数据字典-空间管理-更新房源标签
|
||||
export function updateRoomTag(params: DictionaryForm): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/updateRoomTag`, params);
|
||||
}
|
||||
|
||||
// 数据字典-空间管理-删除房源标签
|
||||
export function delRoomTag(id: number): Promise<SettingProjectProvinceRes> {
|
||||
return axios.get(`dictionary/delRoomTag?id=${id}`);
|
||||
}
|
||||
|
||||
// 数据字典-空间管理-禁用/启用 房源标签(单个)
|
||||
export function checkRoomTag(params: CheckProjectParam): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkRoomTag`, params);
|
||||
}
|
||||
|
||||
// 数据字典-空间管理-禁用/启用 房源标签(全选)
|
||||
export function checkAllRoomTag(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkAllRoomTag`, params);
|
||||
}
|
||||
|
||||
// 数据字典-租客管理-全部租客标签列表(包含所有类型标签)
|
||||
export function getAllTenantDictionaryList(): Promise<SettingTenantDictionaryRes> {
|
||||
return axios.get(`dictionary/getAllTenantDictionaryList`);
|
||||
}
|
||||
|
||||
// 数据字典-租客管理-单个租客标签信息
|
||||
export function getTenantDictionary(
|
||||
id: number,
|
||||
type: TenantDictionaryType
|
||||
): Promise<SettingRoomTagsRes> {
|
||||
return axios.get(`dictionary/getTenantDictionary?id=${id}&type=${type}`);
|
||||
}
|
||||
|
||||
// 数据字典-租客管理-创建租客标签
|
||||
export function createTenantDictionary(
|
||||
params: DictionaryForm
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/createTenantDictionary`, params);
|
||||
}
|
||||
|
||||
// 数据字典-租客管理-更新租客标签
|
||||
export function updateTenantDictionary(
|
||||
params: DictionaryForm
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/updateTenantDictionary`, params);
|
||||
}
|
||||
|
||||
// 数据字典-租客管理-删除租客标签
|
||||
export function delTenantDictionary(
|
||||
id: number,
|
||||
type: TenantDictionaryType
|
||||
): Promise<SettingRoomTagsRes> {
|
||||
return axios.get(`dictionary/delTenantDictionary?id=${id}&type=${type}`);
|
||||
}
|
||||
|
||||
// 数据字典-租客管理-禁用/启用 租客标签(单个)
|
||||
export function checkTenantDictionary(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkTenantDictionary`, params);
|
||||
}
|
||||
|
||||
// 数据字典-租客管理-禁用/启用 租客标签(全选)
|
||||
export function checkAllTenantDictionary(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkAllTenantDictionary`, params);
|
||||
}
|
||||
|
||||
// 数据字典-租客管理--已启用列表(type:1租客类型,2租客来源,3租客行业分类,4租客企业规模)
|
||||
export function getTenantDictionaryListByType(
|
||||
type: TenantDictionaryType
|
||||
): Promise<SettingRoomTagsRes> {
|
||||
return axios.get(`dictionary/getTenantDictionaryList?type=${type}`);
|
||||
}
|
||||
|
||||
// 合同标签列表(已勾选)
|
||||
export function getContractTagsList(): Promise<any> {
|
||||
return axios.get(`dictionary/getContractTagsList`);
|
||||
}
|
||||
|
||||
// 数据字典-合同管理-合同标签列表(全部)
|
||||
export function getAllContractTagsList(): Promise<any> {
|
||||
return axios.get(`dictionary/getAllContractTagsList`);
|
||||
}
|
||||
|
||||
// 数据字典-合同管理-创建合同标签
|
||||
export function createContractTags(
|
||||
params: DictionaryForm
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/createContractTags`, params);
|
||||
}
|
||||
|
||||
// 数据字典-合同管理-更新合同标签
|
||||
export function updateContractTags(
|
||||
params: DictionaryForm
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/updateContractTags`, params);
|
||||
}
|
||||
|
||||
// 数据字典-合同管理-删除合同标签
|
||||
export function delContractTags(
|
||||
id: number
|
||||
): Promise<SettingProjectProvinceRes> {
|
||||
return axios.get(`dictionary/delContractTags?id=${id}`);
|
||||
}
|
||||
|
||||
// 数据字典-合同管理-禁用/启用 合同标签(单个)
|
||||
export function checkContractTags(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkContractTags`, params);
|
||||
}
|
||||
|
||||
// 数据字典-合同管理-禁用/启用 合同标签(全选)
|
||||
export function checkAllContractTags(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkAllContractTags`, params);
|
||||
}
|
||||
|
||||
// 数据字典-合同管理-单个合同标签信息
|
||||
export function getContractTags(
|
||||
id: number
|
||||
): Promise<SettingProjectProvinceRes> {
|
||||
return axios.get(`dictionary/getContractTags?id=${id}`);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
FeeManagementListRes,
|
||||
CreateFeeParams,
|
||||
FeeManagementDetailRes,
|
||||
FeeSelectListParams,
|
||||
DisableUserParam,
|
||||
} from '@/types/setting';
|
||||
import { PaginationParams } from '@/types/comm';
|
||||
import { HttpResponse } from './interceptor';
|
||||
|
||||
// ==================== 财务设置 API (financeSetting 路由) ====================
|
||||
// 合同设置-财务设置-全部列表(带分页)
|
||||
export function getFeeList(
|
||||
params: PaginationParams
|
||||
): Promise<FeeManagementListRes> {
|
||||
return axios.post(`financeSetting/getFeeList`, params);
|
||||
}
|
||||
|
||||
// 合同设置-财务设置-新增
|
||||
export function createFee(params: CreateFeeParams): Promise<HttpResponse> {
|
||||
return axios.post(`financeSetting/createFee`, params);
|
||||
}
|
||||
|
||||
// 合同设置-财务设置-编辑
|
||||
export function updateFee(params: CreateFeeParams): Promise<HttpResponse> {
|
||||
return axios.post(`financeSetting/updateFee`, params);
|
||||
}
|
||||
|
||||
// 合同设置-财务设置-删除
|
||||
export function delFee(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`financeSetting/delFee?id=${id}`);
|
||||
}
|
||||
|
||||
// 合同设置-财务设置-单个详情
|
||||
export function getFeeFromId(id: number): Promise<FeeManagementDetailRes> {
|
||||
return axios.get(`financeSetting/getFee?id=${id}`);
|
||||
}
|
||||
|
||||
// 合同设置-财务设置-启用/禁用
|
||||
export function disableFee(params: DisableUserParam): Promise<HttpResponse> {
|
||||
return axios.post(`financeSetting/disableFee`, params);
|
||||
}
|
||||
|
||||
// 合同设置-财务设置-获取下拉列表
|
||||
export function getUseFeeList(
|
||||
params: FeeSelectListParams
|
||||
): Promise<FeeManagementListRes> {
|
||||
return axios.post(`financeSetting/getUseFeeList`, params);
|
||||
}
|
||||
|
||||
// 合同设置-流水账户管理--获取下拉列表
|
||||
export function getUseTurnoverAccountList(params: any): Promise<any> {
|
||||
return axios.get(`financeSetting/getUseTurnoverAccountList`, params);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export interface BaseInfoModel {
|
||||
activityName: string;
|
||||
channelType: string;
|
||||
promotionTime: string[];
|
||||
promoteLink: string;
|
||||
}
|
||||
export interface ChannelInfoModel {
|
||||
advertisingSource: string;
|
||||
advertisingMedia: string;
|
||||
keyword: string[];
|
||||
pushNotify: boolean;
|
||||
advertisingContent: string;
|
||||
}
|
||||
|
||||
export type UnitChannelModel = BaseInfoModel & ChannelInfoModel;
|
||||
|
||||
export function submitChannelForm(data: UnitChannelModel) {
|
||||
return axios.post('/api/channel-form/submit', { data });
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import qs from 'query-string';
|
||||
import type { DescData } from '@arco-design/web-vue/es/descriptions/interface';
|
||||
|
||||
export interface PolicyRecord {
|
||||
id: string;
|
||||
number: number;
|
||||
name: string;
|
||||
contentType: 'img' | 'horizontalVideo' | 'verticalVideo';
|
||||
filterType: 'artificial' | 'rules';
|
||||
count: number;
|
||||
status: 'online' | 'offline';
|
||||
createdTime: string;
|
||||
}
|
||||
|
||||
export interface PolicyParams extends Partial<PolicyRecord> {
|
||||
current: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface PolicyListRes {
|
||||
list: PolicyRecord[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export function queryPolicyList(params: PolicyParams) {
|
||||
return axios.get<PolicyListRes>('/api/list/policy', {
|
||||
params,
|
||||
paramsSerializer: (obj) => {
|
||||
return qs.stringify(obj);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export interface ServiceRecord {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
name?: string;
|
||||
actionType?: string;
|
||||
icon?: string;
|
||||
data?: DescData[];
|
||||
enable?: boolean;
|
||||
expires?: boolean;
|
||||
}
|
||||
export function queryInspectionList() {
|
||||
return axios.get('/api/list/quality-inspection');
|
||||
}
|
||||
|
||||
export function queryTheServiceList() {
|
||||
return axios.get('/api/list/the-service');
|
||||
}
|
||||
|
||||
export function queryRulesPresetList() {
|
||||
return axios.get('/api/list/rules-preset');
|
||||
}
|
||||
@@ -24,15 +24,3 @@ interface MessageStatus {
|
||||
export function setMessageStatus(data: MessageStatus) {
|
||||
return axios.post<MessageListType>('/api/message/read', data);
|
||||
}
|
||||
|
||||
export interface ChatRecord {
|
||||
id: number;
|
||||
username: string;
|
||||
content: string;
|
||||
time: string;
|
||||
isCollect: boolean;
|
||||
}
|
||||
|
||||
export function queryChatList() {
|
||||
return axios.post<ChatRecord[]>('/api/chat/list');
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export interface ProfileBasicRes {
|
||||
status: number;
|
||||
video: {
|
||||
mode: string;
|
||||
acquisition: {
|
||||
resolution: string;
|
||||
frameRate: number;
|
||||
};
|
||||
encoding: {
|
||||
resolution: string;
|
||||
rate: {
|
||||
min: number;
|
||||
max: number;
|
||||
default: number;
|
||||
};
|
||||
frameRate: number;
|
||||
profile: string;
|
||||
};
|
||||
};
|
||||
audio: {
|
||||
mode: string;
|
||||
acquisition: {
|
||||
channels: number;
|
||||
};
|
||||
encoding: {
|
||||
channels: number;
|
||||
rate: number;
|
||||
profile: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function queryProfileBasic() {
|
||||
return axios.get<ProfileBasicRes>('/api/profile/basic');
|
||||
}
|
||||
|
||||
export type operationLogRes = Array<{
|
||||
key: string;
|
||||
contentNumber: string;
|
||||
updateContent: string;
|
||||
status: number;
|
||||
updateTime: string;
|
||||
}>;
|
||||
|
||||
export function queryOperationLog() {
|
||||
return axios.get<operationLogRes>('/api/operation/log');
|
||||
}
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
} from '@/types/business';
|
||||
import { ProjectPaginationParams, CommonCityRes } from '@/types/comm';
|
||||
import { HttpResponse } from './interceptor';
|
||||
// import type { RouteRecordNormalized } from 'vue-router';
|
||||
// import { UserState } from '@/store/modules/user/types';
|
||||
|
||||
export interface ProjectRecord {
|
||||
id: string;
|
||||
@@ -16,6 +14,7 @@ export interface ProjectRecord {
|
||||
type: string;
|
||||
}
|
||||
|
||||
// ==================== 项目相关 API (project 路由) ====================
|
||||
// 全部项目列表(包含楼栋)
|
||||
export function getAllProjectList(): Promise<AllProjectListRes> {
|
||||
return axios.get(`project/getProjectBuildingList`);
|
||||
@@ -46,3 +45,7 @@ export function updateProject(
|
||||
export function getProjectCityList(): Promise<CommonCityRes> {
|
||||
return axios.get(`project/getCityList`);
|
||||
}
|
||||
// 删除项目
|
||||
export function delProject(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`project/delProject?id=${id}`);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
AllUserListRes,
|
||||
RoleListRes,
|
||||
RoleFormParam,
|
||||
PermissionRes,
|
||||
RolePermissionRes,
|
||||
SaveRolePermissionParam,
|
||||
UpdateUserRoleParam,
|
||||
} from '@/types/setting';
|
||||
import { PaginationParams } from '@/types/comm';
|
||||
import { HttpResponse } from './interceptor';
|
||||
|
||||
// ==================== 角色相关 API ====================
|
||||
export function getRoleList(): Promise<RoleListRes> {
|
||||
return axios.get('/role/getRoleList');
|
||||
}
|
||||
|
||||
export function createRole(params: RoleFormParam): Promise<HttpResponse> {
|
||||
return axios.post('/role/createRole', params);
|
||||
}
|
||||
|
||||
export function updateRole(params: RoleFormParam): Promise<HttpResponse> {
|
||||
return axios.post('/role/updateRole', params);
|
||||
}
|
||||
|
||||
export function delRole(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/role/delRole?id=${id}`);
|
||||
}
|
||||
|
||||
export function getPermissionList(): Promise<PermissionRes> {
|
||||
return axios.get('/role/getPermissionList');
|
||||
}
|
||||
|
||||
export function getRolePermission(id: number): Promise<RolePermissionRes> {
|
||||
return axios.get(`/role/getRolePermission?id=${id}`);
|
||||
}
|
||||
|
||||
export function saveRolePermission(
|
||||
params: SaveRolePermissionParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post('/role/saveRolePermission', params);
|
||||
}
|
||||
|
||||
// 角色下的用户列表
|
||||
export function getUserListFromRole(
|
||||
params: PaginationParams
|
||||
): Promise<AllUserListRes> {
|
||||
return axios.post('/role/getUserList', params);
|
||||
}
|
||||
|
||||
export function updateUserRole(
|
||||
params: UpdateUserRoleParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post('/role/updateUserRole', params);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import axios from 'axios';
|
||||
import { TopBuildingTagsRes } from '@/types/business';
|
||||
import { PaginationParams } from '@/types/comm';
|
||||
import { HttpResponse } from './interceptor';
|
||||
|
||||
// ==================== 空间管理 - rooms 路由 ====================
|
||||
// 创建房源
|
||||
export function createRoom(params: any): Promise<HttpResponse> {
|
||||
return axios.post('rooms/createRoom', params);
|
||||
}
|
||||
// 房源列表(分页)
|
||||
export function getRoomList(params: PaginationParams): Promise<HttpResponse> {
|
||||
return axios.post('rooms/getRoomList', params);
|
||||
}
|
||||
// 更新房源
|
||||
export function updateRoom(params: any): Promise<HttpResponse> {
|
||||
return axios.post('rooms/updateRoom', params);
|
||||
}
|
||||
// 删除房源
|
||||
export function delRoom(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`rooms/delRoom?id=${id}`);
|
||||
}
|
||||
// 房源详情
|
||||
export function getRoom(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`rooms/getRoom?id=${id}`);
|
||||
}
|
||||
// 修改房源状态 status:1招商 2保留 3下架 4在租
|
||||
export function changeRoomStatus(params: any): Promise<HttpResponse> {
|
||||
return axios.post('rooms/changeRoomStatus', params);
|
||||
}
|
||||
|
||||
// ==================== 空间管理 - roomOver 路由 ====================
|
||||
// 房源获取头部楼栋tags
|
||||
export function getTopBuildingTagsByProjectId(
|
||||
id: number
|
||||
): Promise<TopBuildingTagsRes> {
|
||||
return axios.get(`roomOver/getTopBuildingTags?project_id=${id}`);
|
||||
}
|
||||
// 房源剖视-数据统计
|
||||
export function getStatisticsData(params: any): Promise<HttpResponse> {
|
||||
return axios.post('roomOver/getStatisticsData', params);
|
||||
}
|
||||
// 房源剖视-房源列表
|
||||
export function getRoomOverList(params: any): Promise<HttpResponse> {
|
||||
return axios.post('roomOver/getRoomList', params);
|
||||
}
|
||||
|
||||
// ==================== 空间管理 - roomsGraph 路由 ====================
|
||||
// 房源图谱列表
|
||||
export function getRoomsGraphList(params: any): Promise<HttpResponse> {
|
||||
return axios.post('roomsGraph/getRoomList', params);
|
||||
}
|
||||
+4
-543
@@ -1,570 +1,31 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
DepartmentFormParams,
|
||||
DepartmentFormRes,
|
||||
DepartmentListRes,
|
||||
DepartmentDetailRes,
|
||||
AllUserListRes,
|
||||
FormUserItem,
|
||||
UserInfoRes,
|
||||
DisableUserParam,
|
||||
RoleListRes,
|
||||
RoleFormParam,
|
||||
PermissionRes,
|
||||
RolePermissionRes,
|
||||
SaveRolePermissionParam,
|
||||
UpdateUserRoleParam,
|
||||
SafeInfoRes,
|
||||
SafeParams,
|
||||
SettingProjectRes,
|
||||
SettingProjectParam,
|
||||
SettingProjectTypeListRes,
|
||||
SettingProjectTypeRes,
|
||||
SettingProjectProvinceRes,
|
||||
DictionaryForm,
|
||||
CheckProjectParam,
|
||||
SettingRoomTagsRes,
|
||||
SettingRoomTagRes,
|
||||
SettingTenantDictionaryRes,
|
||||
SettingContractNumbeRes,
|
||||
CreateCodeSettingParam,
|
||||
FeeManagementListRes,
|
||||
CreateFeeParams,
|
||||
FeeManagementDetailRes,
|
||||
FeeSelectListParams,
|
||||
CreateTermSettingParam,
|
||||
SettingContractTermListRes,
|
||||
SettingContractTermInfo,
|
||||
SettingContractTemplateListRes,
|
||||
CreateTemplateSettingParam,
|
||||
ContractWordFormParam,
|
||||
ContractWordSettingListRes,
|
||||
SettingContractFileInfoRes,
|
||||
} from '@/types/setting';
|
||||
import { PaginationParams, TenantDictionaryType } from '@/types/comm';
|
||||
import { HttpResponse } from './interceptor';
|
||||
|
||||
// ==================== 部门相关 API ====================
|
||||
export function createDepartment(
|
||||
params: DepartmentFormParams
|
||||
): Promise<DepartmentFormRes> {
|
||||
return axios.post('/department/createDepartment', params);
|
||||
}
|
||||
|
||||
export function updateDepartment(
|
||||
params: DepartmentFormParams
|
||||
): Promise<DepartmentFormRes> {
|
||||
return axios.post('/department/updateDepartment', params);
|
||||
}
|
||||
|
||||
export function getDepartmentList(): Promise<DepartmentListRes> {
|
||||
return axios.get('/department/getDepartmentList');
|
||||
}
|
||||
|
||||
export function getDepartmentDetail(id: number): Promise<DepartmentDetailRes> {
|
||||
return axios.get(`/department/getDepartment?id=${id}`);
|
||||
}
|
||||
|
||||
export function departmentSetUp(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/department/setUp?id=${id}`);
|
||||
}
|
||||
|
||||
export function departmentSetDown(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/department/setDown?id=${id}`);
|
||||
}
|
||||
|
||||
export function delDepartment(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/department/delDepartment?id=${id}`);
|
||||
}
|
||||
// 保存部门项目数据
|
||||
export function saveDataPermission(params: any): Promise<HttpResponse> {
|
||||
return axios.post('/department/saveDataPermission', params);
|
||||
}
|
||||
// 获取部门项目数据
|
||||
export function getDataPermission(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/department/getDataPermission?id=${id}`);
|
||||
}
|
||||
// ==================== 用户相关 API ====================
|
||||
export function getAllUserList(
|
||||
params: PaginationParams
|
||||
): Promise<AllUserListRes> {
|
||||
return axios.post('/user/getAllUserList', params);
|
||||
}
|
||||
|
||||
export function getUserList(params: PaginationParams): Promise<AllUserListRes> {
|
||||
return axios.post('/user/getUserList', params);
|
||||
}
|
||||
|
||||
export function createUser(params: FormUserItem): Promise<HttpResponse> {
|
||||
return axios.post('/user/createUser', params);
|
||||
}
|
||||
|
||||
export function updateUser(params: FormUserItem): Promise<HttpResponse> {
|
||||
return axios.post('/user/updateUser', params);
|
||||
}
|
||||
|
||||
export function getUserInfo(id: number): Promise<UserInfoRes> {
|
||||
return axios.get(`/user/getUser?id=${id}`);
|
||||
}
|
||||
|
||||
export function delUser(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/user/delUser?id=${id}`);
|
||||
}
|
||||
|
||||
export function disableUser(params: DisableUserParam): Promise<HttpResponse> {
|
||||
return axios.post('/user/disableUser', params);
|
||||
}
|
||||
|
||||
// ==================== 角色相关 API ====================
|
||||
export function getRoleList(): Promise<RoleListRes> {
|
||||
return axios.get('/role/getRoleList');
|
||||
}
|
||||
|
||||
export function createRole(params: RoleFormParam): Promise<HttpResponse> {
|
||||
return axios.post('/role/createRole', params);
|
||||
}
|
||||
|
||||
export function updateRole(params: RoleFormParam): Promise<HttpResponse> {
|
||||
return axios.post('/role/updateRole', params);
|
||||
}
|
||||
|
||||
export function delRole(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/role/delRole?id=${id}`);
|
||||
}
|
||||
|
||||
export function getPermissionList(): Promise<PermissionRes> {
|
||||
return axios.get('/role/getPermissionList');
|
||||
}
|
||||
|
||||
export function getRolePermission(id: number): Promise<RolePermissionRes> {
|
||||
return axios.get(`role/getRolePermission?id=${id}`);
|
||||
}
|
||||
|
||||
export function saveRolePermission(
|
||||
params: SaveRolePermissionParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post('role/saveRolePermission', params);
|
||||
}
|
||||
|
||||
// 角色下的用户列表
|
||||
export function getUserListFromRole(
|
||||
params: PaginationParams
|
||||
): Promise<AllUserListRes> {
|
||||
return axios.post('role/getUserList', params);
|
||||
}
|
||||
// 角色下的用户列表
|
||||
export function updateUserRole(
|
||||
params: UpdateUserRoleParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post('role/updateUserRole', params);
|
||||
}
|
||||
|
||||
// ==================== 系统设置相关 API ====================
|
||||
// ==================== 系统设置相关 API (setting 路由) ====================
|
||||
// 通用设置-安全设置信息
|
||||
export function getSafeSettingInfo(): Promise<SafeInfoRes> {
|
||||
return axios.get('setting/getSafeSetting');
|
||||
}
|
||||
|
||||
// 通用设置-安全设置信息保存
|
||||
export function saveSafeSetting(params: SafeParams): Promise<HttpResponse> {
|
||||
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);
|
||||
}
|
||||
// 数据字典-项目管理-全部项目类型列表
|
||||
export function getAllProjectTypeList(): Promise<SettingProjectTypeListRes> {
|
||||
return axios.get('dictionary/getAllProjectTypeList');
|
||||
}
|
||||
// 数据字典-项目管理-单个项目类型信息
|
||||
export function getProjectTypeById(id: number): Promise<SettingProjectTypeRes> {
|
||||
return axios.get(`dictionary/getProjectType?id=${id}`);
|
||||
}
|
||||
// 数据字典-项目管理-项目地址列表
|
||||
export function getProvinceList(): Promise<SettingProjectProvinceRes> {
|
||||
return axios.get(`dictionary/getProvinceList`);
|
||||
}
|
||||
// 数据字典-项目管理-创建项目类型
|
||||
export function createProjectType(
|
||||
params: DictionaryForm
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/createProjectType`, params);
|
||||
}
|
||||
// 数据字典-项目管理-更新项目类型信息
|
||||
export function updateProjectType(
|
||||
params: DictionaryForm
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/updateProjectType`, params);
|
||||
}
|
||||
// 数据字典-项目管理-删除项目类型
|
||||
export function delProjectType(id: number): Promise<SettingProjectProvinceRes> {
|
||||
return axios.get(`dictionary/delProjectType?id=${id}`);
|
||||
}
|
||||
// 数据字典-项目管理-启用/禁用项目类型(单个)
|
||||
export function checkProjectType(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkProjectType`, params);
|
||||
}
|
||||
// 数据字典-项目管理-启用/禁用项目类型(全选)
|
||||
export function checkAllProjectType(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkAllProjectType`, params);
|
||||
}
|
||||
// 数据字典-项目管理-启用/禁用省份(单个)
|
||||
export function checkProvince(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkProvince`, params);
|
||||
}
|
||||
// 数据字典-项目管理-启用/禁用省份(全选)
|
||||
export function checkAllProvince(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkAllProvince`, params);
|
||||
}
|
||||
// 数据字典-空间管理-全部房源标签列表
|
||||
export function getRoomTagList(): Promise<SettingRoomTagsRes> {
|
||||
return axios.get(`dictionary/getRoomTagList`);
|
||||
}
|
||||
// 数据字典-项目管理-单个房源标签信息
|
||||
export function getRoomTagById(id: number): Promise<SettingRoomTagRes> {
|
||||
return axios.get(`dictionary/getRoomTag?id=${id}`);
|
||||
}
|
||||
// 数据字典-空间管理-创建房源标签
|
||||
export function createRoomTag(params: DictionaryForm): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/createRoomTag`, params);
|
||||
}
|
||||
// 数据字典-空间管理-更新房源标签
|
||||
export function updateRoomTag(params: DictionaryForm): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/updateRoomTag`, params);
|
||||
}
|
||||
// 数据字典-空间管理-删除房源标签
|
||||
export function delRoomTag(id: number): Promise<SettingProjectProvinceRes> {
|
||||
return axios.get(`dictionary/delRoomTag?id=${id}`);
|
||||
}
|
||||
// 数据字典-空间管理-禁用/启用 房源标签(单个)
|
||||
export function checkRoomTag(params: CheckProjectParam): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkRoomTag`, params);
|
||||
}
|
||||
// 数据字典-空间管理-禁用/启用 房源标签(全选)
|
||||
export function checkAllRoomTag(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkAllRoomTag`, params);
|
||||
}
|
||||
// 数据字典-租客管理-全部租客标签列表(包含所有类型标签)
|
||||
export function getAllTenantDictionaryList(): Promise<SettingTenantDictionaryRes> {
|
||||
return axios.get(`dictionary/getAllTenantDictionaryList`);
|
||||
}
|
||||
// 数据字典-租客管理-单个租客标签信息
|
||||
export function getTenantDictionary(
|
||||
id: number,
|
||||
type: TenantDictionaryType
|
||||
): Promise<SettingRoomTagsRes> {
|
||||
return axios.get(`dictionary/getTenantDictionary?id=${id}&type=${type}`);
|
||||
}
|
||||
// 数据字典-租客管理-创建租客标签
|
||||
export function createTenantDictionary(
|
||||
params: DictionaryForm
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/createTenantDictionary`, params);
|
||||
}
|
||||
// 数据字典-租客管理-更新租客标签
|
||||
export function updateTenantDictionary(
|
||||
params: DictionaryForm
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/updateTenantDictionary`, params);
|
||||
}
|
||||
// 数据字典-租客管理-删除租客标签
|
||||
export function delTenantDictionary(
|
||||
id: number,
|
||||
type: TenantDictionaryType
|
||||
): Promise<SettingRoomTagsRes> {
|
||||
return axios.get(`dictionary/delTenantDictionary?id=${id}&type=${type}`);
|
||||
}
|
||||
// 数据字典-租客管理-禁用/启用 租客标签(单个)
|
||||
export function checkTenantDictionary(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkTenantDictionary`, params);
|
||||
}
|
||||
// 数据字典-租客管理-禁用/启用 租客标签(全选)
|
||||
export function checkAllTenantDictionary(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkAllTenantDictionary`, params);
|
||||
}
|
||||
// 数据字典-租客管理--已启用列表(type:1租客类型,2租客来源,3租客行业分类,4租客企业规模)
|
||||
export function getTenantDictionaryListByType(
|
||||
type: TenantDictionaryType
|
||||
): Promise<SettingRoomTagsRes> {
|
||||
return axios.get(`dictionary/getTenantDictionaryList?type=${type}`);
|
||||
}
|
||||
// 合同设置-合同编号设置-全部启用列表
|
||||
export function getAllCodeSettingList(): Promise<SettingContractNumbeRes> {
|
||||
return axios.get(`contractSetting/getAllCodeSettingList`);
|
||||
}
|
||||
// 合同设置-合同编号设置-新增
|
||||
export function createCodeSetting(
|
||||
params: CreateCodeSettingParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/createCodeSetting`, params);
|
||||
}
|
||||
// 合同设置-合同编号设置-编辑
|
||||
export function updateCodeSetting(
|
||||
params: CreateCodeSettingParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/updateCodeSetting`, params);
|
||||
}
|
||||
// 合同设置-合同编号设置-删除
|
||||
export function delCodeSetting(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`contractSetting/delCodeSetting?id=${id}`);
|
||||
}
|
||||
// 合同设置-合同编号设置-单个详情
|
||||
export function getCodeSettingFromId(
|
||||
id: number
|
||||
): Promise<SettingContractNumbeRes> {
|
||||
return axios.get(`contractSetting/getCodeSetting?id=${id}`);
|
||||
}
|
||||
// 合同设置-合同编号设置-启用/禁用
|
||||
export function disableCodeSetting(
|
||||
params: DisableUserParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/disableCodeSetting`, params);
|
||||
}
|
||||
// 合同设置-合同编号设置-列表(带分页)
|
||||
export function getCodeSettingList(
|
||||
params: PaginationParams
|
||||
): Promise<SettingContractNumbeRes> {
|
||||
return axios.post(`contractSetting/getCodeSettingList`, params);
|
||||
}
|
||||
// 合同设置-合同条款设置-全部启用列表
|
||||
export function getAllTermsSettingList(): Promise<any> {
|
||||
return axios.get(`contractSetting/getAllTermsSettingList`);
|
||||
}
|
||||
// 合同设置-合同条款设置-新增
|
||||
export function createTermsSetting(
|
||||
params: CreateTermSettingParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/createTermsSetting`, params);
|
||||
}
|
||||
// 合同设置-合同条款设置-编辑
|
||||
export function updateTermsSetting(
|
||||
params: CreateTermSettingParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/updateTermsSetting`, params);
|
||||
}
|
||||
// 合同设置-合同条款设置-删除
|
||||
export function delTermsSetting(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`contractSetting/delTermsSetting?id=${id}`);
|
||||
}
|
||||
// 合同设置-合同条款设置-单个条款配置详情
|
||||
export function getTermsSettingFromId(
|
||||
id: number
|
||||
): Promise<SettingContractTermInfo> {
|
||||
return axios.get(`contractSetting/getTermsSetting?id=${id}`);
|
||||
}
|
||||
// 合同设置-合同条款设置-单个条款数据详情
|
||||
export function getTermsDataFromId(params: any): Promise<any> {
|
||||
return axios.post(`contractSetting/getTerm`, params);
|
||||
}
|
||||
// 合同设置-合同条款设置-启用/禁用
|
||||
export function disableTermsSetting(
|
||||
params: DisableUserParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/disableTermsSetting`, params);
|
||||
}
|
||||
// 合同设置-合同条款设置-列表(带分页)
|
||||
export function getTermsSettingList(
|
||||
params: PaginationParams
|
||||
): Promise<SettingContractTermListRes> {
|
||||
return axios.post(`contractSetting/getTermsSettingList`, params);
|
||||
}
|
||||
|
||||
// 合同设置-财务设置-全部列表(带分页)
|
||||
export function getFeeList(
|
||||
params: PaginationParams
|
||||
): Promise<FeeManagementListRes> {
|
||||
return axios.post(`financeSetting/getFeeList`, params);
|
||||
}
|
||||
// 合同设置-财务设置-新增
|
||||
export function createFee(params: CreateFeeParams): Promise<HttpResponse> {
|
||||
return axios.post(`financeSetting/createFee`, params);
|
||||
}
|
||||
// 合同设置-财务设置-编辑
|
||||
export function updateFee(params: CreateFeeParams): Promise<HttpResponse> {
|
||||
return axios.post(`financeSetting/updateFee`, params);
|
||||
}
|
||||
// 合同设置-财务设置-删除
|
||||
export function delFee(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`financeSetting/delFee?id=${id}`);
|
||||
}
|
||||
// 合同设置-财务设置-单个详情
|
||||
export function getFeeFromId(id: number): Promise<FeeManagementDetailRes> {
|
||||
return axios.get(`financeSetting/getFee?id=${id}`);
|
||||
}
|
||||
// 合同设置-财务设置-启用/禁用
|
||||
export function disableFee(params: DisableUserParam): Promise<HttpResponse> {
|
||||
return axios.post(`financeSetting/disableFee`, params);
|
||||
}
|
||||
// 合同设置-财务设置-获取下拉列表
|
||||
export function getUseFeeList(
|
||||
params: FeeSelectListParams
|
||||
): Promise<FeeManagementListRes> {
|
||||
return axios.post(`financeSetting/getUseFeeList`, params);
|
||||
}
|
||||
// 合同设置-流水账户管理--获取下拉列表
|
||||
export function getUseTurnoverAccountList(params: any): Promise<any> {
|
||||
return axios.get(`financeSetting/getUseTurnoverAccountList`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同模板管理-全部启用列表
|
||||
export function getAllTemplateSettingList(): Promise<SettingContractNumbeRes> {
|
||||
return axios.get(`contractSetting/getAllTemplateSettingList`);
|
||||
}
|
||||
// 合同设置-合同模板管理-新增
|
||||
export function createTemplateSetting(
|
||||
params: CreateTemplateSettingParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/createTemplateSetting`, params);
|
||||
}
|
||||
// 合同设置-合同模板管理-编辑
|
||||
export function updateTemplateSetting(params: any): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/saveTemplateSetting`, params);
|
||||
}
|
||||
// 合同设置-合同模板管理-删除
|
||||
export function delTemplateSetting(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`contractSetting/delTemplateSetting?id=${id}`);
|
||||
}
|
||||
// 合同设置-合同模板管理-单个详情
|
||||
export function getTemplateSettingFromId(id: number): Promise<any> {
|
||||
return axios.get(`contractSetting/getTemplateSetting?id=${id}`);
|
||||
}
|
||||
// 合同设置-合同模板管理-启用/禁用
|
||||
export function disableTemplateSetting(
|
||||
params: DisableUserParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/disableTemplateSetting`, params);
|
||||
}
|
||||
// 合同设置-合同模板管理-列表(带分页)
|
||||
export function getTemplateSettingList(
|
||||
params: PaginationParams
|
||||
): Promise<SettingContractTemplateListRes> {
|
||||
return axios.post(`contractSetting/getTemplateSettingList`, params);
|
||||
}
|
||||
|
||||
// 合同设置-合同文档模板管理-全部启用列表
|
||||
export function getAllWordSettingList(): Promise<ContractWordSettingListRes> {
|
||||
return axios.get(`contractSetting/getAllWordSettingList`);
|
||||
}
|
||||
// 合同设置-合同文档模板管理-新增
|
||||
export function createWordSetting(
|
||||
params: ContractWordFormParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/createWordSetting`, params);
|
||||
}
|
||||
// 合同设置-合同文档模板管理-编辑(更新)
|
||||
export function updateWordSetting(
|
||||
params: ContractWordFormParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/updateWordSetting`, params);
|
||||
}
|
||||
// 合同设置-合同文档模板管理-删除
|
||||
export function delWordSetting(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`contractSetting/delWordSetting?id=${id}`);
|
||||
}
|
||||
// 合同设置-合同文档模板管理-单个详情
|
||||
export function getWordSettingFromId(
|
||||
id: number
|
||||
): Promise<SettingContractFileInfoRes> {
|
||||
return axios.get(`contractSetting/getWordSetting?id=${id}`);
|
||||
}
|
||||
// 合同设置-合同文档模板管理-启用/禁用
|
||||
export function disableWordSetting(
|
||||
params: DisableUserParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`contractSetting/disableWordSetting`, params);
|
||||
}
|
||||
// 合同设置-合同文档模板管理-列表(带分页)
|
||||
export function getWordSettingList(
|
||||
params: PaginationParams
|
||||
): Promise<ContractWordSettingListRes> {
|
||||
return axios.post(`contractSetting/getWordSettingList`, params);
|
||||
}
|
||||
// 合同标签列表(已勾选)
|
||||
export function getContractTagsList(): Promise<any> {
|
||||
return axios.get(`dictionary/getContractTagsList`);
|
||||
}
|
||||
// 数据字典-合同管理-合同标签列表(全部)
|
||||
export function getAllContractTagsList(): Promise<any> {
|
||||
return axios.get(`dictionary/getAllContractTagsList`);
|
||||
}
|
||||
// 数据字典-合同管理-创建合同标签
|
||||
export function createContractTags(
|
||||
params: DictionaryForm
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/createContractTags`, params);
|
||||
}
|
||||
// 数据字典-合同管理-更新合同标签
|
||||
export function updateContractTags(
|
||||
params: DictionaryForm
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/updateContractTags`, params);
|
||||
}
|
||||
// 数据字典-合同管理-删除合同标签
|
||||
export function delContractTags(
|
||||
id: number
|
||||
): Promise<SettingProjectProvinceRes> {
|
||||
return axios.get(`dictionary/delContractTags?id=${id}`);
|
||||
}
|
||||
// 数据字典-合同管理-禁用/启用 合同标签(单个)
|
||||
export function checkContractTags(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkContractTags`, params);
|
||||
}
|
||||
// 数据字典-合同管理-禁用/启用 合同标签(全选)
|
||||
export function checkAllContractTags(
|
||||
params: CheckProjectParam
|
||||
): Promise<HttpResponse> {
|
||||
return axios.post(`dictionary/checkAllContractTags`, params);
|
||||
}
|
||||
// 数据字典-合同管理-单个合同标签信息
|
||||
export function getContractTags(
|
||||
id: number
|
||||
): Promise<SettingProjectProvinceRes> {
|
||||
return axios.get(`dictionary/getContractTags?id=${id}`);
|
||||
}
|
||||
// 系统配置-流程设置-流程设置列表
|
||||
// 类型:1审批流,2工作流
|
||||
export function getWorkflowsList(type: number): Promise<any> {
|
||||
return axios.get(`workflows/getList?type=${type}`);
|
||||
}
|
||||
// 创建流程
|
||||
export function createWorkflows(params: any): Promise<any> {
|
||||
return axios.post(`workflows/create`, params);
|
||||
}
|
||||
// 保存流程
|
||||
export function saveWorkflows(params: any): Promise<any> {
|
||||
return axios.post(`workflows/save`, params);
|
||||
}
|
||||
// 审批流详情
|
||||
export function getWorkflowsDetail(id: number): Promise<any> {
|
||||
return axios.get(`workflows/getDetail?id=${id}`);
|
||||
}
|
||||
// 审批流启用禁用
|
||||
export function toggleWorkflowsDisable(params: any): Promise<any> {
|
||||
return axios.post('workflows/disable', params);
|
||||
}
|
||||
// 删除审批流
|
||||
export function delWorkflows(id: number): Promise<any> {
|
||||
return axios.get(`workflows/del?id=${id}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import axios from 'axios';
|
||||
import { PaginationParams } from '@/types/comm';
|
||||
import { HttpResponse } from './interceptor';
|
||||
|
||||
// ==================== 租客管理 API (tenants 路由) ====================
|
||||
// 创建租客
|
||||
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);
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export interface MyProjectRecord {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
peopleNumber: number;
|
||||
contributors: {
|
||||
name: string;
|
||||
email: string;
|
||||
avatar: string;
|
||||
}[];
|
||||
}
|
||||
export function queryMyProjectList() {
|
||||
return axios.post('/api/user/my-project/list');
|
||||
}
|
||||
|
||||
export interface MyTeamRecord {
|
||||
id: number;
|
||||
avatar: string;
|
||||
name: string;
|
||||
peopleNumber: number;
|
||||
}
|
||||
export function queryMyTeamList() {
|
||||
return axios.post('/api/user/my-team/list');
|
||||
}
|
||||
|
||||
export interface LatestActivity {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
avatar: string;
|
||||
}
|
||||
export function queryLatestActivity() {
|
||||
return axios.post<LatestActivity[]>('/api/user/latest-activity');
|
||||
}
|
||||
|
||||
export function saveUserInfo() {
|
||||
return axios.post('/api/user/save-info');
|
||||
}
|
||||
|
||||
export interface BasicInfoModel {
|
||||
email: string;
|
||||
nickname: string;
|
||||
countryRegion: string;
|
||||
area: string;
|
||||
address: string;
|
||||
profile: string;
|
||||
}
|
||||
|
||||
export interface EnterpriseCertificationModel {
|
||||
accountType: number;
|
||||
status: number;
|
||||
time: string;
|
||||
legalPerson: string;
|
||||
certificateType: string;
|
||||
authenticationNumber: string;
|
||||
enterpriseName: string;
|
||||
enterpriseCertificateType: string;
|
||||
organizationCode: string;
|
||||
}
|
||||
|
||||
export type CertificationRecord = Array<{
|
||||
certificationType: number;
|
||||
certificationContent: string;
|
||||
status: number;
|
||||
time: string;
|
||||
}>;
|
||||
|
||||
export interface UnitCertification {
|
||||
enterpriseInfo: EnterpriseCertificationModel;
|
||||
record: CertificationRecord;
|
||||
}
|
||||
|
||||
export function queryCertification() {
|
||||
return axios.post<UnitCertification>('/api/user/certification');
|
||||
}
|
||||
|
||||
export function userUploadApi(
|
||||
data: FormData,
|
||||
config: {
|
||||
controller: AbortController;
|
||||
onUploadProgress?: (progressEvent: any) => void;
|
||||
}
|
||||
) {
|
||||
// const controller = new AbortController();
|
||||
return axios.post('/api/user/upload', data, config);
|
||||
}
|
||||
+47
-3
@@ -1,6 +1,13 @@
|
||||
import axios from 'axios';
|
||||
import type { RouteRecordNormalized } from 'vue-router';
|
||||
import { UserPermission } from '@/types/api';
|
||||
import {
|
||||
AllUserListRes,
|
||||
FormUserItem,
|
||||
UserInfoRes,
|
||||
DisableUserParam,
|
||||
} from '@/types/setting';
|
||||
import { PaginationParams } from '@/types/comm';
|
||||
import { HttpResponse } from './interceptor';
|
||||
|
||||
export interface LoginData {
|
||||
username: string;
|
||||
@@ -14,6 +21,14 @@ export interface UserPermissionList {
|
||||
permission: UserPermission[];
|
||||
}
|
||||
|
||||
export interface UserProject {
|
||||
id: number;
|
||||
name: string;
|
||||
image?: string;
|
||||
imageUrl?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface UserState {
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
@@ -32,6 +47,8 @@ export interface UserState {
|
||||
certification?: number;
|
||||
role: '' | '*' | 'admin' | 'user';
|
||||
permission: UserPermission[];
|
||||
projects?: UserProject[];
|
||||
currentProjectId?: number;
|
||||
}
|
||||
export async function login(data: LoginData) {
|
||||
const result = axios.post<LoginRes>('/auth/login', data);
|
||||
@@ -51,6 +68,33 @@ export function getUserInfo() {
|
||||
return axios.get<UserState>('/auth/profile');
|
||||
}
|
||||
|
||||
export function getMenuList() {
|
||||
return axios.post<RouteRecordNormalized[]>('/api/user/menu');
|
||||
// ==================== 用户管理 API (user 路由) ====================
|
||||
export function getAllUserList(
|
||||
params: PaginationParams
|
||||
): Promise<AllUserListRes> {
|
||||
return axios.post('/user/getAllUserList', params);
|
||||
}
|
||||
|
||||
export function getUserList(params: PaginationParams): Promise<AllUserListRes> {
|
||||
return axios.post('/user/getUserList', params);
|
||||
}
|
||||
|
||||
export function createUser(params: FormUserItem): Promise<HttpResponse> {
|
||||
return axios.post('/user/createUser', params);
|
||||
}
|
||||
|
||||
export function updateUser(params: FormUserItem): Promise<HttpResponse> {
|
||||
return axios.post('/user/updateUser', params);
|
||||
}
|
||||
|
||||
export function getUserById(id: number): Promise<UserInfoRes> {
|
||||
return axios.get(`/user/getUser?id=${id}`);
|
||||
}
|
||||
|
||||
export function delUser(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/user/delUser?id=${id}`);
|
||||
}
|
||||
|
||||
export function disableUser(params: DisableUserParam): Promise<HttpResponse> {
|
||||
return axios.post('/user/disableUser', params);
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import { GeneralChart } from '@/types/global';
|
||||
|
||||
export interface ChartDataRecord {
|
||||
x: string;
|
||||
y: number;
|
||||
name: string;
|
||||
}
|
||||
export interface DataChainGrowth {
|
||||
quota: string;
|
||||
}
|
||||
|
||||
export interface DataChainGrowthRes {
|
||||
count: number;
|
||||
growth: number;
|
||||
chartData: {
|
||||
xAxis: string[];
|
||||
data: { name: string; value: number[] };
|
||||
};
|
||||
}
|
||||
export function queryDataChainGrowth(data: DataChainGrowth) {
|
||||
return axios.post<DataChainGrowthRes>('/api/data-chain-growth', data);
|
||||
}
|
||||
|
||||
export interface PopularAuthorRes {
|
||||
list: {
|
||||
ranking: number;
|
||||
author: string;
|
||||
contentCount: number;
|
||||
clickCount: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export function queryPopularAuthor() {
|
||||
return axios.get<PopularAuthorRes>('/api/popular-author/list');
|
||||
}
|
||||
|
||||
export interface ContentPublishRecord {
|
||||
x: string[];
|
||||
y: number[];
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function queryContentPublish() {
|
||||
return axios.get<ContentPublishRecord[]>('/api/content-publish');
|
||||
}
|
||||
|
||||
export function queryContentPeriodAnalysis() {
|
||||
return axios.post<GeneralChart>('/api/content-period-analysis');
|
||||
}
|
||||
|
||||
export interface PublicOpinionAnalysis {
|
||||
quota: string;
|
||||
}
|
||||
export interface PublicOpinionAnalysisRes {
|
||||
count: number;
|
||||
growth: number;
|
||||
chartData: ChartDataRecord[];
|
||||
}
|
||||
export function queryPublicOpinionAnalysis(data: DataChainGrowth) {
|
||||
return axios.post<PublicOpinionAnalysisRes>(
|
||||
'/api/public-opinion-analysis',
|
||||
data
|
||||
);
|
||||
}
|
||||
export interface DataOverviewRes {
|
||||
xAxis: string[];
|
||||
data: Array<{ name: string; value: number[]; count: number }>;
|
||||
}
|
||||
|
||||
export function queryDataOverview() {
|
||||
return axios.post<DataOverviewRes>('/api/data-overview');
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// ==================== 流程设置 API (workflows 路由) ====================
|
||||
// 系统配置-流程设置-流程设置列表
|
||||
// 类型:1审批流,2工作流
|
||||
export function getWorkflowsList(type: number): Promise<any> {
|
||||
return axios.get(`workflows/getList?type=${type}`);
|
||||
}
|
||||
|
||||
// 创建流程
|
||||
export function createWorkflows(params: any): Promise<any> {
|
||||
return axios.post(`workflows/create`, params);
|
||||
}
|
||||
|
||||
// 保存流程
|
||||
export function saveWorkflows(params: any): Promise<any> {
|
||||
return axios.post(`workflows/save`, params);
|
||||
}
|
||||
|
||||
// 审批流详情
|
||||
export function getWorkflowsDetail(id: number): Promise<any> {
|
||||
return axios.get(`workflows/getDetail?id=${id}`);
|
||||
}
|
||||
|
||||
// 审批流启用禁用
|
||||
export function toggleWorkflowsDisable(params: any): Promise<any> {
|
||||
return axios.post('workflows/disable', params);
|
||||
}
|
||||
|
||||
// 删除审批流
|
||||
export function delWorkflows(id: number): Promise<any> {
|
||||
return axios.get(`workflows/del?id=${id}`);
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
# DataTable 组件使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
`DataTable` 是一个基于 Arco Design Vue 的 `a-table` 组件封装,提供了统一的分页配置和事件处理,可以在多个页面中复用。
|
||||
|
||||
## 特性
|
||||
|
||||
- 🚀 统一的分页配置
|
||||
- 📊 支持自定义列配置
|
||||
- 🔄 内置加载状态管理
|
||||
- 🎯 支持排序和筛选
|
||||
- 📱 响应式设计
|
||||
- 🎨 可自定义样式
|
||||
|
||||
## 基础用法
|
||||
|
||||
### 1. 使用 DataTable 组件
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<DataTable
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:total="total"
|
||||
:current="current"
|
||||
:page-size="pageSize"
|
||||
@page-change="handlePageChange"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
>
|
||||
<!-- 自定义列插槽 -->
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="record.status === 'active' ? 'green' : 'red'">
|
||||
{{ record.status }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template #operate="{ record }">
|
||||
<a-space>
|
||||
<a-button size="small">查看</a-button>
|
||||
<a-button size="small" type="primary">编辑</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</DataTable>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import type { TableColumnData, TableData } from '@arco-design/web-vue';
|
||||
import useDataTable from '@/composables/useDataTable';
|
||||
|
||||
// 使用 DataTable composable
|
||||
const {
|
||||
data: tableData,
|
||||
columns,
|
||||
loading,
|
||||
total,
|
||||
current,
|
||||
pageSize,
|
||||
setData,
|
||||
setColumns,
|
||||
setLoading,
|
||||
setTotal,
|
||||
setCurrent,
|
||||
setPageSize,
|
||||
} = useDataTable({
|
||||
initialPage: 1,
|
||||
initialPageSize: 10,
|
||||
pageSizeOptions: [10, 20, 50, 100],
|
||||
});
|
||||
|
||||
// 列配置
|
||||
const columnConfig: TableColumnData[] = [
|
||||
{ title: '姓名', dataIndex: 'name', key: 'name' },
|
||||
{ title: '年龄', dataIndex: 'age', key: 'age' },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', slotName: 'status' },
|
||||
{ title: '操作', key: 'operate', slotName: 'operate', width: 200 },
|
||||
];
|
||||
|
||||
// 模拟数据
|
||||
const mockData: TableData[] = [
|
||||
{ id: 1, name: '张三', age: 25, status: 'active' },
|
||||
{ id: 2, name: '李四', age: 30, status: 'inactive' },
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
setColumns(columnConfig);
|
||||
setData(mockData);
|
||||
setTotal(mockData.length);
|
||||
});
|
||||
|
||||
// 分页事件处理
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrent(page);
|
||||
// 调用 API 获取数据
|
||||
fetchData(page, pageSize.value);
|
||||
};
|
||||
|
||||
const handlePageSizeChange = (pageSize: number) => {
|
||||
setPageSize(pageSize);
|
||||
// 调用 API 获取数据
|
||||
fetchData(1, pageSize);
|
||||
};
|
||||
|
||||
const fetchData = async (page: number, size: number) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 调用 API
|
||||
// const { data, total } = await api.getList({ page, size });
|
||||
// setData(data);
|
||||
// setTotal(total);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 高级配置
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<DataTable
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:total="total"
|
||||
:current="current"
|
||||
:page-size="pageSize"
|
||||
:bordered="true"
|
||||
:stripe="true"
|
||||
:size="'small'"
|
||||
:scroll="{ x: 1200, y: 400 }"
|
||||
@page-change="handlePageChange"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sorter-change="handleSorterChange"
|
||||
@filter-change="handleFilterChange"
|
||||
>
|
||||
<!-- 插槽内容 -->
|
||||
</DataTable>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Props 说明
|
||||
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| data | TableData[] | [] | 表格数据 |
|
||||
| columns | TableColumnData[] | [] | 列配置 |
|
||||
| loading | boolean | false | 加载状态 |
|
||||
| rowKey | string | 'id' | 行键 |
|
||||
| pagination | boolean \| PaginationProps | true | 分页配置 |
|
||||
| bordered | boolean | false | 是否显示边框 |
|
||||
| size | 'mini' \| 'small' \| 'medium' \| 'large' | 'medium' | 表格大小 |
|
||||
| stripe | boolean | false | 是否显示斑马纹 |
|
||||
| tableLayout | 'auto' \| 'fixed' | 'auto' | 表格布局 |
|
||||
| scroll | object | - | 滚动配置 |
|
||||
| current | number | 1 | 当前页码 |
|
||||
| pageSize | number | 10 | 每页条数 |
|
||||
| total | number | 0 | 总条数 |
|
||||
| showTotal | boolean | true | 是否显示总数 |
|
||||
| pageSizeOptions | number[] | [10, 20, 50, 100] | 每页条数选项 |
|
||||
| showJumper | boolean | true | 是否显示快速跳转 |
|
||||
| showPageSize | boolean | true | 是否显示每页条数选择器 |
|
||||
|
||||
## Events 说明
|
||||
|
||||
| 事件名 | 参数 | 说明 |
|
||||
|--------|------|------|
|
||||
| page-change | (page: number) | 页码变化 |
|
||||
| page-size-change | (pageSize: number) | 每页条数变化 |
|
||||
| selection-change | (selection: TableData[]) | 选择变化 |
|
||||
| sorter-change | (sorter: any) | 排序变化 |
|
||||
| filter-change | (filters: any) | 筛选变化 |
|
||||
|
||||
## useDataTable Composable
|
||||
|
||||
`useDataTable` 是一个用于管理表格状态的 composable,提供了以下功能:
|
||||
|
||||
### 返回值
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data | TableData[] | 表格数据 |
|
||||
| columns | TableColumnData[] | 列配置 |
|
||||
| loading | boolean | 加载状态 |
|
||||
| pagination | PaginationProps | 分页配置 |
|
||||
| current | number | 当前页码 |
|
||||
| pageSize | number | 每页条数 |
|
||||
| total | number | 总条数 |
|
||||
| setData | (data: TableData[]) => void | 设置数据 |
|
||||
| setColumns | (columns: TableColumnData[]) => void | 设置列配置 |
|
||||
| setLoading | (loading: boolean) => void | 设置加载状态 |
|
||||
| setTotal | (total: number) => void | 设置总条数 |
|
||||
| setCurrent | (current: number) => void | 设置当前页码 |
|
||||
| setPageSize | (pageSize: number) => void | 设置每页条数 |
|
||||
| resetPagination | () => void | 重置分页 |
|
||||
|
||||
### 配置选项
|
||||
|
||||
```typescript
|
||||
interface UseDataTableOptions {
|
||||
initialPage?: number; // 初始页码
|
||||
initialPageSize?: number; // 初始每页条数
|
||||
pageSizeOptions?: number[]; // 每页条数选项
|
||||
showTotal?: boolean; // 是否显示总数
|
||||
showJumper?: boolean; // 是否显示快速跳转
|
||||
showPageSize?: boolean; // 是否显示每页条数选择器
|
||||
rowKey?: string; // 行键
|
||||
bordered?: boolean; // 是否显示边框
|
||||
size?: 'mini' | 'small' | 'medium' | 'large'; // 表格大小
|
||||
stripe?: boolean; // 是否显示斑马纹
|
||||
tableLayout?: 'auto' | 'fixed'; // 表格布局
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **统一使用 DataTable 组件**:在项目中统一使用 DataTable 组件,避免重复配置分页逻辑。
|
||||
|
||||
2. **使用 useDataTable composable**:利用 composable 管理表格状态,提高代码复用性。
|
||||
|
||||
3. **合理配置列插槽**:对于需要自定义渲染的列,使用插槽进行自定义。
|
||||
|
||||
4. **处理加载状态**:在数据请求时正确设置 loading 状态。
|
||||
|
||||
5. **分页事件处理**:在分页事件中调用 API 获取对应页的数据。
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保在 `main.ts` 中注册了 `DataTable` 组件。
|
||||
2. 列配置中的 `slotName` 要与模板中的插槽名称一致。
|
||||
3. 数据源需要包含 `rowKey` 指定的字段。
|
||||
4. 分页事件处理中应该调用实际的 API 接口。
|
||||
@@ -1,20 +1,29 @@
|
||||
<template>
|
||||
<!-- Arco a-upload 只认 #upload-button 插槽作为触发区,需把自定义内容传入该插槽 -->
|
||||
<a-upload
|
||||
draggable
|
||||
:accept="accept"
|
||||
:disabled="disabled"
|
||||
:custom-request="customRequest"
|
||||
:multiple="maxCount > 1"
|
||||
:limit="maxCount"
|
||||
:show-file-list="showFileList"
|
||||
:show-upload-button="showUploadButtonConfig"
|
||||
@change="handleChange"
|
||||
>
|
||||
<div style="padding: 20px; text-align: center">
|
||||
<icon-upload style="font-size: 32px; color: #666" />
|
||||
<div style="margin-top: 8px">
|
||||
<p>点击或拖拽文件到此处上传</p>
|
||||
<p v-if="accept">支持的格式:{{ accept }}</p>
|
||||
<p>最大支持 {{ maxSizeText }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<template #upload-button>
|
||||
<!-- 插槽 trigger:可传入自定义触发内容(如按钮),不传则使用下方默认拖拽区 -->
|
||||
<slot name="trigger">
|
||||
<div style="padding: 20px; text-align: center">
|
||||
<icon-upload style="font-size: 32px; color: #666" />
|
||||
<div style="margin-top: 8px">
|
||||
<p>点击或拖拽文件到此处上传</p>
|
||||
<p v-if="accept">支持的格式:{{ accept }}</p>
|
||||
<p>最大支持 {{ maxSizeText }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
</a-upload>
|
||||
</template>
|
||||
|
||||
@@ -30,14 +39,24 @@
|
||||
objectKey: string;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
objectKeys: string[];
|
||||
signedPutURL: string[];
|
||||
uploadType: UploadType;
|
||||
maxSize?: number; // 单位字节
|
||||
maxCount?: number;
|
||||
accept?: string; // 限制类型,例如 "application/pdf" 或 ".doc,.docx,.xls" 或 "application/pdf,.docx"
|
||||
}>();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
objectKeys?: string[];
|
||||
signedPutURL?: string[];
|
||||
uploadType: UploadType;
|
||||
maxSize?: number; // 单位字节
|
||||
maxCount?: number;
|
||||
accept?: string; // 限制类型,例如 "application/pdf" 或 ".doc,.docx,.xls" 或 "application/pdf,.docx"
|
||||
showFileList?: boolean;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{
|
||||
objectKeys: () => [],
|
||||
signedPutURL: () => [],
|
||||
showFileList: true,
|
||||
disabled: false,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:objectKeys', value: string[]): void;
|
||||
@@ -51,6 +70,10 @@
|
||||
|
||||
const maxCount = props.maxCount ?? 1;
|
||||
const accept = props.accept ?? '*/*'; // 默认支持所有文件
|
||||
// 达到 limit 后仍显示上传按钮,方便替换文件(Arco 默认会隐藏)
|
||||
const showUploadButtonConfig = computed(() =>
|
||||
maxCount > 0 ? { showOnExceedLimit: true } : true
|
||||
);
|
||||
const maxSizeText = computed(() => {
|
||||
const size = props.maxSize || 10 * 1024 * 1024;
|
||||
return `${Math.round(size / 1024 / 1024)}MB`;
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
# ImageUpload 图片上传组件
|
||||
|
||||
一个可复用的图片上传组件,支持自定义上传类型、文件大小限制等功能。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 支持图片格式:JPG、PNG、GIF、WEBP
|
||||
- 可自定义文件大小限制
|
||||
- 支持上传进度显示
|
||||
- 支持图片预览
|
||||
- 支持多文件上传
|
||||
- 上传成功后自动回传 URL
|
||||
|
||||
## Props
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
| ---------- | ------------ | ---------------- | ---------------------------- |
|
||||
| modelValue | UploadFile[] | [] | 文件列表,支持 v-model |
|
||||
| uploadType | UploadType | - | 上传类型:1 合同模版,2 图片 |
|
||||
| maxSize | number | 10 _ 1024 _ 1024 | 最大文件大小(字节) |
|
||||
| maxCount | number | - | 最大上传数量 |
|
||||
|
||||
## Events
|
||||
|
||||
| 事件名 | 参数 | 说明 |
|
||||
| ----------------- | -------------------------------------------- | ------------------ |
|
||||
| update:modelValue | (value: UploadFile[]) | 文件列表更新时触发 |
|
||||
| upload-success | (file: UploadFile, response: UploadResponse) | 上传成功时触发 |
|
||||
| upload-error | (error: Error) | 上传失败时触发 |
|
||||
|
||||
## 类型定义
|
||||
|
||||
```typescript
|
||||
interface UploadFile {
|
||||
uid: string;
|
||||
name: string;
|
||||
url?: string;
|
||||
status: 'done' | 'uploading' | 'error';
|
||||
percent?: number;
|
||||
}
|
||||
|
||||
interface UploadResponse {
|
||||
filename: string;
|
||||
size: number;
|
||||
signedPutURL: string;
|
||||
objectKey?: string;
|
||||
}
|
||||
|
||||
type UploadType = 1 | 2; // 1合同模版,2图片
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础用法
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ImageUpload
|
||||
v-model="images"
|
||||
:upload-type="2"
|
||||
@upload-success="handleUploadSuccess"
|
||||
@upload-error="handleUploadError"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import ImageUpload from '@/components/common/ImageUpload.vue';
|
||||
|
||||
const images = ref([]);
|
||||
|
||||
const handleUploadSuccess = (file, response) => {
|
||||
console.log('上传成功:', response.url);
|
||||
};
|
||||
|
||||
const handleUploadError = (error) => {
|
||||
console.error('上传失败:', error.message);
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
### 自定义文件大小限制
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ImageUpload
|
||||
v-model="images"
|
||||
:upload-type="2"
|
||||
:max-size="5 * 1024 * 1024"
|
||||
@upload-success="handleUploadSuccess"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 在表单中使用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<a-form-item label="项目图片" field="images">
|
||||
<ImageUpload
|
||||
v-model="form.images"
|
||||
:upload-type="2"
|
||||
:max-size="10 * 1024 * 1024"
|
||||
@upload-success="handleUploadSuccess"
|
||||
@upload-error="handleUploadError"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 组件会自动处理文件验证,包括文件大小和格式检查
|
||||
2. 上传成功后,文件对象会自动更新 URL 和状态
|
||||
3. 支持 v-model 双向绑定,父组件可以直接操作文件列表
|
||||
4. 上传类型必须从父组件传入,用于区分不同的上传场景
|
||||
@@ -1,12 +0,0 @@
|
||||
## 组件说明
|
||||
|
||||
该组件非官方最终设计规范,以单独组件存在。
|
||||
|
||||
同时仅仅提供最基本的功能,后续进行优化及更改。
|
||||
|
||||
|
||||
## Component description
|
||||
|
||||
The component unofficial final design specification exists as a separate component.
|
||||
|
||||
At the same time, only the most basic functions are provided, and subsequent optimizations and changes will be made.
|
||||
+167
-100
@@ -1,135 +1,202 @@
|
||||
<template>
|
||||
<div class="top-menu">
|
||||
<a-select :style="{ width: '200px' }" placeholder="请选择" :bordered="false">
|
||||
<template #label="{ data }">
|
||||
<div class="current-building">
|
||||
<img class="img" src="@/assets/images/building1.png" alt="">
|
||||
<p class="building-name">星河商务中心</p>
|
||||
</div>
|
||||
</template>
|
||||
<a-option>
|
||||
<p class="building-name">星河商务中心</p>
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-menu :style="{ width: '100%' }" mode="horizontal" :selected-keys="[selectedKey]"
|
||||
@menu-item-click="onMenuClick" style="width: 100%">
|
||||
<a-menu-item v-for="item in topLevelMenus" :key="item.path">
|
||||
{{ t(item.meta?.locale || '') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</div>
|
||||
<div class="top-menu">
|
||||
<a-select
|
||||
v-model="selectedProjectId"
|
||||
:style="{ width: '200px' }"
|
||||
placeholder="请选择"
|
||||
:bordered="false"
|
||||
@change="handleProjectChange"
|
||||
>
|
||||
<template #label>
|
||||
<div v-if="currentProject" class="current-building">
|
||||
<img
|
||||
class="img"
|
||||
:src="
|
||||
currentProject.imageUrl || currentProject.image || defaultImage
|
||||
"
|
||||
:alt="currentProject.name"
|
||||
/>
|
||||
<p class="building-name">{{ currentProject.name }}</p>
|
||||
</div>
|
||||
<div v-else class="current-building">
|
||||
<p class="building-name">请选择项目</p>
|
||||
</div>
|
||||
</template>
|
||||
<a-option
|
||||
v-for="project in projectList"
|
||||
:key="project.id"
|
||||
:value="project.id"
|
||||
>
|
||||
<div>
|
||||
<div class="flex items-center">
|
||||
<img
|
||||
class="w-[30px] h-[30px] object-contain"
|
||||
:src="project.imageUrl || project.image || defaultImage"
|
||||
:alt="project.name"
|
||||
/>
|
||||
<p class="ml-[10px]">{{ project.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-menu
|
||||
:style="{ width: '100%' }"
|
||||
mode="horizontal"
|
||||
:selected-keys="[selectedKey]"
|
||||
style="width: 100%"
|
||||
@menu-item-click="onMenuClick"
|
||||
>
|
||||
<a-menu-item v-for="item in topLevelMenus" :key="item.path">
|
||||
{{ t(item.meta?.locale || '') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import type { RouteRecordNormalized } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import type { RouteRecordNormalized } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useUserStore } from '@/store';
|
||||
import type { UserProject } from '@/store/modules/user/types';
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
// 默认图片路径
|
||||
const defaultImage = new URL('@/assets/images/building1.png', import.meta.url)
|
||||
.href;
|
||||
|
||||
// 获取一级路由(顶级菜单)
|
||||
const topLevelMenus = computed(() => {
|
||||
return router.getRoutes()
|
||||
.filter(route => {
|
||||
// 过滤出一级路由:有 meta 且有 children
|
||||
return route.meta &&
|
||||
route.children?.length > 0 &&
|
||||
route.meta.isFirst &&
|
||||
route.meta.locale;
|
||||
})
|
||||
.map(route => ({
|
||||
path: route.path,
|
||||
name: route.name,
|
||||
meta: route.meta
|
||||
}));
|
||||
});
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const buildingList = ref([
|
||||
{ logo: new URL('@/assets/images/building1.png', import.meta.url).href, title: '星河商务中心', id: 1 },
|
||||
{ logo: new URL('@/assets/images/building1.png', import.meta.url).href, title: '南山科学科学园', id: 2 },
|
||||
{ logo: new URL('@/assets/images/building1.png', import.meta.url).href, title: '金融科技大厦', id: 3 },
|
||||
{ logo: new URL('@/assets/images/building1.png', import.meta.url).href, title: '福田平安大厦', id: 4 },
|
||||
]);
|
||||
// 获取一级路由(顶级菜单)
|
||||
const topLevelMenus = computed(() => {
|
||||
return router
|
||||
.getRoutes()
|
||||
.filter((route) => {
|
||||
// 过滤出一级路由:有 meta 且有 children
|
||||
return (
|
||||
route.meta &&
|
||||
route.children?.length > 0 &&
|
||||
route.meta.isFirst &&
|
||||
route.meta.locale
|
||||
);
|
||||
})
|
||||
.map((route) => ({
|
||||
path: route.path,
|
||||
name: route.name,
|
||||
meta: route.meta,
|
||||
}));
|
||||
});
|
||||
|
||||
const currentBuildingId = ref(1);
|
||||
// 项目列表
|
||||
const projectList = computed<UserProject[]>(() => {
|
||||
return userStore.projects || [];
|
||||
});
|
||||
|
||||
const getCurrentBuilding = (id: number) => {
|
||||
return buildingList.value.find(building => building.id === id);
|
||||
};
|
||||
// 选中的项目ID,优先使用 store 中保存的值
|
||||
const selectedProjectId = ref<number | undefined>(userStore.currentProjectId);
|
||||
|
||||
defineProps<{
|
||||
// 当前选中的项目
|
||||
const currentProject = computed(() => {
|
||||
if (!selectedProjectId.value) return null;
|
||||
return projectList.value.find(
|
||||
(project) => project.id === selectedProjectId.value
|
||||
);
|
||||
});
|
||||
|
||||
// 项目切换处理
|
||||
const handleProjectChange = (projectId: number) => {
|
||||
selectedProjectId.value = projectId;
|
||||
// 保存到 store
|
||||
userStore.setInfo({ currentProjectId: projectId });
|
||||
};
|
||||
|
||||
// 监听 projects 变化,设置默认选中第一个
|
||||
watch(
|
||||
() => userStore.projects,
|
||||
(projects) => {
|
||||
if (projects && projects.length > 0) {
|
||||
// 优先使用 store 中保存的项目ID,否则使用第一个项目
|
||||
const projectId = userStore.currentProjectId || projects[0].id;
|
||||
selectedProjectId.value = projectId;
|
||||
// 如果 store 中没有保存,则保存第一个项目ID
|
||||
if (!userStore.currentProjectId) {
|
||||
userStore.setInfo({ currentProjectId: projectId });
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 组件挂载时,如果已有项目数据,设置默认选中
|
||||
onMounted(() => {
|
||||
if (projectList.value.length > 0) {
|
||||
// 优先使用 store 中保存的项目ID,否则使用第一个项目
|
||||
const projectId = userStore.currentProjectId || projectList.value[0].id;
|
||||
selectedProjectId.value = projectId;
|
||||
// 如果 store 中没有保存,则保存第一个项目ID
|
||||
if (!userStore.currentProjectId) {
|
||||
userStore.setInfo({ currentProjectId: projectId });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
defineProps<{
|
||||
selectedKey: string;
|
||||
}>();
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['menu-click']);
|
||||
const emit = defineEmits(['menu-click']);
|
||||
|
||||
const onMenuClick = (key: string) => {
|
||||
const onMenuClick = (key: string) => {
|
||||
// 查找对应的路由
|
||||
const targetRoute = topLevelMenus.value.find(item => item.path === key);
|
||||
const targetRoute = topLevelMenus.value.find((item) => item.path === key);
|
||||
if (targetRoute) {
|
||||
// 跳转到对应路由
|
||||
router.push(targetRoute.path);
|
||||
// 跳转到对应路由
|
||||
router.push(targetRoute.path);
|
||||
}
|
||||
// 触发父组件事件
|
||||
emit('menu-click', key);
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.top-menu {
|
||||
.top-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.current-building,
|
||||
.building {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.building-name {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-select-option-content) {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
|
||||
.current-building,
|
||||
.building {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.building-name {
|
||||
margin-left: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.building-item {
|
||||
.building {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.building-name {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-select-option-content) {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
|
||||
.current-building,
|
||||
.building {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.building-name {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,196 +0,0 @@
|
||||
# 分页处理 Composable 使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
本项目提供了两个用于处理表格分页的 composable:
|
||||
|
||||
1. `usePagination` - 基础分页事件处理
|
||||
2. `useTableWithPagination` - 完整的分页和数据获取集成
|
||||
|
||||
## usePagination
|
||||
|
||||
### 基础用法
|
||||
|
||||
```typescript
|
||||
import usePagination from '@/composables/usePagination';
|
||||
|
||||
const { handlePageChange, handlePageSizeChange } = usePagination({
|
||||
onPageChange: () => getUserListData(),
|
||||
onPageSizeChange: () => getUserListData(),
|
||||
resetToFirstPage: true,
|
||||
});
|
||||
|
||||
// 在分页事件中使用
|
||||
const onPageChange = (page: number) => {
|
||||
handlePageChange(page, setCurrent);
|
||||
};
|
||||
|
||||
const onPageSizeChange = (pageSize: number) => {
|
||||
handlePageSizeChange(pageSize, setPageSize, setCurrent);
|
||||
};
|
||||
```
|
||||
|
||||
### 配置选项
|
||||
|
||||
| 选项 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| onPageChange | Function | - | 页码变化时的回调函数 |
|
||||
| onPageSizeChange | Function | - | 每页条数变化时的回调函数 |
|
||||
| resetToFirstPage | boolean | true | 切换每页条数时是否重置到第一页 |
|
||||
|
||||
## useTableWithPagination
|
||||
|
||||
### 完整用法
|
||||
|
||||
```typescript
|
||||
import useTableWithPagination from '@/composables/useTableWithPagination';
|
||||
|
||||
const {
|
||||
data: renderData,
|
||||
columns: projectColumn,
|
||||
loading,
|
||||
total,
|
||||
current,
|
||||
pageSize,
|
||||
setColumns,
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
refresh,
|
||||
updateParams,
|
||||
} = useTableWithPagination({
|
||||
// 数据获取函数
|
||||
fetchData: async (params) => {
|
||||
const { code, data } = await getUserList({
|
||||
dept_id: selectedKeys.value[0],
|
||||
keyword: searchKeyWord.value,
|
||||
page: params.page,
|
||||
page_size: params.pageSize,
|
||||
});
|
||||
|
||||
if (code === 200) {
|
||||
const { page, user } = data;
|
||||
return {
|
||||
data: Array.isArray(user) ? user : [],
|
||||
total: typeof page.total === 'number' ? page.total : 0,
|
||||
};
|
||||
}
|
||||
|
||||
return { data: [], total: 0 };
|
||||
},
|
||||
initialPage: 1,
|
||||
initialPageSize: 10,
|
||||
pageSizeOptions: [10, 20, 50, 100],
|
||||
autoFetch: false, // 不自动获取,等选中部门后再获取
|
||||
});
|
||||
```
|
||||
|
||||
### 配置选项
|
||||
|
||||
| 选项 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| fetchData | Function | - | 数据获取函数,必须返回 { data, total } |
|
||||
| initialPage | number | 1 | 初始页码 |
|
||||
| initialPageSize | number | 10 | 初始每页条数 |
|
||||
| pageSizeOptions | number[] | [10, 20, 50, 100] | 每页条数选项 |
|
||||
| extraParams | object | {} | 额外的查询参数 |
|
||||
| autoFetch | boolean | true | 是否自动获取数据 |
|
||||
| resetToFirstPage | boolean | true | 切换每页条数时是否重置到第一页 |
|
||||
|
||||
### 返回值
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| data | Ref<TableData[]> | 表格数据 |
|
||||
| columns | Ref<TableColumnData[]> | 列配置 |
|
||||
| loading | Ref<boolean> | 加载状态 |
|
||||
| total | Ref<number> | 总条数 |
|
||||
| current | Ref<number> | 当前页码 |
|
||||
| pageSize | Ref<number> | 每页条数 |
|
||||
| setColumns | Function | 设置列配置 |
|
||||
| onPageChange | Function | 页码变化处理 |
|
||||
| onPageSizeChange | Function | 每页条数变化处理 |
|
||||
| refresh | Function | 刷新数据 |
|
||||
| updateParams | Function | 更新查询参数 |
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 使用 usePagination(推荐用于简单场景)
|
||||
|
||||
```typescript
|
||||
// 在现有的 useDataTable 基础上添加分页处理
|
||||
const { handlePageChange, handlePageSizeChange } = usePagination({
|
||||
onPageChange: () => getUserListData(),
|
||||
onPageSizeChange: () => getUserListData(),
|
||||
});
|
||||
|
||||
const onPageChange = (page: number) => {
|
||||
handlePageChange(page, setCurrent);
|
||||
};
|
||||
|
||||
const onPageSizeChange = (pageSize: number) => {
|
||||
handlePageSizeChange(pageSize, setPageSize, setCurrent);
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 使用 useTableWithPagination(推荐用于新页面)
|
||||
|
||||
```typescript
|
||||
// 完整的分页和数据获取集成
|
||||
const {
|
||||
data: renderData,
|
||||
columns: projectColumn,
|
||||
loading,
|
||||
total,
|
||||
current,
|
||||
pageSize,
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
refresh,
|
||||
} = useTableWithPagination({
|
||||
fetchData: async (params) => {
|
||||
// 你的数据获取逻辑
|
||||
return { data: [], total: 0 };
|
||||
},
|
||||
autoFetch: false,
|
||||
});
|
||||
|
||||
// 搜索时刷新数据
|
||||
const handleSearch = () => {
|
||||
refresh();
|
||||
};
|
||||
|
||||
// 删除后刷新数据
|
||||
const handleDelete = async (id: number) => {
|
||||
await deleteItem(id);
|
||||
refresh();
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 处理搜索和筛选
|
||||
|
||||
```typescript
|
||||
// 使用 updateParams 更新查询参数
|
||||
const handleSearch = () => {
|
||||
updateParams({ keyword: searchKeyWord.value });
|
||||
};
|
||||
|
||||
// 或者使用 refresh 直接刷新
|
||||
const handleSearch = () => {
|
||||
refresh();
|
||||
};
|
||||
```
|
||||
|
||||
## 优势
|
||||
|
||||
1. **代码复用**:避免在每个页面重复编写分页逻辑
|
||||
2. **统一处理**:确保所有页面的分页行为一致
|
||||
3. **类型安全**:完整的 TypeScript 类型支持
|
||||
4. **易于维护**:分页逻辑集中管理,便于修改和扩展
|
||||
5. **自动优化**:自动处理页码重置、加载状态等细节
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. `fetchData` 函数必须返回 `{ data, total }` 格式
|
||||
2. 使用 `useTableWithPagination` 时,确保 `fetchData` 函数能正确处理错误
|
||||
3. 如果需要自定义分页行为,可以使用 `usePagination` 进行更精细的控制
|
||||
4. `autoFetch: false` 适用于需要等待用户操作后再获取数据的场景
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { getDepartmentList, getDepartmentDetail } from '@/api/setting';
|
||||
import { getDepartmentList, getDepartmentDetail } from '@/api/department';
|
||||
import { DepartmentItem } from '@/types/setting';
|
||||
|
||||
export default function useDepartmentList(autoFetch = true) {
|
||||
@@ -40,8 +40,8 @@ export default function useDepartmentList(autoFetch = true) {
|
||||
);
|
||||
departmentListData.value = fetchDepartmentList;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取部门列表失败:', error);
|
||||
} catch {
|
||||
// 获取部门列表失败,静默处理
|
||||
}
|
||||
};
|
||||
|
||||
@@ -52,8 +52,7 @@ export default function useDepartmentList(autoFetch = true) {
|
||||
return data;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('获取部门详情失败:', error);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -42,3 +42,157 @@ export const VALIDATION_RULES = {
|
||||
ID_CARD:
|
||||
/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
|
||||
} as const;
|
||||
|
||||
// 房源状态
|
||||
//状态:1招商 2保留 3下架 4在租
|
||||
export const HOUSE_STATUS = [
|
||||
{ id: 1, text: '招商' },
|
||||
{ id: 2, text: '保留' },
|
||||
{ id: 3, text: '下架' },
|
||||
{ id: 4, text: '在租' },
|
||||
];
|
||||
|
||||
// 价格单位选项
|
||||
export const PRICE_UNITS = [
|
||||
{ value: 1, text: '元/㎡*月' },
|
||||
{ value: 2, text: '元/㎡*天' },
|
||||
{ value: 3, text: '元/天' },
|
||||
{ value: 4, text: '元/月' },
|
||||
{ value: 5, text: '元/年' },
|
||||
] as const;
|
||||
|
||||
// 价格单位映射表
|
||||
export const PRICE_UNIT_MAP = new Map([
|
||||
[1, '元/㎡*月'],
|
||||
[2, '元/㎡*天'],
|
||||
[3, '元/天'],
|
||||
[4, '元/月'],
|
||||
[5, '元/年'],
|
||||
]);
|
||||
|
||||
// 租客来源
|
||||
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, '其他原因'],
|
||||
]);
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import localeMessageBox from '@/components/message-box/locale/en-US';
|
||||
import localeLogin from '@/views/login/locale/en-US';
|
||||
|
||||
import localeWorkplace from '@/views/dashboard/workplace/locale/en-US';
|
||||
|
||||
import localeMonitor from '@/views/dashboard/monitor/locale/en-US';
|
||||
|
||||
import locale403 from '@/views/exception/403/locale/en-US';
|
||||
import locale404 from '@/views/exception/404/locale/en-US';
|
||||
import locale500 from '@/views/exception/500/locale/en-US';
|
||||
@@ -30,9 +26,6 @@ export default {
|
||||
...localeSettings,
|
||||
...localeMessageBox,
|
||||
...localeLogin,
|
||||
...localeWorkplace,
|
||||
|
||||
...localeMonitor,
|
||||
...locale403,
|
||||
...locale404,
|
||||
...locale500,
|
||||
|
||||
+8
-8
@@ -1,10 +1,6 @@
|
||||
import localeMessageBox from '@/components/message-box/locale/zh-CN';
|
||||
import localeLogin from '@/views/login/locale/zh-CN';
|
||||
|
||||
import localeWorkplace from '@/views/dashboard/workplace/locale/zh-CN';
|
||||
|
||||
import localeMonitor from '@/views/dashboard/monitor/locale/zh-CN';
|
||||
|
||||
import locale403 from '@/views/exception/403/locale/zh-CN';
|
||||
import locale404 from '@/views/exception/404/locale/zh-CN';
|
||||
import locale500 from '@/views/exception/500/locale/zh-CN';
|
||||
@@ -41,19 +37,22 @@ export default {
|
||||
'content': '内容',
|
||||
'table.filter': '筛选',
|
||||
'table.export': '导出',
|
||||
'table.import': '导入',
|
||||
'watch-detail': '查看详情',
|
||||
'menu.business.project': '项目管理',
|
||||
'menu.business.space': '空间管理',
|
||||
'menu.business.house-view': '房源剖视',
|
||||
'menu.business.house-list': '房源列表',
|
||||
'menu.business.house-atlas': '房源图谱',
|
||||
'menu.business.contract': '合同管理',
|
||||
'menu.business.contract-list': '合同列表',
|
||||
'menu.business.contract-file': '合同归档',
|
||||
'menu.business.contract-template': '合同模板',
|
||||
'menu.business.tenant': '租客管理',
|
||||
'menu.business.tenant-list': '租客列表',
|
||||
'menu.business.tenant.contacts': '租客联系人',
|
||||
'menu.business.tenant-file': '租客归档',
|
||||
'add-project': '新建项目',
|
||||
'edit-project': '编辑项目',
|
||||
'menu.setting.user': '用户设置',
|
||||
'menu.setting.user-list': '用户列表',
|
||||
'menu.setting.role': '角色权限',
|
||||
@@ -123,13 +122,14 @@ export default {
|
||||
'setting.contract': '合同设置',
|
||||
'setting.finance': '财务设置',
|
||||
'setting.data-dictionary': '数据字典',
|
||||
'add-house': '新建房源',
|
||||
'edit-house': '编辑房源',
|
||||
'add-tenant': '新建租客',
|
||||
'edit-tenant': '编辑租客',
|
||||
|
||||
...localeSettings,
|
||||
...localeMessageBox,
|
||||
...localeLogin,
|
||||
...localeWorkplace,
|
||||
|
||||
...localeMonitor,
|
||||
...locale403,
|
||||
...locale404,
|
||||
...locale500,
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import { AppRouteRecordRaw } from '../types';
|
||||
|
||||
const RESULT: AppRouteRecordRaw = {
|
||||
path: '/result',
|
||||
name: 'result',
|
||||
component: DEFAULT_LAYOUT,
|
||||
redirect: '/result/success',
|
||||
meta: {
|
||||
locale: 'menu.result',
|
||||
icon: 'icon-check-circle',
|
||||
requiresAuth: true,
|
||||
order: 5,
|
||||
isFirst: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'success',
|
||||
name: 'Success',
|
||||
component: () => import('@/views/result/success/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.result.success',
|
||||
requiresAuth: true,
|
||||
roles: ['admin'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'error',
|
||||
name: 'Error',
|
||||
component: () => import('@/views/result/error/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.result.error',
|
||||
requiresAuth: true,
|
||||
roles: ['admin'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default RESULT;
|
||||
@@ -1,41 +0,0 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import { AppRouteRecordRaw } from '../types';
|
||||
|
||||
const VISUALIZATION: AppRouteRecordRaw = {
|
||||
path: '/visualization',
|
||||
name: 'visualization',
|
||||
component: DEFAULT_LAYOUT,
|
||||
redirect: '/visualization/data-analysis',
|
||||
meta: {
|
||||
locale: 'menu.visualization',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-apps',
|
||||
order: 1,
|
||||
isFirst: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'data-analysis',
|
||||
name: 'DataAnalysis',
|
||||
component: () => import('@/views/visualization/data-analysis/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.visualization.dataAnalysis',
|
||||
requiresAuth: true,
|
||||
roles: ['admin'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'multi-dimension-data-analysis',
|
||||
name: 'MultiDimensionDataAnalysis',
|
||||
component: () =>
|
||||
import('@/views/visualization/multi-dimension-data-analysis/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.visualization.multiDimensionDataAnalysis',
|
||||
requiresAuth: true,
|
||||
roles: ['admin'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default VISUALIZATION;
|
||||
@@ -55,7 +55,7 @@ const BUSINESS: AppRouteRecordRaw = {
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "business-space" */
|
||||
'@/views/business/space/house-view.vue'
|
||||
'@/views/business/space/house-view/index.vue'
|
||||
),
|
||||
meta: {
|
||||
locale: 'menu.business.house-view',
|
||||
@@ -69,10 +69,7 @@ const BUSINESS: AppRouteRecordRaw = {
|
||||
path: 'house-list',
|
||||
name: 'business-space-house-list',
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "business-space" */
|
||||
'@/views/business/space/house-list.vue'
|
||||
),
|
||||
import('@/views/business/space/house-list/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.business.house-list',
|
||||
requiresAuth: true,
|
||||
@@ -81,6 +78,20 @@ const BUSINESS: AppRouteRecordRaw = {
|
||||
permissionKey: 'businessHouseList',
|
||||
},
|
||||
},
|
||||
// 房源图谱
|
||||
{
|
||||
path: 'house-atlas',
|
||||
name: 'business-space-house-atlas',
|
||||
component: () =>
|
||||
import('@/views/business/space/house-atlas/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.business.house-atlas',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
icon: 'icon-brush',
|
||||
permissionKey: 'businessHouseAtlas',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// 合同管理
|
||||
@@ -171,7 +182,7 @@ const BUSINESS: AppRouteRecordRaw = {
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "business-tenant" */
|
||||
'@/views/business/tenant/list.vue'
|
||||
'@/views/business/tenant/list/index.vue'
|
||||
),
|
||||
meta: {
|
||||
locale: 'menu.business.tenant-list',
|
||||
@@ -183,15 +194,15 @@ const BUSINESS: AppRouteRecordRaw = {
|
||||
},
|
||||
// 租客联系人
|
||||
{
|
||||
path: 'tenant-contacts',
|
||||
name: 'business-tenant-contacts',
|
||||
path: 'tenant-file',
|
||||
name: 'business-tenant-file',
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "business-tenant" */
|
||||
'@/views/business/tenant/contacts.vue'
|
||||
'@/views/business/tenant/file/index.vue'
|
||||
),
|
||||
meta: {
|
||||
locale: 'menu.business.tenant.contacts',
|
||||
locale: 'menu.business.tenant-file',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
icon: 'icon-font-colors',
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import PageLayout from '@/layout/page-layout.vue';
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import { AppRouteRecordRaw } from '../types';
|
||||
|
||||
const DASHBOARD: AppRouteRecordRaw = {
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
component: DEFAULT_LAYOUT,
|
||||
redirect: '/dashboard/workplace',
|
||||
meta: {
|
||||
locale: 'menu.dashboard.workplace',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-dashboard',
|
||||
order: 1,
|
||||
isFirst: true,
|
||||
permissionKey: 'dashboard',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'workplace',
|
||||
name: 'Workplace',
|
||||
component: PageLayout,
|
||||
redirect: '/dashboard/workplace/step',
|
||||
meta: {
|
||||
locale: 'menu.dashboard.workplace',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
icon: 'icon-dashboard',
|
||||
permissionKey: 'workplace',
|
||||
// activeMenu: 'dashboard',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'step',
|
||||
name: 'Step',
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "dashboard-workplace" */
|
||||
/* webpackPreload: true */
|
||||
'@/views/dashboard/workplace/index.vue'
|
||||
),
|
||||
meta: {
|
||||
locale: 'menu.form.step',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
icon: 'icon-exclamation-circle',
|
||||
permissionKey: 'step',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
path: 'monitor',
|
||||
name: 'Monitor',
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "dashboard-monitor" */
|
||||
'@/views/dashboard/monitor/index.vue'
|
||||
),
|
||||
meta: {
|
||||
locale: 'menu.dashboard.monitor',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
icon: 'icon-settings',
|
||||
permissionKey: 'monitor',
|
||||
// activeMenu: 'dashboard',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default DASHBOARD;
|
||||
@@ -1,9 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { Notification } from '@arco-design/web-vue';
|
||||
import type { NotificationReturn } from '@arco-design/web-vue/es/notification/interface';
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import defaultSettings from '@/config/settings.json';
|
||||
import { getMenuList } from '@/api/user';
|
||||
import { AppState } from './types';
|
||||
|
||||
const useAppStore = defineStore('app', {
|
||||
@@ -42,7 +39,7 @@ const useAppStore = defineStore('app', {
|
||||
actions: {
|
||||
// Update app settings
|
||||
updateSettings(partial: Partial<AppState>) {
|
||||
// @ts-ignore-next-line
|
||||
// @ts-expect-error pinia $patch typing with partial
|
||||
this.$patch(partial);
|
||||
},
|
||||
|
||||
@@ -62,30 +59,6 @@ const useAppStore = defineStore('app', {
|
||||
toggleMenu(value: boolean) {
|
||||
this.hideMenu = value;
|
||||
},
|
||||
async fetchServerMenuConfig() {
|
||||
let notifyInstance: NotificationReturn | null = null;
|
||||
try {
|
||||
notifyInstance = Notification.info({
|
||||
id: 'menuNotice', // Keep the instance id the same
|
||||
content: 'loading',
|
||||
closable: true,
|
||||
});
|
||||
const { data } = await getMenuList();
|
||||
this.serverMenu = data;
|
||||
notifyInstance = Notification.success({
|
||||
id: 'menuNotice',
|
||||
content: 'success',
|
||||
closable: true,
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
notifyInstance = Notification.error({
|
||||
id: 'menuNotice',
|
||||
content: 'error',
|
||||
closable: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
clearServerMenu() {
|
||||
this.serverMenu = [];
|
||||
},
|
||||
|
||||
@@ -30,6 +30,8 @@ const useUserStore = defineStore('user', {
|
||||
certification: undefined,
|
||||
role: '',
|
||||
permission: [],
|
||||
projects: undefined,
|
||||
currentProjectId: undefined,
|
||||
}),
|
||||
|
||||
// 配置持久化
|
||||
@@ -37,7 +39,16 @@ const useUserStore = defineStore('user', {
|
||||
key: 'user-store',
|
||||
storage: localStorage,
|
||||
// 只持久化关键的用户信息,排除敏感信息
|
||||
pick: ['name', 'avatar', 'role', 'permission', 'accountId', 'email'],
|
||||
pick: [
|
||||
'name',
|
||||
'avatar',
|
||||
'role',
|
||||
'permission',
|
||||
'accountId',
|
||||
'email',
|
||||
'projects',
|
||||
'currentProjectId',
|
||||
],
|
||||
},
|
||||
|
||||
getters: {
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { UserPermission } from '@/types/api';
|
||||
|
||||
export type RoleType = '' | '*' | 'admin' | 'user';
|
||||
|
||||
export interface UserProject {
|
||||
id: number;
|
||||
name: string;
|
||||
image?: string;
|
||||
imageUrl?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface UserState {
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
@@ -19,4 +28,6 @@ export interface UserState {
|
||||
certification?: number;
|
||||
role: RoleType;
|
||||
permission: UserPermission[];
|
||||
projects?: UserProject[];
|
||||
currentProjectId?: number;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ export interface ProjectItem extends BaseEntity {
|
||||
roomArea: number;
|
||||
rentArea: number;
|
||||
useArea: number;
|
||||
keepArea: number;
|
||||
rentRate: number;
|
||||
RoomAreaStr: string; // 列表显示的计租面积
|
||||
RentAreaStr: string; // 列表显示的招商面积
|
||||
@@ -114,3 +115,31 @@ export interface ProjectInfo extends BaseEntity {
|
||||
export type ProjectDetailRes = HttpResponse<{
|
||||
project: ProjectItem;
|
||||
}>;
|
||||
|
||||
// 楼栋标签响应类型
|
||||
export interface BuildingTag {
|
||||
buildingId: number;
|
||||
projectName: string;
|
||||
buildingName: string;
|
||||
}
|
||||
|
||||
export type TopBuildingTagsRes = HttpResponse<{
|
||||
tags: BuildingTag[];
|
||||
}>;
|
||||
|
||||
// 创建房源参数
|
||||
export interface CreateHouseParam {
|
||||
id?: number; // 编辑时需要
|
||||
projectId: number; // 项目id
|
||||
buildingId: number; // 楼栋id
|
||||
floorId: number; // 楼层id
|
||||
name: string; // 房源名称
|
||||
rentArea: number | string; // 计租面积
|
||||
price: number | string; // 价格
|
||||
priceType: number; // 价格类型
|
||||
area: number | string; // 面积
|
||||
useArea: number | string; // 使用面积
|
||||
status: number; // 状态
|
||||
objectKeys: string[]; // 图片数组
|
||||
tags: number[]; // 标签数组
|
||||
}
|
||||
|
||||
@@ -15,12 +15,6 @@ function preloadRouteComponent(routeName: string): void {
|
||||
'@/views/business/project/index.vue'
|
||||
);
|
||||
break;
|
||||
case 'dashboard-workplace':
|
||||
import(
|
||||
/* webpackChunkName: "dashboard-workplace" */
|
||||
'@/views/dashboard/workplace/index.vue'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
// 未知路由,跳过预加载
|
||||
break;
|
||||
@@ -63,7 +57,7 @@ export function smartPreload(
|
||||
userRole: string,
|
||||
currentRoute?: RouteLocationNormalized
|
||||
): void {
|
||||
const commonRoutes = ['dashboard-workplace'];
|
||||
const commonRoutes: string[] = [];
|
||||
|
||||
// 根据用户角色预加载不同的路由
|
||||
const roleBasedRoutes: Record<string, string[]> = {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<a-drawer
|
||||
:width="670"
|
||||
:visible="visible"
|
||||
:title="isEdit ? '编辑条款设置' : '新增条款设置'"
|
||||
:title="isEdit ? $t('edit-project') : $t('add-project')"
|
||||
unmount-on-close
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
@@ -185,11 +185,11 @@
|
||||
getCityListByProvinceId,
|
||||
getDistrictListByCityId,
|
||||
} from '@/api/common';
|
||||
import { createProject, updateProject } from '@/api/business';
|
||||
import { createProject, updateProject } from '@/api/project';
|
||||
import { CreateProjectParam } from '@/types/business';
|
||||
import { projectTypeInfo } from '@/types/setting';
|
||||
import ImageUpload from '@/components/common/ImageUpload.vue';
|
||||
import { getAllProjectTypeList } from '@/api/setting';
|
||||
import { getAllProjectTypeList } from '@/api/dictionary';
|
||||
import draggable from 'vuedraggable';
|
||||
import { generateRandomId } from '@/utils';
|
||||
|
||||
@@ -374,7 +374,7 @@
|
||||
emit('update:visible', false);
|
||||
};
|
||||
const handleDragEnd = () => {
|
||||
console.log('handleDragEnd');
|
||||
// 拖拽结束处理
|
||||
};
|
||||
onMounted(() => {
|
||||
getProvinceListData();
|
||||
|
||||
@@ -7,20 +7,70 @@
|
||||
unmount-on-close
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
项目详情
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p>{{ detailInfo?.name }}</p>
|
||||
<p class="mt-[10px]">
|
||||
<a-tag :color="getTypeColor(detailInfo?.type)">{{
|
||||
detailInfo?.typeName
|
||||
}}</a-tag>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-[10px]">
|
||||
<p>{{ detailInfo?.updatedAt }}</p>
|
||||
<div>
|
||||
<a-button @click="handleEdit">
|
||||
<template #icon>
|
||||
<icon-edit />
|
||||
</template>
|
||||
{{ $t('edit') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<!-- <a-button type="primary" @click="handleDelete">删除</a-button> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[10px] grid grid-cols-3 gap-[10px]">
|
||||
<a-card title="面积信息">
|
||||
<div class="ring-chart-container">
|
||||
<Chart :options="ringChartOption" height="200px" />
|
||||
</div>
|
||||
</a-card>
|
||||
<a-card title="房源统计">
|
||||
<div class="grid grid-cols-3 gap-[10px] text-center">
|
||||
<div>
|
||||
<p class="text-[18px] font-bold">{{ detailInfo?.roomNum }}</p>
|
||||
<p>房源总数</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-[18px] font-bold">{{ detailInfo?.roomsRentNum }}</p>
|
||||
<p>已出租</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-[18px] font-bold">{{
|
||||
detailInfo?.roomsUnRentNum
|
||||
}}</p>
|
||||
<p>空置</p>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
<a-card title="租客信息"></a-card>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch, onMounted } from 'vue';
|
||||
import { watch, onMounted, computed } from 'vue';
|
||||
// import { Message } from '@arco-design/web-vue';
|
||||
// import { projectTypeList } from '@/utils/constant';
|
||||
import { ProjectItem } from '@/types/business';
|
||||
import Chart from '@/components/chart/index.vue';
|
||||
import type { EChartsOption } from 'echarts';
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
projectId: number | null;
|
||||
detailInfo: ProjectItem | null;
|
||||
projectTypeList: any[];
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
@@ -36,6 +86,158 @@
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
const handleEdit = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
const getTypeColor = (type: number | undefined) => {
|
||||
if (!type) return '';
|
||||
const color = props.projectTypeList.find((item) => item.id === type)?.color;
|
||||
return color || '';
|
||||
};
|
||||
|
||||
// 格式化面积数值,保留2位小数
|
||||
const formatArea = (value: number): number => {
|
||||
return Math.round(value * 100) / 100;
|
||||
};
|
||||
|
||||
// 格式化面积显示,添加千分位分隔符
|
||||
const formatAreaDisplay = (value: number): string => {
|
||||
if (!value && value !== 0) return '0';
|
||||
return formatArea(value).toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
};
|
||||
|
||||
// 计算环形图数据
|
||||
const ringChartData = computed(() => {
|
||||
if (!props.detailInfo) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { roomArea = 0, rentArea = 0, keepArea = 0 } = props.detailInfo;
|
||||
|
||||
// 确保数值有效
|
||||
const totalArea = Number(roomArea) || 0;
|
||||
const rentedArea = Number(rentArea) || 0;
|
||||
const reservedArea = Number(keepArea) || 0;
|
||||
|
||||
// 可租面积 = 计租面积 - 在租面积 - 保留面积
|
||||
const availableArea = Math.max(0, totalArea - rentedArea - reservedArea);
|
||||
|
||||
// 构建图表数据,如果总面积大于0,显示所有三个部分(即使某些为0)
|
||||
const data: Array<{ name: string; value: number }> = [];
|
||||
|
||||
// 如果总面积大于0,显示所有分类
|
||||
if (totalArea > 0) {
|
||||
// 在租面积
|
||||
data.push({
|
||||
name: '在租面积',
|
||||
value: formatArea(rentedArea),
|
||||
});
|
||||
|
||||
// 保留面积
|
||||
data.push({
|
||||
name: '保留面积',
|
||||
value: formatArea(reservedArea),
|
||||
});
|
||||
|
||||
// 可租面积
|
||||
data.push({
|
||||
name: '可租面积',
|
||||
value: formatArea(availableArea),
|
||||
});
|
||||
} else {
|
||||
// 如果所有数据都为0,显示占位数据
|
||||
data.push({
|
||||
name: '计租面积',
|
||||
value: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// 调试信息(开发时使用)
|
||||
console.log('环形图数据:', {
|
||||
roomArea: totalArea,
|
||||
rentArea: rentedArea,
|
||||
keepArea: reservedArea,
|
||||
availableArea,
|
||||
chartData: data,
|
||||
});
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
// 环形图配置
|
||||
const ringChartOption = computed<EChartsOption>(() => {
|
||||
const data = ringChartData.value;
|
||||
|
||||
if (data.length === 0) {
|
||||
return {
|
||||
title: {
|
||||
text: '暂无数据',
|
||||
left: 'center',
|
||||
top: 'middle',
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
fontSize: 14,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: (params: any) => {
|
||||
const { name, value, percent } = params;
|
||||
return `${name}<br/>${formatAreaDisplay(value)}㎡ (${percent}%)`;
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
top: 'middle',
|
||||
itemWidth: 12,
|
||||
itemHeight: 12,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '面积信息',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'], // 环形图:内半径40%,外半径70%
|
||||
center: ['60%', '50%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 4,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}\n{d}%',
|
||||
fontSize: 12,
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
data: data,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
@@ -43,4 +245,33 @@
|
||||
:deep(label.arco-form-item-label) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.ring-chart-container {
|
||||
position: relative;
|
||||
min-height: 300px;
|
||||
|
||||
.area-summary {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--color-border-2);
|
||||
|
||||
.summary-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.summary-label {
|
||||
color: var(--color-text-2);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
color: var(--color-text-1);
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -41,8 +41,8 @@
|
||||
<div class="slider">
|
||||
<a-slider
|
||||
v-model="rentRate"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:min="rentRateMin"
|
||||
:max="rentRateMax"
|
||||
range
|
||||
:marks="marks"
|
||||
></a-slider>
|
||||
@@ -51,12 +51,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { getProjectCityList } from '@/api/business';
|
||||
import { CommonCityItem } from '@/types/comm';
|
||||
import { ref, watch, onMounted, computed } from 'vue';
|
||||
import { getAllProjectTypeList } from '@/api/dictionary';
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
projectList?: any[]; // 项目列表数据,用于提取筛选选项
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
@@ -66,30 +66,120 @@
|
||||
typeIds: number[] | null;
|
||||
cityIds: number[] | null;
|
||||
rentRate: number | number[];
|
||||
filteredList: any[]; // 筛选后的列表数据
|
||||
}
|
||||
): void;
|
||||
}>();
|
||||
// 保存的筛选状态
|
||||
const savedFilterState = ref<{
|
||||
projectTypeChecked: number[];
|
||||
projectCityChecked: number[];
|
||||
rentRate: number | number[];
|
||||
} | null>(null);
|
||||
|
||||
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 rentRate = ref<number | number[]>([0, 100]);
|
||||
|
||||
// 所有项目类型列表(从API获取)
|
||||
const allProjectTypeList = ref<any[]>([]);
|
||||
|
||||
// 获取项目类型列表
|
||||
const getAllProjectTypeListData = async () => {
|
||||
try {
|
||||
const { code, data } = await getAllProjectTypeList();
|
||||
if (code === 200 && data) {
|
||||
allProjectTypeList.value = data?.projectTypesList || [];
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 项目类型列表(从项目列表中提取实际存在的类型)
|
||||
const projectTypeList = computed(() => {
|
||||
const result = [{ text: '全部', value: 0 }];
|
||||
|
||||
// 如果项目列表为空,返回所有项目类型
|
||||
if (!props.projectList || props.projectList.length === 0) {
|
||||
if (allProjectTypeList.value.length > 0) {
|
||||
allProjectTypeList.value.forEach((type) => {
|
||||
result.push({
|
||||
text: type.name,
|
||||
value: type.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 从项目列表中提取实际存在的类型ID
|
||||
const existingTypeIds = new Set<number>();
|
||||
props.projectList.forEach((project) => {
|
||||
if (project.type) {
|
||||
existingTypeIds.add(project.type);
|
||||
}
|
||||
});
|
||||
|
||||
// 从所有项目类型中筛选出实际存在的类型
|
||||
allProjectTypeList.value.forEach((type) => {
|
||||
if (existingTypeIds.has(type.id)) {
|
||||
result.push({
|
||||
text: type.name,
|
||||
value: type.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
// 城市列表(从项目列表中提取唯一值)
|
||||
const projectCityList = computed(() => {
|
||||
if (!props.projectList || props.projectList.length === 0) {
|
||||
return [{ id: 0, code: '0', name: '全部' }];
|
||||
}
|
||||
const cityMap = new Map();
|
||||
// 添加"全部"选项
|
||||
cityMap.set(0, { id: 0, code: '0', name: '全部' });
|
||||
// 从项目列表中提取城市
|
||||
props.projectList.forEach((project) => {
|
||||
if (project.cityId && project.city) {
|
||||
if (!cityMap.has(project.cityId)) {
|
||||
cityMap.set(project.cityId, {
|
||||
id: project.cityId,
|
||||
code: String(project.cityId),
|
||||
name: project.city,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return Array.from(cityMap.values());
|
||||
});
|
||||
|
||||
// 出租率范围
|
||||
const rentRateMin = computed(() => {
|
||||
if (!props.projectList || props.projectList.length === 0) return 0;
|
||||
const rates = props.projectList
|
||||
.map((project) => Number(project.rentRate))
|
||||
.filter((rate) => !isNaN(rate) && rate >= 0);
|
||||
return rates.length > 0 ? Math.min(...rates) : 0;
|
||||
});
|
||||
|
||||
const rentRateMax = computed(() => {
|
||||
if (!props.projectList || props.projectList.length === 0) return 100;
|
||||
const rates = props.projectList
|
||||
.map((project) => Number(project.rentRate))
|
||||
.filter((rate) => !isNaN(rate) && rate >= 0);
|
||||
return rates.length > 0 ? Math.max(...rates) : 100;
|
||||
});
|
||||
|
||||
const marks = computed(() => {
|
||||
return {
|
||||
[rentRateMin.value]: `${rentRateMin.value}%`,
|
||||
[rentRateMax.value]: `${rentRateMax.value}%`,
|
||||
};
|
||||
});
|
||||
const handleCheckTypeIds = (id: number) => {
|
||||
if (typeof id !== 'number') return;
|
||||
if (id === 0) {
|
||||
@@ -129,26 +219,114 @@
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
// 筛选项目列表数据
|
||||
const filterProjectList = () => {
|
||||
if (!props.projectList || props.projectList.length === 0) return [];
|
||||
|
||||
let filtered = [...props.projectList];
|
||||
|
||||
// 筛选项目类型
|
||||
const normalizedTypeIds = normalizeIds(projectTypeChecked.value);
|
||||
if (normalizedTypeIds && normalizedTypeIds.length > 0) {
|
||||
filtered = filtered.filter((project) =>
|
||||
normalizedTypeIds.includes(project.type)
|
||||
);
|
||||
}
|
||||
|
||||
// 筛选城市
|
||||
const normalizedCityIds = normalizeIds(projectCityChecked.value);
|
||||
if (normalizedCityIds && normalizedCityIds.length > 0) {
|
||||
filtered = filtered.filter((project) =>
|
||||
normalizedCityIds.includes(project.cityId)
|
||||
);
|
||||
}
|
||||
|
||||
// 筛选出租率
|
||||
const [minRate, maxRate] = Array.isArray(rentRate.value)
|
||||
? rentRate.value
|
||||
: [0, 100];
|
||||
filtered = filtered.filter((project) => {
|
||||
const rate = Number(project.rentRate);
|
||||
return !isNaN(rate) && rate >= minRate && rate <= maxRate;
|
||||
});
|
||||
|
||||
return filtered;
|
||||
};
|
||||
|
||||
const normalizeIds = (ids: number[]) =>
|
||||
ids.length === 1 && ids[0] === 0 ? null : ids;
|
||||
const handleOk = () => {
|
||||
// 保存当前筛选状态
|
||||
savedFilterState.value = {
|
||||
projectTypeChecked: [...projectTypeChecked.value],
|
||||
projectCityChecked: [...projectCityChecked.value],
|
||||
rentRate: Array.isArray(rentRate.value)
|
||||
? [...rentRate.value]
|
||||
: rentRate.value,
|
||||
};
|
||||
|
||||
const filteredList = filterProjectList();
|
||||
emit('confirm', {
|
||||
typeIds: normalizeIds(projectTypeChecked.value),
|
||||
cityIds: normalizeIds(projectCityChecked.value),
|
||||
rentRate: rentRate.value,
|
||||
filteredList, // 筛选后的列表数据
|
||||
});
|
||||
handleCancel();
|
||||
};
|
||||
// 初始化筛选表单
|
||||
const initFilterForm = () => {
|
||||
// 如果有保存的状态,恢复它
|
||||
if (savedFilterState.value) {
|
||||
projectTypeChecked.value = [...savedFilterState.value.projectTypeChecked];
|
||||
projectCityChecked.value = [...savedFilterState.value.projectCityChecked];
|
||||
rentRate.value = Array.isArray(savedFilterState.value.rentRate)
|
||||
? [...savedFilterState.value.rentRate]
|
||||
: savedFilterState.value.rentRate;
|
||||
} else {
|
||||
// 否则使用默认值
|
||||
projectTypeChecked.value = [0];
|
||||
projectCityChecked.value = [0];
|
||||
const min = rentRateMin.value || 0;
|
||||
const max = rentRateMax.value || 100;
|
||||
rentRate.value = [min, max];
|
||||
}
|
||||
};
|
||||
|
||||
// 监听项目列表变化,更新出租率范围
|
||||
watch(
|
||||
() => props.projectList,
|
||||
() => {
|
||||
if (props.visible) {
|
||||
const min = rentRateMin.value || 0;
|
||||
const max = rentRateMax.value || 100;
|
||||
// 如果当前出租率范围不在新的范围内,则更新
|
||||
const currentRate = Array.isArray(rentRate.value)
|
||||
? rentRate.value
|
||||
: [0, 100];
|
||||
if (currentRate[0] < min || currentRate[1] > max) {
|
||||
rentRate.value = [min, max];
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (val) => {
|
||||
if (val && projectCityList.value.length === 0) {
|
||||
getProjectCityListData();
|
||||
(val) => {
|
||||
if (val) {
|
||||
initFilterForm();
|
||||
if (allProjectTypeList.value.length === 0) {
|
||||
getAllProjectTypeListData();
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
onMounted(() => {});
|
||||
|
||||
onMounted(() => {
|
||||
getAllProjectTypeListData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</a-button>
|
||||
<a-button>
|
||||
<template #icon>
|
||||
<icon-download />
|
||||
<icon-export />
|
||||
</template>
|
||||
{{ t('table.export') }}
|
||||
</a-button>
|
||||
@@ -47,7 +47,7 @@
|
||||
@page-size-change="onPageSizeChange"
|
||||
>
|
||||
<template #type="{ record }">
|
||||
<a-tag checkable :color="record.color">
|
||||
<a-tag checkable :color="getTypeColor(record.type)">
|
||||
{{ record.typeName }}
|
||||
</a-tag>
|
||||
</template>
|
||||
@@ -71,9 +71,20 @@
|
||||
<a-button @click="handleWatchHouse(record.id)">
|
||||
查看房源
|
||||
</a-button>
|
||||
<a-button status="danger" @click="handleDel(record.id)">
|
||||
删除项目
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
:ok-text="$t('cancel')"
|
||||
:cancel-text="$t('delete')"
|
||||
:ok-button-props="{ type: 'outline' }"
|
||||
:cancel-button-props="{
|
||||
type: 'primary',
|
||||
status: 'danger',
|
||||
}"
|
||||
content="确定删除该项目?"
|
||||
type="error"
|
||||
@cancel="handleDel(record?.id)"
|
||||
>
|
||||
<a-button status="danger">删除项目</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-popover>
|
||||
@@ -91,9 +102,11 @@
|
||||
v-model:visible="showProjectInfoDrawer"
|
||||
:project-id="currentProjectId"
|
||||
:detail-info="currentProjectInfo"
|
||||
:project-type-list="projectTypeList"
|
||||
/>
|
||||
<projectListFilterDrawer
|
||||
v-model:visible="showListFilterDrawer"
|
||||
:project-list="projectList"
|
||||
@confirm="handleFilterConfirm"
|
||||
/>
|
||||
</div>
|
||||
@@ -106,12 +119,13 @@
|
||||
import type { TableColumnData } from '@arco-design/web-vue';
|
||||
import useDataTable from '@/composables/useDataTable';
|
||||
import { ProjectItem } from '@/types/business';
|
||||
import { getProjectList, getProjectDetail } from '@/api/business';
|
||||
import { getProjectList, getProjectDetail, delProject } from '@/api/project';
|
||||
import { getAllProjectTypeList } from '@/api/dictionary';
|
||||
import { ProjectPaginationParams } from '@/types/comm';
|
||||
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';
|
||||
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
const { t } = useI18n();
|
||||
const { loading } = useLoading(false);
|
||||
const showDrawer = ref<boolean>(false);
|
||||
@@ -155,7 +169,13 @@
|
||||
},
|
||||
{ title: '房源数量', dataIndex: 'roomNum', key: 'roomNum' },
|
||||
{ title: '创建时间', dataIndex: 'createdAt', key: 'createdAt' },
|
||||
{ title: '操作', key: 'operate', slotName: 'operate', width: 200 },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'operate',
|
||||
align: 'center',
|
||||
slotName: 'operate',
|
||||
width: 300,
|
||||
},
|
||||
];
|
||||
const queryParams = ref<ProjectPaginationParams>({
|
||||
page: current.value,
|
||||
@@ -184,7 +204,11 @@
|
||||
!edit
|
||||
)
|
||||
return;
|
||||
edit ? (isLoadingEdit.value = true) : (isLoadingDetail.value = true);
|
||||
if (edit) {
|
||||
isLoadingEdit.value = true;
|
||||
} else {
|
||||
isLoadingDetail.value = true;
|
||||
}
|
||||
const { code, data } = await getProjectDetail(id);
|
||||
if (code === 200 && data) {
|
||||
currentProjectInfo.value = data.project;
|
||||
@@ -194,20 +218,41 @@
|
||||
};
|
||||
const handleWatchHouse = async (id: number) => {
|
||||
try {
|
||||
console.log(id, 'is id');
|
||||
// TODO: 实现查看房源功能
|
||||
} 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 handleFilterConfirm = (payload: {
|
||||
typeIds: number[] | null;
|
||||
cityIds: number[] | null;
|
||||
rentRate: number | number[];
|
||||
filteredList: any[]; // 筛选后的列表数据
|
||||
}) => {
|
||||
// 更新查询参数(用于后端筛选)
|
||||
queryParams.value.type = payload.typeIds;
|
||||
queryParams.value.province_ids = payload.cityIds;
|
||||
const rentRateArray = Array.isArray(payload.rentRate)
|
||||
? payload.rentRate
|
||||
: [0, 100];
|
||||
queryParams.value.rent_rate_min = rentRateArray[0];
|
||||
queryParams.value.rent_rate_max = rentRateArray[1];
|
||||
|
||||
// 使用筛选后的列表数据更新表格
|
||||
if (payload.filteredList) {
|
||||
setData(payload.filteredList);
|
||||
setTotal(payload.filteredList.length);
|
||||
} else {
|
||||
// 如果没有筛选数据,重新加载列表数据
|
||||
getProjectListData();
|
||||
}
|
||||
};
|
||||
const handleDel = (id: number) => {
|
||||
console.log(id, 'iiii');
|
||||
const handleDel = async (id: number) => {
|
||||
const { code, message } = await delProject(id);
|
||||
if (code === 200) {
|
||||
Message.success(message);
|
||||
getProjectListData();
|
||||
}
|
||||
};
|
||||
// 分页事件处理
|
||||
const onPageChange = (page: number) => {
|
||||
@@ -240,8 +285,20 @@
|
||||
const handleFilter = () => {
|
||||
showListFilterDrawer.value = true;
|
||||
};
|
||||
onMounted(() => {
|
||||
setColumns(columns);
|
||||
getProjectListData();
|
||||
const projectTypeList = ref<any[]>([]);
|
||||
const getAllProjectTypeListData = async () => {
|
||||
const { code, data } = await getAllProjectTypeList();
|
||||
if (code === 200 && data) {
|
||||
projectTypeList.value = data?.projectTypesList;
|
||||
}
|
||||
};
|
||||
const getTypeColor = (type: number) => {
|
||||
const color = projectTypeList.value.find((item) => item.id === type)?.color;
|
||||
return color || '';
|
||||
};
|
||||
onMounted(async () => {
|
||||
await setColumns(columns);
|
||||
await getAllProjectTypeListData();
|
||||
await getProjectListData();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,409 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
:width="670"
|
||||
:visible="visible"
|
||||
:title="isEdit ? $t('edit-house') : $t('add-house')"
|
||||
unmount-on-close
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
auto-label-width
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<!-- 房源名称 -->
|
||||
<a-form-item label="房源名称" field="name" required>
|
||||
<a-input
|
||||
v-model="form.name"
|
||||
:placeholder="$t('please.enter', { placeholder: '房源名称' })"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<!-- 房源状态 -->
|
||||
<a-form-item label="房源状态" field="status" required>
|
||||
<a-select v-model="form.status" placeholder="请选择房源状态">
|
||||
<a-option
|
||||
v-for="item in HOUSE_STATUS"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.text }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<!-- 所属项目 -->
|
||||
<a-form-item label="所属项目" field="projectId" required>
|
||||
<a-select
|
||||
v-model="form.projectId"
|
||||
placeholder="请选择所属项目"
|
||||
@change="handleProjectChange"
|
||||
>
|
||||
<a-option
|
||||
v-for="item in allProjetList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<!-- 所属楼栋/楼层 -->
|
||||
<a-form-item label="所属楼栋/楼层" field="buildingId" required>
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="12">
|
||||
<a-select
|
||||
v-model="form.buildingId"
|
||||
placeholder="请选择所属楼栋"
|
||||
:disabled="!form.projectId"
|
||||
@change="handleBuildingChange"
|
||||
>
|
||||
<a-option
|
||||
v-for="item in allBuildingList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-select
|
||||
v-model="form.floorId"
|
||||
placeholder="请选择所属楼层"
|
||||
:disabled="!form.buildingId"
|
||||
>
|
||||
<a-option
|
||||
v-for="item in allFloorList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="计租面积" field="rentArea" required>
|
||||
<a-input-number
|
||||
v-model="form.rentArea"
|
||||
append="㎡"
|
||||
:placeholder="$t('please.enter', { placeholder: '房源计租面积' })"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="招商价格" field="price">
|
||||
<a-input-number
|
||||
v-model="form.price"
|
||||
:placeholder="
|
||||
$t('please.enter', { placeholder: '房源计租招商价格' })
|
||||
"
|
||||
>
|
||||
<template #append>
|
||||
<a-select v-model="form.priceType" style="width: 100px">
|
||||
<a-option
|
||||
v-for="item in PRICE_UNITS"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>{{ item.text }}</a-option
|
||||
>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="建筑面积(可选)" field="area">
|
||||
<a-input
|
||||
v-model="form.area"
|
||||
append="㎡"
|
||||
:placeholder="$t('please.enter', { placeholder: '建筑面积' })"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="使用面积(可选)" field="useArea">
|
||||
<a-input
|
||||
v-model="form.useArea"
|
||||
append="㎡"
|
||||
:placeholder="$t('please.enter', { placeholder: '使用面积' })"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="标签(可选)" field="tags">
|
||||
<a-select v-model="form.tags" multiple placeholder="请选择标签">
|
||||
<a-option
|
||||
v-for="item in roomTagsList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="房源图片(可选)" field="objectKeys">
|
||||
<ImageUpload
|
||||
v-model:object-keys="form.objectKeys"
|
||||
v-model:signed-put-u-r-l="form.signedPutURL"
|
||||
:upload-type="2"
|
||||
:max-count="5"
|
||||
list-type="picture-card"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, reactive, onMounted } from 'vue';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { CreateHouseParam } from '@/types/business';
|
||||
import ImageUpload from '@/components/common/ImageUpload.vue';
|
||||
import { HOUSE_STATUS, PRICE_UNITS } from '@/constants';
|
||||
import {
|
||||
getAllProjectIdNameList,
|
||||
getAllBuildingIdNameList,
|
||||
getAllFloorIdNameList,
|
||||
} from '@/api/common';
|
||||
import { getRoomTagList } from '@/api/dictionary';
|
||||
import { createRoom, updateRoom } from '@/api/room';
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
isEdit: boolean;
|
||||
record?: any | null;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
(e: 'submitted'): void;
|
||||
}>();
|
||||
const isLoading = ref<boolean>(false);
|
||||
// 表单数据
|
||||
const formRef = ref<FormInstance>();
|
||||
const allProjetList = ref<any>([]);
|
||||
const allBuildingList = ref<any>([]);
|
||||
const allFloorList = ref<any>([]);
|
||||
const roomTagsList = ref<any[]>([]);
|
||||
const defaultForm: CreateHouseParam = {
|
||||
projectId: null as any,
|
||||
buildingId: null as any,
|
||||
floorId: null as any,
|
||||
name: '', // 房源名称
|
||||
rentArea: '', // 计租面积
|
||||
price: '', // 价格
|
||||
priceType: 1, // 价格类型
|
||||
area: '', // 面积
|
||||
useArea: '', // 使用面积
|
||||
status: 1, // 状态
|
||||
objectKeys: [], // 图片数组
|
||||
tags: [], // 标签数组
|
||||
};
|
||||
// 扩展表单类型,包含 ImageUpload 需要的 signedPutURL(不提交到后端)
|
||||
const form = reactive<CreateHouseParam & { signedPutURL: string[] }>({
|
||||
...defaultForm,
|
||||
signedPutURL: [],
|
||||
});
|
||||
// 校验规则
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入房源名称' }],
|
||||
projectId: [{ required: true, message: '请选择项目' }],
|
||||
buildingId: [{ required: true, message: '请选择楼栋' }],
|
||||
floorId: [{ required: true, message: '请选择楼层' }],
|
||||
status: [{ required: true, message: '请选择房源状态' }],
|
||||
});
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (val) => {
|
||||
if (val) {
|
||||
if (props.isEdit && props.record) {
|
||||
const record = props.record;
|
||||
// 回显数据 - 使用 Object.assign 批量赋值,处理特殊字段转换
|
||||
Object.assign(form, {
|
||||
id: record.id,
|
||||
projectId: record.projectId,
|
||||
buildingId: record.buildingId,
|
||||
floorId: record.floorId,
|
||||
name: record.name || '',
|
||||
rentArea: record.rentArea || 0,
|
||||
price: record.price || 0,
|
||||
priceType:
|
||||
PRICE_UNITS.find((i) => i.text === record.priceType)?.value ?? 1,
|
||||
area: record.area || 0,
|
||||
useArea: record.useArea || 0,
|
||||
status: record.status || 1,
|
||||
objectKeys: record.objectKeys || record.imageUrls || [],
|
||||
tags: [
|
||||
...new Set(record?.tags?.map((i) => i.id).filter(Boolean) || []),
|
||||
],
|
||||
signedPutURL: record.signedPutURL || [],
|
||||
});
|
||||
// 编辑模式下,需要加载对应的楼栋和楼层列表
|
||||
if (record.projectId) {
|
||||
await getAllBuildingListData(record.projectId);
|
||||
if (record.buildingId) {
|
||||
await getAllFloorListData(record.buildingId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Object.assign(form, { ...defaultForm, signedPutURL: [] });
|
||||
// 清空楼栋和楼层列表
|
||||
allBuildingList.value = [];
|
||||
allFloorList.value = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const handleOk = async () => {
|
||||
if (isLoading.value) return;
|
||||
try {
|
||||
isLoading.value = true;
|
||||
const valid = await formRef.value.validate();
|
||||
if (valid) return;
|
||||
// 手动验证楼层字段(因为楼栋和楼层在同一个form-item中)
|
||||
if (!form.floorId) {
|
||||
formRef.value.setFields({
|
||||
floorId: { status: 'error', message: '请选择楼层' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 构建提交参数,排除 signedPutURL(仅用于前端显示)
|
||||
// 将面积、价格相关字段的空值转为0
|
||||
const params: CreateHouseParam = {
|
||||
projectId: form.projectId,
|
||||
buildingId: form.buildingId,
|
||||
floorId: form.floorId,
|
||||
name: form.name,
|
||||
rentArea: form.rentArea || 0,
|
||||
price: form.price || 0,
|
||||
priceType: form.priceType,
|
||||
area: form.area || 0,
|
||||
useArea: form.useArea || 0,
|
||||
status: form.status,
|
||||
objectKeys: form.objectKeys,
|
||||
tags: form.tags,
|
||||
};
|
||||
// 如果是编辑模式,需要包含 id
|
||||
if (props.isEdit && form.id) {
|
||||
params.id = form.id;
|
||||
}
|
||||
// 调用创建/更新房源的 API
|
||||
const res = props.isEdit
|
||||
? await updateRoom(params)
|
||||
: await createRoom(params);
|
||||
if (res?.code === 200) {
|
||||
Message.success(
|
||||
res?.message || (props.isEdit ? '更新成功' : '创建成功')
|
||||
);
|
||||
// 清空表单数据
|
||||
Object.assign(form, { ...defaultForm, signedPutURL: [] });
|
||||
allBuildingList.value = [];
|
||||
allFloorList.value = [];
|
||||
formRef.value?.clearValidate();
|
||||
emit('submitted');
|
||||
emit('update:visible', false);
|
||||
}
|
||||
} catch (err: any) {
|
||||
Message.error(err?.message || (props.isEdit ? '更新失败' : '创建失败'));
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
async function getAllProjectListData() {
|
||||
try {
|
||||
const { code, data } = await getAllProjectIdNameList();
|
||||
if (code === 200) {
|
||||
allProjetList.value = data?.projectsList || [];
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error('获取项目列表失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getAllBuildingListData(projectId: number) {
|
||||
try {
|
||||
const { code, data } = await getAllBuildingIdNameList(projectId);
|
||||
if (code === 200) {
|
||||
allBuildingList.value = data?.buildingList || [];
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error('获取楼栋列表失败:', error);
|
||||
allBuildingList.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getAllFloorListData(buildingId: number) {
|
||||
try {
|
||||
const { code, data } = await getAllFloorIdNameList(buildingId);
|
||||
if (code === 200) {
|
||||
allFloorList.value = data?.floorList || [];
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error('获取楼层列表失败:', error);
|
||||
allFloorList.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 项目选择变化处理
|
||||
const handleProjectChange = async (projectId: number) => {
|
||||
// 清空楼栋和楼层选择
|
||||
form.buildingId = null as any;
|
||||
form.floorId = null as any;
|
||||
allBuildingList.value = [];
|
||||
allFloorList.value = [];
|
||||
// 如果选择了项目,加载楼栋列表
|
||||
if (projectId) {
|
||||
await getAllBuildingListData(projectId);
|
||||
}
|
||||
};
|
||||
|
||||
// 楼栋选择变化处理
|
||||
const handleBuildingChange = async (buildingId: number) => {
|
||||
// 清空楼层选择
|
||||
form.floorId = null as any;
|
||||
allFloorList.value = [];
|
||||
// 如果选择了楼栋,加载楼层列表
|
||||
if (buildingId) {
|
||||
await getAllFloorListData(buildingId);
|
||||
}
|
||||
};
|
||||
const getAllRoomTagListData = async () => {
|
||||
try {
|
||||
const { code, data } = await getRoomTagList();
|
||||
if (code === 200 && data) {
|
||||
roomTagsList.value = data?.roomTagsList;
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化数据加载
|
||||
getAllProjectListData();
|
||||
getAllRoomTagListData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(label.arco-form-item-label) {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
:width="520"
|
||||
:visible="visible"
|
||||
title="房源详情"
|
||||
:footer="false"
|
||||
unmount-on-close
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p>
|
||||
{{
|
||||
`${detailInfo?.name} -
|
||||
${detailInfo?.projectName}${detailInfo?.buildingName}${detailInfo?.floorName}`
|
||||
}}
|
||||
</p>
|
||||
<div class="mt-[10px]">
|
||||
<a-tag :color="getTagsColor(detailInfo?.status)">{{
|
||||
detailInfo?.statusStr
|
||||
}}</a-tag>
|
||||
<a-tag class="mx-[10px]">{{ detailInfo?.rentArea }}㎡</a-tag>
|
||||
<a-tag>{{ `${detailInfo?.price}${detailInfo?.priceType}` }}</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <a-button>
|
||||
<template #icon>
|
||||
<icon-edit />
|
||||
</template>
|
||||
{{ $t('edit') }}
|
||||
</a-button> -->
|
||||
</div>
|
||||
<div class="mt-[20px]">
|
||||
<a-tabs v-model:active-key="currentTab">
|
||||
<a-tab-pane key="1">
|
||||
<template #title> <icon-calendar /> 基本信息 </template>
|
||||
<a-alert type="warning">房源空置 xx 天</a-alert>
|
||||
<a-descriptions bordered class="mt-[10px]" :column="2">
|
||||
<a-descriptions-item label="房源编码" :span="1">
|
||||
{{ detailInfo?.code || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="房源名称" :span="1">
|
||||
{{ detailInfo?.name || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="所属项目" :span="1">
|
||||
{{ detailInfo?.projectName || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="所属楼栋/楼层" :span="1">
|
||||
<span>{{ detailInfo?.buildingName || '-' }}</span>
|
||||
<span>*</span>
|
||||
<span>{{ detailInfo?.floorName || '-' }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="计租面积" :span="1">
|
||||
{{
|
||||
Number(detailInfo?.rentArea) > 0
|
||||
? `${detailInfo?.rentArea}㎡`
|
||||
: '-'
|
||||
}}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="招商价格" :span="1">
|
||||
{{
|
||||
Number(detailInfo?.price) > 0
|
||||
? `${detailInfo?.rentArea}${detailInfo?.priceType}`
|
||||
: '-'
|
||||
}}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="建筑面积" :span="1">
|
||||
{{ Number(detailInfo?.area) > 0 ? `${detailInfo?.area}㎡` : '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="使用面积" :span="1">
|
||||
{{
|
||||
Number(detailInfo?.useArea) > 0
|
||||
? `${detailInfo?.useArea}㎡`
|
||||
: '-'
|
||||
}}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="标签" :span="2">
|
||||
<template v-if="detailInfo?.tags?.length">
|
||||
<a-tag
|
||||
v-for="item in detailInfo?.tags"
|
||||
:key="item.id || item.name"
|
||||
class="mr-[5px] last:mr-0"
|
||||
>
|
||||
{{ item.name }}</a-tag
|
||||
>
|
||||
</template>
|
||||
<template v-else> - </template>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="房源图片" :span="2">
|
||||
<template v-if="detailInfo?.signedPutURL?.length">
|
||||
<img
|
||||
v-for="(item, index) in detailInfo?.signedPutURL"
|
||||
:key="index"
|
||||
:src="item"
|
||||
alt=""
|
||||
class="mt-[5px] last:mr-0 w-[120px] h-[80px] object-contain"
|
||||
/>
|
||||
</template>
|
||||
<template v-else> - </template>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2">
|
||||
<template #title> <icon-clock-circle /> 合同信息 </template>
|
||||
合同信息与租客信息 待开发
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3">
|
||||
<template #title> <icon-user /> 租客信息 </template>
|
||||
合同信息与租客信息 待开发
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch, onMounted, ref } from 'vue';
|
||||
// import { Message } from '@arco-design/web-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
projectId: number | null;
|
||||
detailInfo: any;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
}>();
|
||||
const currentTab = ref<string>('1');
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (val) => {
|
||||
// 抽屉显示状态变化
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
const getTagsColor = (status) => {
|
||||
const colorMap = {
|
||||
1: 'orangered',
|
||||
2: 'orange',
|
||||
3: 'red',
|
||||
4: 'green',
|
||||
};
|
||||
return colorMap[status];
|
||||
};
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(label.arco-form-item-label) {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,624 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
:width="420"
|
||||
:visible="visible"
|
||||
title="筛选"
|
||||
unmount-on-close
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form :model="filterForm" layout="vertical">
|
||||
<!-- 项目(仅显示) -->
|
||||
<a-form-item label="项目">
|
||||
<span class="w-full bg-[#f2f3f5] h-[30px] px-[5px] leading-[30px]">{{
|
||||
projectName
|
||||
}}</span>
|
||||
<!-- <a-input :value="projectName" disabled /> -->
|
||||
</a-form-item>
|
||||
|
||||
<!-- 楼栋(多选) -->
|
||||
<a-form-item label="楼栋">
|
||||
<a-select
|
||||
v-model="filterForm.buildingIds"
|
||||
multiple
|
||||
placeholder="请选择楼栋"
|
||||
:max-tag-count="3"
|
||||
>
|
||||
<a-option :value="ALL_BUILDING_ID">所有楼栋</a-option>
|
||||
<a-option
|
||||
v-for="item in buildingList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 楼层(多选) -->
|
||||
<a-form-item label="楼层">
|
||||
<a-select
|
||||
v-model="filterForm.floorIds"
|
||||
multiple
|
||||
placeholder="请选择楼层"
|
||||
:max-tag-count="3"
|
||||
>
|
||||
<a-option :value="ALL_FLOOR_ID">所有楼层</a-option>
|
||||
<a-option v-for="item in floorList" :key="item.id" :value="item.id">
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 房源状态(多选) -->
|
||||
<a-form-item label="房源状态">
|
||||
<a-select
|
||||
v-model="filterForm.statusIds"
|
||||
multiple
|
||||
placeholder="请选择房源状态"
|
||||
:max-tag-count="3"
|
||||
>
|
||||
<a-option
|
||||
v-for="item in HOUSE_STATUS"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.text }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 计租面积(滑块) -->
|
||||
<a-form-item label="计租面积(㎡)">
|
||||
<a-slider
|
||||
v-model="filterForm.rentArea"
|
||||
range
|
||||
:min="rentAreaMin"
|
||||
:max="rentAreaMax"
|
||||
:marks="rentAreaMarks"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 标签(多选) -->
|
||||
<a-form-item label="标签">
|
||||
<a-select
|
||||
v-model="filterForm.tagIds"
|
||||
multiple
|
||||
placeholder="请选择标签"
|
||||
:max-tag-count="3"
|
||||
>
|
||||
<a-option v-for="item in tagList" :key="item.id" :value="item.id">
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, computed, onMounted, nextTick } from 'vue';
|
||||
import { HOUSE_STATUS } from '@/constants';
|
||||
import { getRoomTagList } from '@/api/dictionary';
|
||||
|
||||
// 特殊值用于表示"所有"选项(使用负数避免与真实ID冲突)
|
||||
const ALL_BUILDING_ID = -1;
|
||||
const ALL_FLOOR_ID = -2;
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
roomList?: any[]; // 房源列表数据,用于提取筛选选项
|
||||
projectName?: string; // 项目名称(仅显示)
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
(
|
||||
e: 'confirm',
|
||||
payload: {
|
||||
buildingIds: number[] | null;
|
||||
floorIds: number[] | null;
|
||||
statusIds: number[] | null;
|
||||
rentArea: number[];
|
||||
tagIds: number[] | null;
|
||||
filteredList: any[]; // 筛选后的列表数据
|
||||
}
|
||||
): void;
|
||||
}>();
|
||||
|
||||
// 保存的筛选状态
|
||||
const savedFilterState = ref<{
|
||||
buildingIds: number[];
|
||||
floorIds: number[];
|
||||
statusIds: number[];
|
||||
rentArea: number[];
|
||||
tagIds: number[];
|
||||
} | null>(null);
|
||||
|
||||
// 筛选表单数据
|
||||
const filterForm = ref({
|
||||
buildingIds: [] as number[],
|
||||
floorIds: [] as number[],
|
||||
statusIds: [] as number[],
|
||||
rentArea: [0, 0] as number[],
|
||||
tagIds: [] as number[],
|
||||
});
|
||||
|
||||
// 楼栋列表(从房源列表中提取唯一值)
|
||||
const buildingList = computed(() => {
|
||||
if (!props.roomList || props.roomList.length === 0) return [];
|
||||
const buildingMap = new Map();
|
||||
props.roomList.forEach((room) => {
|
||||
if (room.buildingId && room.buildingName) {
|
||||
if (!buildingMap.has(room.buildingId)) {
|
||||
buildingMap.set(room.buildingId, {
|
||||
id: room.buildingId,
|
||||
name: room.buildingName,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return Array.from(buildingMap.values());
|
||||
});
|
||||
|
||||
// 楼层列表(从房源列表中提取唯一值)
|
||||
const floorList = computed(() => {
|
||||
if (!props.roomList || props.roomList.length === 0) return [];
|
||||
const floorMap = new Map();
|
||||
props.roomList.forEach((room) => {
|
||||
if (room.floorId && room.floorName) {
|
||||
if (!floorMap.has(room.floorId)) {
|
||||
floorMap.set(room.floorId, {
|
||||
id: room.floorId,
|
||||
name: room.floorName,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return Array.from(floorMap.values());
|
||||
});
|
||||
|
||||
// 计租面积范围
|
||||
const rentAreaMin = computed(() => {
|
||||
if (!props.roomList || props.roomList.length === 0) return 0;
|
||||
const areas = props.roomList
|
||||
.map((room) => Number(room.rentArea))
|
||||
.filter((area) => !isNaN(area) && area > 0);
|
||||
return areas.length > 0 ? Math.min(...areas) : 0;
|
||||
});
|
||||
|
||||
const rentAreaMax = computed(() => {
|
||||
if (!props.roomList || props.roomList.length === 0) return 100;
|
||||
const areas = props.roomList
|
||||
.map((room) => Number(room.rentArea))
|
||||
.filter((area) => !isNaN(area) && area > 0);
|
||||
return areas.length > 0 ? Math.max(...areas) : 100;
|
||||
});
|
||||
|
||||
const rentAreaMarks = computed(() => {
|
||||
return {
|
||||
[rentAreaMin.value]: `${rentAreaMin.value}㎡`,
|
||||
[rentAreaMax.value]: `${rentAreaMax.value}㎡`,
|
||||
};
|
||||
});
|
||||
|
||||
// 标签列表
|
||||
const tagList = ref<any[]>([]);
|
||||
|
||||
// 获取标签列表
|
||||
const getTagListData = async () => {
|
||||
try {
|
||||
const { code, data } = await getRoomTagList();
|
||||
if (code === 200 && data) {
|
||||
tagList.value = data?.roomTagsList || [];
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化筛选表单
|
||||
const initFilterForm = () => {
|
||||
const min = rentAreaMin.value || 0;
|
||||
const max = rentAreaMax.value || 100;
|
||||
|
||||
// 如果有保存的状态,恢复它(但要过滤掉不存在的ID)
|
||||
if (savedFilterState.value) {
|
||||
const allBuildingIds = buildingList.value.map((b) => b.id);
|
||||
const allFloorIds = floorList.value.map((f) => f.id);
|
||||
|
||||
// 如果保存的状态中包含"所有"选项,直接使用
|
||||
let buildingIds = savedFilterState.value.buildingIds;
|
||||
let floorIds = savedFilterState.value.floorIds;
|
||||
|
||||
// 如果保存的状态中不包含"所有"选项,检查是否所有ID都被选中
|
||||
if (!buildingIds.includes(ALL_BUILDING_ID)) {
|
||||
const validBuildingIds = buildingIds.filter((id) =>
|
||||
allBuildingIds.includes(id)
|
||||
);
|
||||
// 如果选中的数量等于总数,则使用"所有"选项
|
||||
if (
|
||||
validBuildingIds.length === allBuildingIds.length &&
|
||||
allBuildingIds.length > 0
|
||||
) {
|
||||
buildingIds = [ALL_BUILDING_ID];
|
||||
} else if (validBuildingIds.length > 0) {
|
||||
buildingIds = validBuildingIds;
|
||||
} else {
|
||||
buildingIds = [ALL_BUILDING_ID];
|
||||
}
|
||||
}
|
||||
|
||||
if (!floorIds.includes(ALL_FLOOR_ID)) {
|
||||
const validFloorIds = floorIds.filter((id) => allFloorIds.includes(id));
|
||||
// 如果选中的数量等于总数,则使用"所有"选项
|
||||
if (
|
||||
validFloorIds.length === allFloorIds.length &&
|
||||
allFloorIds.length > 0
|
||||
) {
|
||||
floorIds = [ALL_FLOOR_ID];
|
||||
} else if (validFloorIds.length > 0) {
|
||||
floorIds = validFloorIds;
|
||||
} else {
|
||||
floorIds = [ALL_FLOOR_ID];
|
||||
}
|
||||
}
|
||||
|
||||
filterForm.value = {
|
||||
buildingIds,
|
||||
floorIds,
|
||||
statusIds: [...savedFilterState.value.statusIds],
|
||||
rentArea: [...savedFilterState.value.rentArea],
|
||||
tagIds: [...savedFilterState.value.tagIds],
|
||||
};
|
||||
} else {
|
||||
// 否则初始化,楼栋和楼层默认选中"所有"
|
||||
filterForm.value = {
|
||||
buildingIds: [ALL_BUILDING_ID],
|
||||
floorIds: [ALL_FLOOR_ID],
|
||||
statusIds: [],
|
||||
rentArea: [min, max],
|
||||
tagIds: [],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
// 筛选列表数据
|
||||
const filterRoomList = () => {
|
||||
if (!props.roomList || props.roomList.length === 0) return [];
|
||||
|
||||
let filtered = [...props.roomList];
|
||||
|
||||
// 筛选楼栋(如果选择了"所有楼栋",则不过滤)
|
||||
if (
|
||||
filterForm.value.buildingIds.length > 0 &&
|
||||
!filterForm.value.buildingIds.includes(ALL_BUILDING_ID)
|
||||
) {
|
||||
filtered = filtered.filter((room) =>
|
||||
filterForm.value.buildingIds.includes(room.buildingId)
|
||||
);
|
||||
}
|
||||
|
||||
// 筛选楼层(如果选择了"所有楼层",则不过滤)
|
||||
if (
|
||||
filterForm.value.floorIds.length > 0 &&
|
||||
!filterForm.value.floorIds.includes(ALL_FLOOR_ID)
|
||||
) {
|
||||
filtered = filtered.filter((room) =>
|
||||
filterForm.value.floorIds.includes(room.floorId)
|
||||
);
|
||||
}
|
||||
|
||||
// 筛选状态
|
||||
if (filterForm.value.statusIds.length > 0) {
|
||||
filtered = filtered.filter((room) =>
|
||||
filterForm.value.statusIds.includes(room.status)
|
||||
);
|
||||
}
|
||||
|
||||
// 筛选计租面积
|
||||
const [minArea, maxArea] = filterForm.value.rentArea;
|
||||
filtered = filtered.filter((room) => {
|
||||
const area = Number(room.rentArea);
|
||||
return !isNaN(area) && area >= minArea && area <= maxArea;
|
||||
});
|
||||
|
||||
// 筛选标签
|
||||
if (filterForm.value.tagIds.length > 0) {
|
||||
filtered = filtered.filter((room) => {
|
||||
if (!room.tags || !Array.isArray(room.tags)) return false;
|
||||
const roomTagIds = room.tags.map((tag: any) => tag.id || tag);
|
||||
return filterForm.value.tagIds.some((id) => roomTagIds.includes(id));
|
||||
});
|
||||
}
|
||||
|
||||
return filtered;
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
// 处理"所有"选项:如果同时选择了"所有"和具体选项,只保留"所有"
|
||||
let finalBuildingIds = [...filterForm.value.buildingIds];
|
||||
let finalFloorIds = [...filterForm.value.floorIds];
|
||||
|
||||
if (
|
||||
finalBuildingIds.includes(ALL_BUILDING_ID) &&
|
||||
finalBuildingIds.length > 1
|
||||
) {
|
||||
finalBuildingIds = [ALL_BUILDING_ID];
|
||||
}
|
||||
if (finalFloorIds.includes(ALL_FLOOR_ID) && finalFloorIds.length > 1) {
|
||||
finalFloorIds = [ALL_FLOOR_ID];
|
||||
}
|
||||
|
||||
// 保存当前筛选状态(保存处理后的值)
|
||||
savedFilterState.value = {
|
||||
buildingIds: finalBuildingIds,
|
||||
floorIds: finalFloorIds,
|
||||
statusIds: [...filterForm.value.statusIds],
|
||||
rentArea: [...filterForm.value.rentArea],
|
||||
tagIds: [...filterForm.value.tagIds],
|
||||
};
|
||||
|
||||
// 标准化ID数组:如果选择了"所有"选项,则返回null(表示全部)
|
||||
const normalizeIds = (ids: number[], allOptionId: number) => {
|
||||
if (ids.length === 0) return null;
|
||||
// 如果选择了"所有"选项,则返回null
|
||||
if (ids.includes(allOptionId)) {
|
||||
return null;
|
||||
}
|
||||
return ids;
|
||||
};
|
||||
|
||||
// 使用处理后的值进行筛选
|
||||
const originalBuildingIds = filterForm.value.buildingIds;
|
||||
const originalFloorIds = filterForm.value.floorIds;
|
||||
filterForm.value.buildingIds = finalBuildingIds;
|
||||
filterForm.value.floorIds = finalFloorIds;
|
||||
const filteredList = filterRoomList();
|
||||
// 恢复原始值(用于显示)
|
||||
filterForm.value.buildingIds = originalBuildingIds;
|
||||
filterForm.value.floorIds = originalFloorIds;
|
||||
|
||||
emit('confirm', {
|
||||
buildingIds: normalizeIds(finalBuildingIds, ALL_BUILDING_ID),
|
||||
floorIds: normalizeIds(finalFloorIds, ALL_FLOOR_ID),
|
||||
statusIds:
|
||||
filterForm.value.statusIds.length === 0
|
||||
? null
|
||||
: filterForm.value.statusIds,
|
||||
rentArea: filterForm.value.rentArea,
|
||||
tagIds:
|
||||
filterForm.value.tagIds.length === 0 ? null : filterForm.value.tagIds,
|
||||
filteredList, // 筛选后的列表数据
|
||||
});
|
||||
handleCancel();
|
||||
};
|
||||
|
||||
// 用于防止 watch 循环更新的标志
|
||||
const isUpdatingBuilding = ref(false);
|
||||
const isUpdatingFloor = ref(false);
|
||||
|
||||
// 监听楼栋选择变化
|
||||
watch(
|
||||
() => filterForm.value.buildingIds,
|
||||
(newVal, oldVal) => {
|
||||
if (isUpdatingBuilding.value) return;
|
||||
if (!oldVal || oldVal.length === 0) return; // 初始化时跳过
|
||||
|
||||
const isAllSelected = newVal.includes(ALL_BUILDING_ID);
|
||||
const wasAllOnly = oldVal.length === 1 && oldVal[0] === ALL_BUILDING_ID;
|
||||
const hasSpecificSelected = newVal.some((id) => id !== ALL_BUILDING_ID);
|
||||
const specificIds = newVal.filter((id) => id !== ALL_BUILDING_ID);
|
||||
const allBuildingIds = buildingList.value.map((b) => b.id);
|
||||
|
||||
// 情况1:用户选择了"所有"选项(从非"所有"状态切换到包含"所有")
|
||||
// 判断:旧值不包含"所有"或包含多个值,新值包含"所有"且有其他选项
|
||||
if (isAllSelected && hasSpecificSelected && !wasAllOnly) {
|
||||
isUpdatingBuilding.value = true;
|
||||
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingBuilding.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况2:用户从"所有"切换到选择具体选项(新值不包含"所有",但旧值只有"所有")
|
||||
if (!isAllSelected && wasAllOnly && hasSpecificSelected) {
|
||||
// v-model已经处理了,不需要额外操作,允许用户选择
|
||||
// 检查是否选中的数量等于总数
|
||||
if (
|
||||
specificIds.length === allBuildingIds.length &&
|
||||
allBuildingIds.every((id) => specificIds.includes(id))
|
||||
) {
|
||||
// 选中的数量等于总数,自动切换为"所有"
|
||||
isUpdatingBuilding.value = true;
|
||||
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingBuilding.value = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况3:用户选择了具体选项,但当前值还包含"所有"(可能是从"所有"状态添加了具体选项)
|
||||
if (hasSpecificSelected && isAllSelected && wasAllOnly) {
|
||||
// 移除"所有"选项,只保留具体选项
|
||||
isUpdatingBuilding.value = true;
|
||||
filterForm.value.buildingIds = specificIds;
|
||||
nextTick(() => {
|
||||
isUpdatingBuilding.value = false;
|
||||
// 检查是否选中的数量等于总数
|
||||
if (
|
||||
specificIds.length === allBuildingIds.length &&
|
||||
allBuildingIds.every((id) => specificIds.includes(id))
|
||||
) {
|
||||
isUpdatingBuilding.value = true;
|
||||
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingBuilding.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况4:用户一个个勾选具体选项,当选中数量等于总数时,自动切换为"所有"
|
||||
if (
|
||||
!isAllSelected &&
|
||||
hasSpecificSelected &&
|
||||
specificIds.length === allBuildingIds.length
|
||||
) {
|
||||
// 检查是否所有选项都被选中
|
||||
if (allBuildingIds.every((id) => specificIds.includes(id))) {
|
||||
isUpdatingBuilding.value = true;
|
||||
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingBuilding.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听楼层选择变化
|
||||
watch(
|
||||
() => filterForm.value.floorIds,
|
||||
(newVal, oldVal) => {
|
||||
if (isUpdatingFloor.value) return;
|
||||
if (!oldVal || oldVal.length === 0) return; // 初始化时跳过
|
||||
|
||||
const isAllSelected = newVal.includes(ALL_FLOOR_ID);
|
||||
const wasAllOnly = oldVal.length === 1 && oldVal[0] === ALL_FLOOR_ID;
|
||||
const hasSpecificSelected = newVal.some((id) => id !== ALL_FLOOR_ID);
|
||||
const specificIds = newVal.filter((id) => id !== ALL_FLOOR_ID);
|
||||
const allFloorIds = floorList.value.map((f) => f.id);
|
||||
|
||||
// 情况1:用户选择了"所有"选项(从非"所有"状态切换到包含"所有")
|
||||
// 判断:旧值不包含"所有"或包含多个值,新值包含"所有"且有其他选项
|
||||
if (isAllSelected && hasSpecificSelected && !wasAllOnly) {
|
||||
isUpdatingFloor.value = true;
|
||||
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingFloor.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况2:用户从"所有"切换到选择具体选项(新值不包含"所有",但旧值只有"所有")
|
||||
if (!isAllSelected && wasAllOnly && hasSpecificSelected) {
|
||||
// v-model已经处理了,不需要额外操作,允许用户选择
|
||||
// 检查是否选中的数量等于总数
|
||||
if (
|
||||
specificIds.length === allFloorIds.length &&
|
||||
allFloorIds.every((id) => specificIds.includes(id))
|
||||
) {
|
||||
// 选中的数量等于总数,自动切换为"所有"
|
||||
isUpdatingFloor.value = true;
|
||||
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingFloor.value = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况3:用户选择了具体选项,但当前值还包含"所有"(可能是从"所有"状态添加了具体选项)
|
||||
if (hasSpecificSelected && isAllSelected && wasAllOnly) {
|
||||
// 移除"所有"选项,只保留具体选项
|
||||
isUpdatingFloor.value = true;
|
||||
filterForm.value.floorIds = specificIds;
|
||||
nextTick(() => {
|
||||
isUpdatingFloor.value = false;
|
||||
// 检查是否选中的数量等于总数
|
||||
if (
|
||||
specificIds.length === allFloorIds.length &&
|
||||
allFloorIds.every((id) => specificIds.includes(id))
|
||||
) {
|
||||
isUpdatingFloor.value = true;
|
||||
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingFloor.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况4:用户一个个勾选具体选项,当选中数量等于总数时,自动切换为"所有"
|
||||
if (
|
||||
!isAllSelected &&
|
||||
hasSpecificSelected &&
|
||||
specificIds.length === allFloorIds.length
|
||||
) {
|
||||
// 检查是否所有选项都被选中
|
||||
if (allFloorIds.every((id) => specificIds.includes(id))) {
|
||||
isUpdatingFloor.value = true;
|
||||
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingFloor.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听抽屉打开,初始化数据
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
// 延迟初始化,确保 buildingList 和 floorList 已计算
|
||||
setTimeout(() => {
|
||||
initFilterForm();
|
||||
}, 0);
|
||||
if (tagList.value.length === 0) {
|
||||
getTagListData();
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 监听楼栋和楼层列表变化,如果当前没有保存状态且列表为空,则默认选中"所有"
|
||||
watch(
|
||||
[buildingList, floorList],
|
||||
() => {
|
||||
if (props.visible && !savedFilterState.value) {
|
||||
// 如果楼栋列表为空,默认选中"所有楼栋"
|
||||
if (filterForm.value.buildingIds.length === 0) {
|
||||
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||
}
|
||||
// 如果楼层列表为空,默认选中"所有楼层"
|
||||
if (filterForm.value.floorIds.length === 0) {
|
||||
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
getTagListData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(label.arco-form-item-label) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.slider-value {
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
color: var(--color-text-2);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.arco-slider-mark) {
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<div
|
||||
class="cross-year-timeline-container"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
width: `${yearRange.length * 200}px`,
|
||||
height: '40px',
|
||||
zIndex: 2,
|
||||
}"
|
||||
>
|
||||
<!-- 年份分隔线 -->
|
||||
<template v-for="(year, index) in yearRange" :key="`divider-${year}`">
|
||||
<div
|
||||
v-if="index < yearRange.length - 1"
|
||||
class="year-divider"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: `${(index + 1) * 200}px`,
|
||||
top: '0',
|
||||
bottom: '0',
|
||||
width: '1px',
|
||||
backgroundColor: '#e5e6eb',
|
||||
zIndex: 1,
|
||||
}"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<!-- 连续时间线(跨列显示) -->
|
||||
<div
|
||||
class="timeline-bar"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
right: '0',
|
||||
top: '0',
|
||||
bottom: '0',
|
||||
}"
|
||||
>
|
||||
<!-- 空置状态 -->
|
||||
<div
|
||||
v-for="(segment, index) in allSegments"
|
||||
:key="`segment-${index}`"
|
||||
class="timeline-segment"
|
||||
:class="segment.type"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: `${segment.startPercentage}%`,
|
||||
width: `${segment.percentage}%`,
|
||||
height: '100%',
|
||||
backgroundColor:
|
||||
segment.type === 'vacant'
|
||||
? '#f2f3f5'
|
||||
: segment.type === 'lease'
|
||||
? '#00b42a'
|
||||
: 'transparent',
|
||||
backgroundImage:
|
||||
segment.type === 'unarranged'
|
||||
? 'repeating-linear-gradient(45deg, #f2f3f5, #f2f3f5 4px, #d9d9d9 4px, #d9d9d9 8px)'
|
||||
: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 2,
|
||||
}"
|
||||
>
|
||||
<span
|
||||
v-if="segment.label && segment.percentage > 5"
|
||||
class="segment-label"
|
||||
:style="{
|
||||
fontSize: '12px',
|
||||
color: segment.type === 'lease' ? '#fff' : '#86909c',
|
||||
fontWeight: segment.type === 'lease' ? '500' : 'normal',
|
||||
padding: '0 4px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}"
|
||||
>
|
||||
{{ segment.label }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface YearData {
|
||||
year: string;
|
||||
allDays: number;
|
||||
allData: Array<{
|
||||
companyName: string;
|
||||
days: number;
|
||||
type: number; // 1: 在租, 其他可能是空置
|
||||
}>;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
yearsData?: YearData[];
|
||||
yearRange: number[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
yearsData: () => [],
|
||||
yearRange: () => [],
|
||||
});
|
||||
|
||||
// 计算总天数(5年)
|
||||
const totalDays = computed(() => {
|
||||
// 如果数据为空,使用默认的5年天数
|
||||
if (!props.yearsData || props.yearsData.length === 0) {
|
||||
return 365 * props.yearRange.length;
|
||||
}
|
||||
// 确保所有年份都有数据,缺失的年份使用365天
|
||||
let total = 0;
|
||||
props.yearRange.forEach((year) => {
|
||||
const yearData = props.yearsData?.find((d) => d.year === String(year));
|
||||
total += yearData?.allDays || 365;
|
||||
});
|
||||
return total;
|
||||
});
|
||||
|
||||
// 合并所有年份的数据,创建连续的时间段
|
||||
interface TimelineSegment {
|
||||
type: 'vacant' | 'lease' | 'unarranged';
|
||||
percentage: number;
|
||||
startPercentage: number; // 在整个时间线中的起始位置百分比
|
||||
days: number;
|
||||
label?: string;
|
||||
startDay: number; // 在整个5年时间线中的起始天数
|
||||
endDay: number; // 在整个5年时间线中的结束天数
|
||||
}
|
||||
|
||||
const allSegments = computed(() => {
|
||||
if (!props.yearsData || props.yearsData.length === 0) {
|
||||
// 如果没有数据,全部显示为未安排
|
||||
return [
|
||||
{
|
||||
type: 'unarranged' as const,
|
||||
percentage: 100,
|
||||
startPercentage: 0,
|
||||
days: totalDays.value,
|
||||
startDay: 0,
|
||||
endDay: totalDays.value,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const segments: TimelineSegment[] = [];
|
||||
let globalOffset = 0; // 在整个5年时间线中的累计天数
|
||||
|
||||
// 按年份顺序处理数据,确保年份顺序正确
|
||||
const sortedYearsData = [...props.yearsData].sort((a, b) => {
|
||||
return Number(a.year) - Number(b.year);
|
||||
});
|
||||
|
||||
// 创建年份数据映射
|
||||
const yearDataMap = new Map<string, YearData>();
|
||||
sortedYearsData.forEach((data) => {
|
||||
yearDataMap.set(data.year, data);
|
||||
});
|
||||
|
||||
// 按年份范围顺序处理
|
||||
props.yearRange.forEach((year) => {
|
||||
const yearStr = String(year);
|
||||
const yearData = yearDataMap.get(yearStr);
|
||||
const yearDays = yearData?.allDays || 365;
|
||||
const yearStartDay = globalOffset;
|
||||
const yearEndDay = globalOffset + yearDays;
|
||||
|
||||
if (!yearData || !yearData.allData || yearData.allData.length === 0) {
|
||||
// 如果该年份没有数据,全部显示为未安排
|
||||
segments.push({
|
||||
type: 'unarranged',
|
||||
percentage: (yearDays / totalDays.value) * 100,
|
||||
startPercentage: (yearStartDay / totalDays.value) * 100,
|
||||
days: yearDays,
|
||||
startDay: yearStartDay,
|
||||
endDay: yearEndDay,
|
||||
});
|
||||
globalOffset = yearEndDay;
|
||||
return;
|
||||
}
|
||||
|
||||
// 收集该年份的所有时间段,按顺序排列
|
||||
const yearSegments: Array<{
|
||||
type: 'vacant' | 'lease';
|
||||
days: number;
|
||||
label: string;
|
||||
}> = [];
|
||||
|
||||
yearData.allData.forEach((item) => {
|
||||
if (item.type === 1 && item.days > 0) {
|
||||
// 在租
|
||||
yearSegments.push({
|
||||
type: 'lease',
|
||||
days: item.days,
|
||||
label: item.companyName || '',
|
||||
});
|
||||
} else if (item.type !== 1 && item.days > 0) {
|
||||
// 空置
|
||||
yearSegments.push({
|
||||
type: 'vacant',
|
||||
days: item.days,
|
||||
label: `空置${item.days}天`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 计算已安排的天数
|
||||
const arrangedDays = yearSegments.reduce((sum, seg) => sum + seg.days, 0);
|
||||
const unarrangedDays = yearDays - arrangedDays;
|
||||
|
||||
// 添加已安排的时间段(连续显示)
|
||||
let offset = 0;
|
||||
yearSegments.forEach((seg) => {
|
||||
const startDay = yearStartDay + offset;
|
||||
const endDay = startDay + seg.days;
|
||||
segments.push({
|
||||
type: seg.type,
|
||||
percentage: (seg.days / totalDays.value) * 100,
|
||||
startPercentage: (startDay / totalDays.value) * 100,
|
||||
days: seg.days,
|
||||
label: seg.label,
|
||||
startDay,
|
||||
endDay,
|
||||
});
|
||||
offset += seg.days;
|
||||
});
|
||||
|
||||
// 添加未安排的时间段
|
||||
if (unarrangedDays > 0) {
|
||||
const startDay = yearStartDay + offset;
|
||||
segments.push({
|
||||
type: 'unarranged',
|
||||
percentage: (unarrangedDays / totalDays.value) * 100,
|
||||
startPercentage: (startDay / totalDays.value) * 100,
|
||||
days: unarrangedDays,
|
||||
startDay,
|
||||
endDay: yearEndDay,
|
||||
});
|
||||
}
|
||||
|
||||
globalOffset = yearEndDay;
|
||||
});
|
||||
|
||||
return segments;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cross-year-timeline-container {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.timeline-bar {
|
||||
border-radius: 2px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.timeline-segment {
|
||||
transition: all 0.3s;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.segment-label {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<div
|
||||
class="year-timeline-container"
|
||||
:style="{ position: 'relative', width: '100%', height: '40px' }"
|
||||
>
|
||||
<div
|
||||
class="timeline-bar"
|
||||
:style="{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}"
|
||||
>
|
||||
<!-- 空置状态 -->
|
||||
<div
|
||||
v-for="(vacant, index) in vacantSegments"
|
||||
:key="`vacant-${index}`"
|
||||
class="timeline-segment vacant-segment"
|
||||
:style="{
|
||||
width: `${vacant.percentage}%`,
|
||||
height: '100%',
|
||||
backgroundColor: '#f2f3f5',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
marginRight: '2px',
|
||||
}"
|
||||
>
|
||||
<span
|
||||
v-if="vacant.days > 0"
|
||||
class="segment-label"
|
||||
:style="{ fontSize: '12px', color: '#86909c' }"
|
||||
>
|
||||
空置{{ vacant.days }}天
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 在租状态 -->
|
||||
<div
|
||||
v-for="(lease, index) in leaseSegments"
|
||||
:key="`lease-${index}`"
|
||||
class="timeline-segment lease-segment"
|
||||
:style="{
|
||||
width: `${lease.percentage}%`,
|
||||
height: '100%',
|
||||
backgroundColor: '#00b42a',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
marginRight: '2px',
|
||||
}"
|
||||
>
|
||||
<span
|
||||
v-if="lease.companyName"
|
||||
class="segment-label"
|
||||
:style="{
|
||||
fontSize: '12px',
|
||||
color: '#fff',
|
||||
fontWeight: '500',
|
||||
padding: '0 4px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}"
|
||||
>
|
||||
{{ lease.companyName }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 未安排状态(斜线) -->
|
||||
<div
|
||||
v-if="unarrangedPercentage > 0"
|
||||
class="timeline-segment unarranged-segment"
|
||||
:style="{
|
||||
width: `${unarrangedPercentage}%`,
|
||||
height: '100%',
|
||||
backgroundImage:
|
||||
'repeating-linear-gradient(45deg, #f2f3f5, #f2f3f5 4px, #d9d9d9 4px, #d9d9d9 8px)',
|
||||
position: 'relative',
|
||||
marginRight: '2px',
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface YearData {
|
||||
year: string;
|
||||
allDays: number;
|
||||
allData: Array<{
|
||||
companyName: string;
|
||||
days: number;
|
||||
type: number; // 1: 在租, 其他可能是空置
|
||||
}>;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
yearData?: YearData;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
yearData: undefined,
|
||||
});
|
||||
|
||||
// 计算各个状态的百分比和显示信息
|
||||
// 根据需求:进度条仅显示空置、在租(有合同),未有安排的用斜线代替
|
||||
const vacantSegments = computed(() => {
|
||||
if (
|
||||
!props.yearData ||
|
||||
!props.yearData.allData ||
|
||||
props.yearData.allData.length === 0
|
||||
) {
|
||||
// 如果没有数据,不显示空置段,全部显示为未安排(斜线)
|
||||
return [];
|
||||
}
|
||||
|
||||
const segments: Array<{ percentage: number; days: number }> = [];
|
||||
const totalDays = props.yearData.allDays || 365;
|
||||
|
||||
// 计算在租天数
|
||||
const leaseDays = props.yearData.allData
|
||||
.filter((item) => item.type === 1)
|
||||
.reduce((sum, item) => sum + (item.days || 0), 0);
|
||||
|
||||
// 计算其他已安排天数(可能是空置记录,type !== 1)
|
||||
const otherArrangedDays = props.yearData.allData
|
||||
.filter((item) => item.type !== 1)
|
||||
.reduce((sum, item) => sum + (item.days || 0), 0);
|
||||
|
||||
// 总已安排天数
|
||||
const totalArrangedDays = leaseDays + otherArrangedDays;
|
||||
|
||||
// 如果总已安排天数小于总天数,剩余部分可能是空置(如果是在租期间的空置)
|
||||
// 但根据需求,只有明确标记为空置的才显示,否则显示为未安排
|
||||
if (otherArrangedDays > 0) {
|
||||
// 如果有其他类型的数据(非在租),可能是空置记录
|
||||
segments.push({
|
||||
percentage: (otherArrangedDays / totalDays) * 100,
|
||||
days: otherArrangedDays,
|
||||
});
|
||||
} else if (totalArrangedDays < totalDays && leaseDays > 0) {
|
||||
// 如果有在租数据,但总天数大于已安排天数,剩余部分可能是空置
|
||||
// 这里需要根据实际业务逻辑判断,暂时不显示为空置,显示为未安排
|
||||
}
|
||||
|
||||
return segments;
|
||||
});
|
||||
|
||||
const leaseSegments = computed(() => {
|
||||
if (!props.yearData || !props.yearData.allData) return [];
|
||||
|
||||
const totalDays = props.yearData.allDays || 365;
|
||||
const segments: Array<{
|
||||
percentage: number;
|
||||
companyName: string;
|
||||
days: number;
|
||||
}> = [];
|
||||
|
||||
// type === 1 表示在租(有合同)
|
||||
props.yearData.allData?.forEach((item) => {
|
||||
if (item.type === 1 && item.days > 0) {
|
||||
segments.push({
|
||||
percentage: (item.days / totalDays) * 100,
|
||||
companyName: item.companyName || '',
|
||||
days: item.days,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return segments;
|
||||
});
|
||||
|
||||
const unarrangedPercentage = computed(() => {
|
||||
if (!props.yearData) return 100; // 如果没有数据,全部显示为未安排
|
||||
|
||||
const totalDays = props.yearData.allDays || 365;
|
||||
|
||||
// 只计算已安排的天数(空置 + 在租)
|
||||
let arrangedDays = 0;
|
||||
|
||||
if (props.yearData.allData && props.yearData.allData.length > 0) {
|
||||
props.yearData.allData.forEach((item) => {
|
||||
arrangedDays += item.days || 0;
|
||||
});
|
||||
}
|
||||
|
||||
const unarrangedDays = totalDays - arrangedDays;
|
||||
return unarrangedDays > 0 ? (unarrangedDays / totalDays) * 100 : 0;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.year-timeline-container {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.timeline-bar {
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timeline-segment {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.segment-label {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,413 @@
|
||||
<template>
|
||||
<div class="business_project_wrap">
|
||||
<Breadcrumb :items="['menu.business', 'menu.business.house-atlas']" />
|
||||
<a-card class="general-card full_height">
|
||||
<template #title>
|
||||
<div class="card-title_between">
|
||||
<p class="title">{{ t('menu.business.house-atlas') }}</p>
|
||||
<div class="flex_center_between">
|
||||
<a-space>
|
||||
<a-input-search
|
||||
v-model="queryParams.keyword"
|
||||
:style="{ width: '320px' }"
|
||||
:placeholder="t('please.enter', { placeholder: t('content') })"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<a-button @click="handleFilter">
|
||||
<template #icon>
|
||||
<icon-filter />
|
||||
</template>
|
||||
{{ t('table.filter') }}
|
||||
</a-button>
|
||||
<a-button>
|
||||
<template #icon>
|
||||
<icon-export />
|
||||
</template>
|
||||
{{ t('table.export') }}
|
||||
</a-button>
|
||||
<a-button>
|
||||
<template #icon>
|
||||
<icon-download />
|
||||
</template>
|
||||
{{ t('table.import') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 使用新的 DataTable 组件 -->
|
||||
<div class="table-container" :style="{ position: 'relative' }">
|
||||
<DataTable
|
||||
:data="renderData"
|
||||
:columns="projectColumn"
|
||||
: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
|
||||
v-for="(year, index) in yearRange"
|
||||
:key="year"
|
||||
#[`year-${year}`]="{ record }"
|
||||
>
|
||||
<div class="year-cell" :class="{ 'first-year': index === 0 }">
|
||||
<!-- 时间线只在第一个列中渲染,使用绝对定位跨越所有列 -->
|
||||
<div
|
||||
v-if="index === 0"
|
||||
class="timeline-row-wrapper"
|
||||
:style="{ width: `${timelineWidth}px` }"
|
||||
>
|
||||
<CrossYearTimeline
|
||||
:years-data="getAllYearsData(record)"
|
||||
:year-range="yearRange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #operate="{ record }">
|
||||
<a-space>
|
||||
<a-button @click="handleDetail(record.id)">查看</a-button>
|
||||
<a-popover trigger="click" position="bottom">
|
||||
<a-button>{{ $t('more') }}</a-button>
|
||||
<template #content>
|
||||
<a-space direction="vertical">
|
||||
<a-button @click="handleEditProject(record.id)">
|
||||
编辑房源
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
:ok-text="$t('cancel')"
|
||||
:cancel-text="$t('delete')"
|
||||
:ok-button-props="{ type: 'outline' }"
|
||||
:cancel-button-props="{
|
||||
type: 'primary',
|
||||
status: 'danger',
|
||||
}"
|
||||
content="确定删除该房源?"
|
||||
type="error"
|
||||
@cancel="handleDel(record?.id)"
|
||||
>
|
||||
<a-button status="danger">删除房源</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-popover>
|
||||
</a-space>
|
||||
</template>
|
||||
</DataTable>
|
||||
</div>
|
||||
</a-card>
|
||||
<houseFormDrawer
|
||||
v-model:visible="showDrawer"
|
||||
:is-edit="isEdit"
|
||||
:record="currentRecordInfo"
|
||||
@submitted="getHouseListData"
|
||||
/>
|
||||
<houseInfoDrawer
|
||||
v-model:visible="showProjectInfoDrawer"
|
||||
:project-id="currentProjectId"
|
||||
:detail-info="currentRecordInfo"
|
||||
/>
|
||||
<houseListFilterDrawer
|
||||
v-model:visible="showListFilterDrawer"
|
||||
:room-list="projectList"
|
||||
:project-name="currentProjectName"
|
||||
@confirm="handleFilterConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, computed } 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 { getRoomsGraphList, getRoom, delRoom } from '@/api/room';
|
||||
import { ProjectPaginationParams } from '@/types/comm';
|
||||
import houseFormDrawer from '../drawer/house-form-drawer.vue';
|
||||
import houseInfoDrawer from '../drawer/house-info-drawer.vue';
|
||||
import houseListFilterDrawer from '../drawer/house-list-filter-drawer.vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import CrossYearTimeline from './components/cross-year-timeline.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { loading } = useLoading(false);
|
||||
const showDrawer = ref<boolean>(false);
|
||||
const showProjectInfoDrawer = ref<boolean>(false);
|
||||
const showListFilterDrawer = ref<boolean>(false);
|
||||
const currentProjectId = ref<number | null>(null);
|
||||
const isLoadingEdit = ref<boolean>(false);
|
||||
const isLoadingDetail = ref<boolean>(false);
|
||||
const isEdit = ref<boolean>(false);
|
||||
const currentRecordInfo = ref<any | null>(null);
|
||||
const projectList = ref<any[]>([]);
|
||||
const currentProjectName = ref<string>(''); // 当前项目名称
|
||||
const {
|
||||
data: renderData,
|
||||
columns: projectColumn,
|
||||
total,
|
||||
current,
|
||||
pageSize,
|
||||
setData,
|
||||
setColumns,
|
||||
setTotal,
|
||||
setCurrent,
|
||||
setPageSize,
|
||||
setLoading,
|
||||
} = useDataTable({
|
||||
initialPage: 1,
|
||||
initialPageSize: 10,
|
||||
pageSizeOptions: [10, 20, 50, 100],
|
||||
});
|
||||
|
||||
// 计算5年年份范围(当前年的前1年到后3年)
|
||||
const currentYear = new Date().getFullYear();
|
||||
const yearRange = computed(() => {
|
||||
const years: number[] = [];
|
||||
for (let i = -1; i <= 3; i++) {
|
||||
years.push(currentYear + i);
|
||||
}
|
||||
return years;
|
||||
});
|
||||
|
||||
// 计算时间线总宽度
|
||||
const timelineWidth = computed(() => {
|
||||
return yearRange.value.length * 200; // 每个年份列200px
|
||||
});
|
||||
|
||||
// 通用空值处理函数
|
||||
const renderEmpty = (value: any) => value ?? '-';
|
||||
|
||||
// 获取所有年份的数据
|
||||
const getAllYearsData = (record: any) => {
|
||||
if (!record?.yearsData || !Array.isArray(record.yearsData)) {
|
||||
return [];
|
||||
}
|
||||
// 只返回在年份范围内的数据
|
||||
return record.yearsData.filter((item: any) => {
|
||||
const year = Number(item.year);
|
||||
return yearRange.value.includes(year);
|
||||
});
|
||||
};
|
||||
|
||||
// 动态生成列配置
|
||||
const generateColumns = (): TableColumnData[] => {
|
||||
const baseColumns: TableColumnData[] = [
|
||||
{
|
||||
title: '房源名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 150,
|
||||
render: ({ record }) => renderEmpty(record.name),
|
||||
},
|
||||
{
|
||||
title: '所属',
|
||||
dataIndex: 'projectName',
|
||||
key: 'projectName',
|
||||
width: 200,
|
||||
render: ({ record }) => {
|
||||
const buildingName = record.buildingName
|
||||
? ` ${record.buildingName}座`
|
||||
: '';
|
||||
const floorName = record.floorName ? `・${record.floorName}层` : '';
|
||||
return renderEmpty(record.projectName) + buildingName + floorName;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '面积(㎡)',
|
||||
dataIndex: 'rentArea',
|
||||
key: 'rentArea',
|
||||
width: 100,
|
||||
render: ({ record }) => renderEmpty(record.rentArea),
|
||||
},
|
||||
];
|
||||
|
||||
// 添加年份列(每个年份独立一列,各自显示标题)
|
||||
const yearColumns: TableColumnData[] = yearRange.value.map((year) => ({
|
||||
title: String(year),
|
||||
key: `year-${year}`,
|
||||
slotName: `year-${year}`,
|
||||
width: 200,
|
||||
align: 'center' as const,
|
||||
}));
|
||||
|
||||
// 添加状态和操作列
|
||||
const otherColumns: TableColumnData[] = [
|
||||
{
|
||||
title: '当前状态',
|
||||
key: 'statusStr',
|
||||
slotName: 'status',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'operate',
|
||||
slotName: 'operate',
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
|
||||
return [...baseColumns, ...yearColumns, ...otherColumns];
|
||||
};
|
||||
|
||||
// 列配置
|
||||
const columns = ref<TableColumnData[]>(generateColumns());
|
||||
const queryParams = ref<ProjectPaginationParams>({
|
||||
page: current.value,
|
||||
page_size: pageSize.value,
|
||||
keyword: '',
|
||||
});
|
||||
const getHouseListData = async () => {
|
||||
setLoading(true);
|
||||
const { code, data } = await getRoomsGraphList(queryParams.value);
|
||||
if (code === 200 && data) {
|
||||
const { list, page } = data as {
|
||||
list?: any[];
|
||||
page?: { total?: number };
|
||||
};
|
||||
projectList.value = Array.isArray(list) ? list : [];
|
||||
// 提取项目名称(从第一条数据中获取,假设所有房源属于同一项目)
|
||||
if (projectList.value.length > 0 && projectList.value[0].projectName) {
|
||||
currentProjectName.value = projectList.value[0].projectName;
|
||||
}
|
||||
setData(projectList.value);
|
||||
setTotal(page?.total ? page.total : 0);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
const getRoomData = async (id: number, edit: boolean) => {
|
||||
if (currentRecordInfo.value && currentRecordInfo.value?.id === id && !edit)
|
||||
return;
|
||||
if (edit) {
|
||||
isLoadingEdit.value = true;
|
||||
} else {
|
||||
isLoadingDetail.value = true;
|
||||
}
|
||||
const { code, data } = await getRoom(id);
|
||||
if (code === 200 && data) {
|
||||
currentRecordInfo.value = (data as { room?: any }).room || null;
|
||||
}
|
||||
isLoadingEdit.value = false;
|
||||
isLoadingDetail.value = false;
|
||||
};
|
||||
const handleFilterConfirm = (payload: {
|
||||
buildingIds: number[] | null;
|
||||
floorIds: number[] | null;
|
||||
statusIds: number[] | null;
|
||||
rentArea: number[];
|
||||
tagIds: number[] | null;
|
||||
filteredList: any[]; // 筛选后的列表数据
|
||||
}) => {
|
||||
// 更新查询参数(用于后端筛选)
|
||||
(queryParams.value as any).building_ids = payload.buildingIds;
|
||||
(queryParams.value as any).floor_ids = payload.floorIds;
|
||||
(queryParams.value as any).status_ids = payload.statusIds;
|
||||
(queryParams.value as any).rent_area_min = payload.rentArea?.[0];
|
||||
(queryParams.value as any).rent_area_max = payload.rentArea?.[1];
|
||||
(queryParams.value as any).tag_ids = payload.tagIds;
|
||||
|
||||
// 使用筛选后的列表数据更新表格
|
||||
if (payload.filteredList) {
|
||||
setData(payload.filteredList);
|
||||
setTotal(payload.filteredList.length);
|
||||
} else {
|
||||
// 如果没有筛选数据,重新加载列表数据
|
||||
getHouseListData();
|
||||
}
|
||||
};
|
||||
const handleDel = async (id: number) => {
|
||||
const { code, message } = await delRoom(id);
|
||||
if (code === 200) {
|
||||
Message.success(message || '删除成功');
|
||||
// 重新加载列表数据
|
||||
await getHouseListData();
|
||||
} else {
|
||||
Message.error(message || '删除失败');
|
||||
}
|
||||
};
|
||||
// 分页事件处理
|
||||
const onPageChange = (page: number) => {
|
||||
setCurrent(page);
|
||||
current.value = page;
|
||||
};
|
||||
const onPageSizeChange = (size: number) => {
|
||||
setPageSize(size);
|
||||
pageSize.value = size;
|
||||
};
|
||||
const handleEditProject = async (id: number) => {
|
||||
if (isLoadingEdit.value) return;
|
||||
isEdit.value = true;
|
||||
await getRoomData(id, true);
|
||||
showDrawer.value = true;
|
||||
};
|
||||
const handleDetail = async (id: number) => {
|
||||
if (isLoadingDetail.value) return;
|
||||
await getRoomData(id, false);
|
||||
showProjectInfoDrawer.value = true;
|
||||
};
|
||||
const handleSearch = () => {
|
||||
getHouseListData();
|
||||
};
|
||||
const handleFilter = () => {
|
||||
showListFilterDrawer.value = true;
|
||||
};
|
||||
const getTagsColor = (status) => {
|
||||
const colorMap = {
|
||||
1: 'orangered',
|
||||
2: 'orange',
|
||||
3: 'red',
|
||||
4: 'green',
|
||||
};
|
||||
return colorMap[status];
|
||||
};
|
||||
onMounted(() => {
|
||||
setColumns(columns.value);
|
||||
getHouseListData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.table-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.year-cell {
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
overflow: visible;
|
||||
height: 56px;
|
||||
min-height: 56px;
|
||||
|
||||
&.first-year {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-row-wrapper {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 确保表格行可以包含绝对定位的元素
|
||||
:deep(.arco-table-tr) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:deep(.arco-table-td) {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<div>house-list</div>
|
||||
</template>
|
||||
@@ -0,0 +1,343 @@
|
||||
<template>
|
||||
<div class="business_project_wrap">
|
||||
<Breadcrumb :items="['menu.business', 'menu.business.house-list']" />
|
||||
<a-card class="general-card full_height">
|
||||
<template #title>
|
||||
<div class="card-title_between">
|
||||
<p class="title">{{ t('menu.business.house-list') }}</p>
|
||||
<div class="flex_center_between">
|
||||
<a-space>
|
||||
<a-input-search
|
||||
v-model="queryParams.keyword"
|
||||
:style="{ width: '320px' }"
|
||||
:placeholder="t('please.enter', { placeholder: t('content') })"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<a-button @click="handleFilter">
|
||||
<template #icon>
|
||||
<icon-filter />
|
||||
</template>
|
||||
{{ t('table.filter') }}
|
||||
</a-button>
|
||||
<a-button>
|
||||
<template #icon>
|
||||
<icon-export />
|
||||
</template>
|
||||
{{ t('table.export') }}
|
||||
</a-button>
|
||||
<a-button>
|
||||
<template #icon>
|
||||
<icon-download />
|
||||
</template>
|
||||
{{ t('table.import') }}
|
||||
</a-button>
|
||||
<a-button type="primary" @click="handleAddHouse">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
{{ t('add-house') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 使用新的 DataTable 组件 -->
|
||||
<DataTable
|
||||
:data="renderData"
|
||||
:columns="projectColumn"
|
||||
:loading="loading"
|
||||
:total="total"
|
||||
:current="current"
|
||||
:page-size="pageSize"
|
||||
@page-change="onPageChange"
|
||||
@page-size-change="onPageSizeChange"
|
||||
>
|
||||
<template #tags="{ record }">
|
||||
<template v-if="record.tags?.length">
|
||||
<a-tag
|
||||
v-for="item in record.tags"
|
||||
:key="item.id"
|
||||
class="mr-[5px] first:mr-0"
|
||||
>
|
||||
{{ item.name }}</a-tag
|
||||
>
|
||||
</template>
|
||||
<span v-else> - </span>
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="getTagsColor(record?.status)">{{
|
||||
record?.statusStr
|
||||
}}</a-tag>
|
||||
</template>
|
||||
<template #operate="{ record }">
|
||||
<a-space>
|
||||
<a-button @click="handleDetail(record.id)">查看</a-button>
|
||||
<a-popover trigger="click" position="bottom">
|
||||
<a-button>{{ $t('more') }}</a-button>
|
||||
<template #content>
|
||||
<a-space direction="vertical">
|
||||
<a-button @click="handleEditProject(record.id)">
|
||||
编辑房源
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
:ok-text="$t('cancel')"
|
||||
:cancel-text="$t('delete')"
|
||||
:ok-button-props="{ type: 'outline' }"
|
||||
:cancel-button-props="{
|
||||
type: 'primary',
|
||||
status: 'danger',
|
||||
}"
|
||||
content="确定删除该房源?"
|
||||
type="error"
|
||||
@cancel="handleDel(record?.id)"
|
||||
>
|
||||
<a-button status="danger">删除房源</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-popover>
|
||||
</a-space>
|
||||
</template>
|
||||
</DataTable>
|
||||
</a-card>
|
||||
<houseFormDrawer
|
||||
v-model:visible="showDrawer"
|
||||
:is-edit="isEdit"
|
||||
:record="currentRecordInfo"
|
||||
@submitted="getHouseListData"
|
||||
/>
|
||||
<houseInfoDrawer
|
||||
v-model:visible="showProjectInfoDrawer"
|
||||
:project-id="currentProjectId"
|
||||
:detail-info="currentRecordInfo"
|
||||
/>
|
||||
<houseListFilterDrawer
|
||||
v-model:visible="showListFilterDrawer"
|
||||
:room-list="projectList"
|
||||
:project-name="currentProjectName"
|
||||
@confirm="handleFilterConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { TableColumnData } from '@arco-design/web-vue';
|
||||
import useDataTable from '@/composables/useDataTable';
|
||||
import { getRoomList, getRoom, delRoom } from '@/api/room';
|
||||
import { ProjectPaginationParams } from '@/types/comm';
|
||||
import houseFormDrawer from '../drawer/house-form-drawer.vue';
|
||||
import houseInfoDrawer from '../drawer/house-info-drawer.vue';
|
||||
import houseListFilterDrawer from '../drawer/house-list-filter-drawer.vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { PRICE_UNIT_MAP } from '@/constants';
|
||||
const { t } = useI18n();
|
||||
const { loading } = useLoading(false);
|
||||
const showDrawer = ref<boolean>(false);
|
||||
const showProjectInfoDrawer = ref<boolean>(false);
|
||||
const showListFilterDrawer = ref<boolean>(false);
|
||||
const currentProjectId = ref<number | null>(null);
|
||||
const isLoadingEdit = ref<boolean>(false);
|
||||
const isLoadingDetail = ref<boolean>(false);
|
||||
const isEdit = ref<boolean>(false);
|
||||
const currentRecordInfo = ref<any | null>(null);
|
||||
const projectList = ref<any[]>([]);
|
||||
const currentProjectName = ref<string>(''); // 当前项目名称
|
||||
const {
|
||||
data: renderData,
|
||||
columns: projectColumn,
|
||||
total,
|
||||
current,
|
||||
pageSize,
|
||||
setData,
|
||||
setColumns,
|
||||
setTotal,
|
||||
setCurrent,
|
||||
setPageSize,
|
||||
setLoading,
|
||||
} = useDataTable({
|
||||
initialPage: 1,
|
||||
initialPageSize: 10,
|
||||
pageSizeOptions: [10, 20, 50, 100],
|
||||
});
|
||||
|
||||
// 通用空值处理函数
|
||||
const renderEmpty = (value: any) => value ?? '-';
|
||||
|
||||
// 列配置
|
||||
const columns: TableColumnData[] = [
|
||||
{
|
||||
title: '房源名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: ({ record }) => renderEmpty(record.name),
|
||||
},
|
||||
{
|
||||
title: '所属项目',
|
||||
dataIndex: 'projectName',
|
||||
key: 'projectName',
|
||||
render: ({ record }) => renderEmpty(record.projectName),
|
||||
},
|
||||
{
|
||||
title: '所属楼栋',
|
||||
dataIndex: 'buildingName',
|
||||
key: 'buildingName',
|
||||
render: ({ record }) => renderEmpty(record.buildingName),
|
||||
},
|
||||
{
|
||||
title: '楼层',
|
||||
dataIndex: 'floorName',
|
||||
key: 'floorName',
|
||||
render: ({ record }) => renderEmpty(record.floorName),
|
||||
},
|
||||
{
|
||||
title: '计租面积(㎡)',
|
||||
dataIndex: 'rentArea',
|
||||
key: 'rentArea',
|
||||
render: ({ record }) => renderEmpty(record.rentArea),
|
||||
},
|
||||
{
|
||||
title: '租金(实时)',
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
render: ({ record }) => {
|
||||
if (Number(record.price) <= 0) return '-';
|
||||
const unit = PRICE_UNIT_MAP.get(record?.priceType);
|
||||
return `${record?.price} ${unit}`;
|
||||
},
|
||||
},
|
||||
{ title: '标签', dataIndex: 'tags', key: 'tags', slotName: 'tags' },
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'statusStr',
|
||||
key: 'statusStr',
|
||||
slotName: 'status',
|
||||
},
|
||||
{
|
||||
title: '合同',
|
||||
dataIndex: 'contractStatusStr',
|
||||
key: 'contractStatusStr',
|
||||
render: ({ record }) => renderEmpty(record.contractStatusStr),
|
||||
},
|
||||
{ title: '操作', key: 'operate', slotName: 'operate', width: 200 },
|
||||
];
|
||||
const queryParams = ref<ProjectPaginationParams>({
|
||||
page: current.value,
|
||||
page_size: pageSize.value,
|
||||
keyword: '',
|
||||
});
|
||||
const getHouseListData = async () => {
|
||||
setLoading(true);
|
||||
const { code, data } = await getRoomList(queryParams.value);
|
||||
if (code === 200 && data) {
|
||||
const { list, page } = data as {
|
||||
list?: any[];
|
||||
page?: { total?: number };
|
||||
};
|
||||
projectList.value = Array.isArray(list) ? list : [];
|
||||
// 提取项目名称(从第一条数据中获取,假设所有房源属于同一项目)
|
||||
if (projectList.value.length > 0 && projectList.value[0].projectName) {
|
||||
currentProjectName.value = projectList.value[0].projectName;
|
||||
}
|
||||
setData(projectList.value);
|
||||
setTotal(page?.total ? page.total : 0);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
const getRoomData = async (id: number, edit: boolean) => {
|
||||
if (currentRecordInfo.value && currentRecordInfo.value?.id === id && !edit)
|
||||
return;
|
||||
if (edit) {
|
||||
isLoadingEdit.value = true;
|
||||
} else {
|
||||
isLoadingDetail.value = true;
|
||||
}
|
||||
const { code, data } = await getRoom(id);
|
||||
if (code === 200 && data) {
|
||||
currentRecordInfo.value = (data as { room?: any }).room || null;
|
||||
}
|
||||
isLoadingEdit.value = false;
|
||||
isLoadingDetail.value = false;
|
||||
};
|
||||
const handleFilterConfirm = (payload: {
|
||||
buildingIds: number[] | null;
|
||||
floorIds: number[] | null;
|
||||
statusIds: number[] | null;
|
||||
rentArea: number[];
|
||||
tagIds: number[] | null;
|
||||
filteredList: any[]; // 筛选后的列表数据
|
||||
}) => {
|
||||
// 更新查询参数(用于后端筛选)
|
||||
(queryParams.value as any).building_ids = payload.buildingIds;
|
||||
(queryParams.value as any).floor_ids = payload.floorIds;
|
||||
(queryParams.value as any).status_ids = payload.statusIds;
|
||||
(queryParams.value as any).rent_area_min = payload.rentArea?.[0];
|
||||
(queryParams.value as any).rent_area_max = payload.rentArea?.[1];
|
||||
(queryParams.value as any).tag_ids = payload.tagIds;
|
||||
|
||||
// 使用筛选后的列表数据更新表格
|
||||
if (payload.filteredList) {
|
||||
setData(payload.filteredList);
|
||||
setTotal(payload.filteredList.length);
|
||||
} else {
|
||||
// 如果没有筛选数据,重新加载列表数据
|
||||
getHouseListData();
|
||||
}
|
||||
};
|
||||
const handleDel = async (id: number) => {
|
||||
const { code, message } = await delRoom(id);
|
||||
if (code === 200) {
|
||||
Message.success(message || '删除成功');
|
||||
// 重新加载列表数据
|
||||
await getHouseListData();
|
||||
} else {
|
||||
Message.error(message || '删除失败');
|
||||
}
|
||||
};
|
||||
// 分页事件处理
|
||||
const onPageChange = (page: number) => {
|
||||
setCurrent(page);
|
||||
current.value = page;
|
||||
};
|
||||
const onPageSizeChange = (size: number) => {
|
||||
setPageSize(size);
|
||||
pageSize.value = size;
|
||||
};
|
||||
const handleAddHouse = () => {
|
||||
isEdit.value = false;
|
||||
currentRecordInfo.value = null;
|
||||
showDrawer.value = true;
|
||||
};
|
||||
const handleEditProject = async (id: number) => {
|
||||
if (isLoadingEdit.value) return;
|
||||
isEdit.value = true;
|
||||
await getRoomData(id, true);
|
||||
showDrawer.value = true;
|
||||
};
|
||||
const handleDetail = async (id: number) => {
|
||||
if (isLoadingDetail.value) return;
|
||||
await getRoomData(id, false);
|
||||
showProjectInfoDrawer.value = true;
|
||||
};
|
||||
const handleSearch = () => {
|
||||
getHouseListData();
|
||||
};
|
||||
const handleFilter = () => {
|
||||
showListFilterDrawer.value = true;
|
||||
};
|
||||
const getTagsColor = (status) => {
|
||||
const colorMap = {
|
||||
1: 'orangered',
|
||||
2: 'orange',
|
||||
3: 'red',
|
||||
4: 'green',
|
||||
};
|
||||
return colorMap[status];
|
||||
};
|
||||
onMounted(() => {
|
||||
setColumns(columns);
|
||||
getHouseListData();
|
||||
});
|
||||
</script>
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<div>house-view</div>
|
||||
</template>
|
||||
@@ -0,0 +1,595 @@
|
||||
<template>
|
||||
<div class="house-view-content px-[24px]">
|
||||
<!-- 统计数据区域 -->
|
||||
<div class="flex items-center justify-between bg-white pb-[5px] w-full">
|
||||
<div class="flex items-center bg-[#f2f3f5] flex-1">
|
||||
<div class="flex items-center justify-center flex-1">
|
||||
<div class="flex flex-col items-center justify-center flex-1">
|
||||
<span class="text-sm text-[#86909c]">房源</span>
|
||||
<span class="text-[28px] text-[#121111] mt-[8px]"
|
||||
>{{ props.statisticsData.roomNum || 0 }}套</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center flex-1">
|
||||
<span class="text-sm text-[#86909c]">出租率</span>
|
||||
<span class="text-[28px] text-[#121111] mt-[8px]"
|
||||
>{{ props.statisticsData.rentRateStr || '0.00' }}%</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center flex-1">
|
||||
<span class="text-sm text-[#86909c]">累计出租率</span>
|
||||
<span class="text-[28px] text-[#121111] mt-[8px]"
|
||||
>{{ props.statisticsData.totalRentRateStr || '0.00' }}%</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center flex-1">
|
||||
<span class="text-sm text-[#86909c]">计租面积</span>
|
||||
<span class="text-[28px] text-[#121111] mt-[8px]">{{
|
||||
props.statisticsData.rentAreaStr || '0.00㎡'
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-[300px] h-[150px] ml-[50px] flex-shrink-0 relative">
|
||||
<Chart :options="chartOption" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 筛选区域 -->
|
||||
<div class="filter-section mt-[24px]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- 状态筛选 -->
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
v-for="status in statusOptions"
|
||||
:key="status.id"
|
||||
class="flex items-center mr-[20px] last:mr-0 rounded cursor-pointer transition-all border border-transparent hover:bg-[#f2f3f5]"
|
||||
:class="{
|
||||
'bg-[#e8f3ff] border-[#165dff]': selectedStatus.includes(
|
||||
status.id
|
||||
),
|
||||
}"
|
||||
@click="toggleStatus(status.id)"
|
||||
>
|
||||
<span
|
||||
class="w-[20px] h-[20px] rounded-full"
|
||||
:style="{ backgroundColor: status.color }"
|
||||
></span>
|
||||
<span class="text-[14px] ml-[8px] text-[#333]">{{
|
||||
status.text
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<a-input-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入内容"
|
||||
style="width: 200px"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<a-button class="mx-[8px]" @click="showFilterDrawer = true">
|
||||
<template #icon>
|
||||
<icon-filter />
|
||||
</template>
|
||||
筛选
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button
|
||||
:type="viewMode === 'grid' ? 'primary' : 'outline'"
|
||||
@click="viewMode = 'grid'"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-apps />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button
|
||||
:type="viewMode === 'list' ? 'primary' : 'outline'"
|
||||
@click="viewMode = 'list'"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-list />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
<a-button type="primary" class="ml-[8px]" @click="handleAddHouse">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
新建房源
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 房源网格区域 -->
|
||||
<div class="room-grid-section">
|
||||
<div v-for="floor in floorList" :key="floor.floorId">
|
||||
<div class="flex items-center my-[10px]">
|
||||
<span class="text-base font-semibold text-[#1d2129]"
|
||||
>{{ floor.floorName }}·</span
|
||||
>
|
||||
<span class="text-sm text-[#86909c]">{{ floor.totalArea }}m²</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
v-for="(row, rowIndex) in floor.rows"
|
||||
:key="rowIndex"
|
||||
class="grid grid-cols-8 gap-x-[8px] mb-2"
|
||||
>
|
||||
<div
|
||||
v-for="(cell, cellIndex) in row"
|
||||
:key="cellIndex"
|
||||
class="h-[74px] border-2 rounded p-2 relative cursor-pointer transition-all hover:shadow-md p-[5px]"
|
||||
:class="getRoomCellClass(cell)"
|
||||
:style="getRoomCellStyle(cell)"
|
||||
@click="handleRoomClick(cell.room)"
|
||||
>
|
||||
<template v-if="cell.room">
|
||||
<div class="h-full flex flex-col">
|
||||
<div
|
||||
class="text-sm font-semibold text-[#1d2129] overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
{{ cell.room.name || cell.room.roomName }}
|
||||
</div>
|
||||
<div class="text-xs text-[#86909c]"
|
||||
>{{ cell.room.rentArea || cell.room.area }}m²</div
|
||||
>
|
||||
<div class="text-xs text-[#4e5969] flex-1">{{
|
||||
getRoomInfo(cell.room)
|
||||
}}</div>
|
||||
<div
|
||||
v-if="cell.room.status === 4"
|
||||
class="absolute top-1 right-1 px-1.5 py-0.5 bg-[#00b42a] text-white rounded text-[10px]"
|
||||
>
|
||||
{{ cell.room.statusStr || '在租' }}
|
||||
</div>
|
||||
<div
|
||||
v-if="cell.room.isExpired"
|
||||
class="absolute top-1 right-1 px-1.5 py-0.5 bg-[#f53f3f] text-white rounded text-[10px]"
|
||||
>
|
||||
逾
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选弹窗 -->
|
||||
<HouseViewFilterDrawer
|
||||
v-model:visible="showFilterDrawer"
|
||||
:room-list="props.roomList"
|
||||
:project-name="props.projectName"
|
||||
@confirm="handleFilterConfirm"
|
||||
/>
|
||||
|
||||
<!-- 房源详情弹窗 -->
|
||||
<HouseInfoDrawer
|
||||
v-model:visible="showRoomDetailDrawer"
|
||||
:project-id="currentProjectId"
|
||||
:detail-info="roomDetailInfo"
|
||||
/>
|
||||
|
||||
<!-- 新建/编辑房源弹窗 -->
|
||||
<HouseFormDrawer
|
||||
v-model:visible="showHouseFormDrawer"
|
||||
:is-edit="false"
|
||||
@submitted="handleHouseFormSubmitted"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import Chart from '@/components/chart/index.vue';
|
||||
import HouseViewFilterDrawer from './house-view-filter-drawer.vue';
|
||||
import HouseInfoDrawer from '@/views/business/space/drawer/house-info-drawer.vue';
|
||||
import HouseFormDrawer from '@/views/business/space/drawer/house-form-drawer.vue';
|
||||
import { getRoom } from '@/api/room';
|
||||
import { useUserStore } from '@/store';
|
||||
|
||||
interface Props {
|
||||
statisticsData: any;
|
||||
roomList: any[];
|
||||
loading?: boolean;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
statisticsData: () => ({}),
|
||||
roomList: () => [],
|
||||
loading: false,
|
||||
projectName: '',
|
||||
});
|
||||
|
||||
const userStore = useUserStore();
|
||||
const showRoomDetailDrawer = ref(false);
|
||||
const roomDetailInfo = ref<any>(null);
|
||||
const showHouseFormDrawer = ref(false);
|
||||
const currentProjectId = computed(() => userStore.currentProjectId ?? null);
|
||||
|
||||
const emit = defineEmits<{
|
||||
search: [keyword: string];
|
||||
filterChange: [
|
||||
filters: {
|
||||
buildingIds?: number[] | null;
|
||||
floorIds?: number[] | null;
|
||||
status?: number[];
|
||||
rentArea?: number[];
|
||||
vacantDays?: number[];
|
||||
contractStatuses?: string[] | null;
|
||||
}
|
||||
];
|
||||
refresh: [];
|
||||
}>();
|
||||
|
||||
const searchKeyword = ref('');
|
||||
const selectedStatus = ref<number[]>([]);
|
||||
const viewMode = ref<'grid' | 'list'>('grid');
|
||||
const showFilterDrawer = ref(false);
|
||||
|
||||
// 房源状态选项(6个状态)
|
||||
const statusOptions = [
|
||||
{ id: 4, text: '在租', color: '#00b42a' },
|
||||
{ id: 1, text: '招商', color: '#165dff' },
|
||||
{ id: 2, text: '保留', color: '#ff7d00' },
|
||||
{ id: 5, text: '即将到期', color: '#f53f3f' },
|
||||
{ id: 6, text: '到期', color: '#f53f3f' },
|
||||
{ id: 3, text: '下架', color: '#86909c' },
|
||||
];
|
||||
|
||||
// 切换状态筛选
|
||||
const toggleStatus = (statusId: number) => {
|
||||
const index = selectedStatus.value.indexOf(statusId);
|
||||
if (index > -1) {
|
||||
selectedStatus.value.splice(index, 1);
|
||||
} else {
|
||||
selectedStatus.value.push(statusId);
|
||||
}
|
||||
emit('filterChange', { status: selectedStatus.value });
|
||||
};
|
||||
|
||||
// 环形图配置
|
||||
const chartOption = computed(() => {
|
||||
const data = [
|
||||
{
|
||||
name: '下架面积',
|
||||
value: parseFloat(
|
||||
props.statisticsData.removalAreaStr?.replace('㎡', '') || '0'
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '保留面积',
|
||||
value: parseFloat(
|
||||
props.statisticsData.keepAreaStr?.replace('㎡', '') || '0'
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '可租面积',
|
||||
value: parseFloat(
|
||||
props.statisticsData.rentAreaStr?.replace('㎡', '') || '0'
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '已租面积',
|
||||
value: parseFloat(
|
||||
props.statisticsData.useAreaStr?.replace('㎡', '') || '0'
|
||||
),
|
||||
},
|
||||
].filter((item) => item.value > 0);
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c}㎡ ({d}%)',
|
||||
confine: true, // 将 tooltip 限制在图表区域内
|
||||
appendToBody: true, // 将 tooltip 追加到 body,避免被遮挡
|
||||
extraCssText: 'z-index: 9999;', // 确保 tooltip 在最上层
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
right: 0,
|
||||
top: 'middle',
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
itemGap: 8,
|
||||
textStyle: {
|
||||
fontSize: 11,
|
||||
color: '#4e5969',
|
||||
},
|
||||
formatter: (name: string) => {
|
||||
// 限制图例文本长度,避免过长
|
||||
return name.length > 6 ? name.substring(0, 6) + '...' : name;
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: 0,
|
||||
right: '25%', // 为图例留出空间
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '面积分布',
|
||||
type: 'pie',
|
||||
radius: ['35%', '65%'], // 稍微缩小半径,为图例留出空间
|
||||
center: ['40%', '50%'], // 将图表向左移动
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
data: data,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// 计算房源占用的格子数
|
||||
const getRoomCells = (area: number): number => {
|
||||
if (area <= 250) return 1;
|
||||
if (area <= 500) return 2;
|
||||
if (area <= 750) return 3;
|
||||
if (area <= 1000) return 4;
|
||||
if (area <= 1250) return 5;
|
||||
if (area <= 1500) return 6;
|
||||
if (area <= 1750) return 7;
|
||||
return 8;
|
||||
};
|
||||
|
||||
// 按楼层组织房源数据(数据已经是按楼层分组的结构)
|
||||
const floorList = computed(() => {
|
||||
// 数据已经是按楼层分组的结构:list 中每个元素是一个楼层,包含 rooms 数组
|
||||
return props.roomList
|
||||
.map((floorData: any) => {
|
||||
const floorId = floorData.id || floorData.floorId || 0;
|
||||
const floorName = floorData.floorName || `楼层${floorId}`;
|
||||
const rooms = floorData.rooms || [];
|
||||
// 计算该楼层所有房间的总面积(使用 rentArea 累加)
|
||||
const totalArea = rooms.reduce((sum: number, room: any) => {
|
||||
return sum + parseFloat(room.rentArea || room.area || 0);
|
||||
}, 0);
|
||||
|
||||
// 对每个楼层进行网格布局
|
||||
const rows: any[][] = [];
|
||||
let currentRow: any[] = [];
|
||||
|
||||
if (viewMode.value === 'list') {
|
||||
// 列表模式:每个房源固定占用1格,一行最多8个
|
||||
rooms.forEach((room: any) => {
|
||||
// 如果当前行已满8个,换行
|
||||
if (currentRow.length >= 8) {
|
||||
rows.push(currentRow);
|
||||
currentRow = [];
|
||||
}
|
||||
// 添加房源到当前行,固定占用1格
|
||||
currentRow.push({ room, cells: 1 });
|
||||
});
|
||||
// 添加最后一行
|
||||
if (currentRow.length > 0) {
|
||||
rows.push(currentRow);
|
||||
}
|
||||
// 填充空单元格到8格
|
||||
rows.forEach((row) => {
|
||||
while (row.length < 8) {
|
||||
row.push({ room: null, cells: 1 });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 网格模式:按面积计算格子数
|
||||
let currentRowArea = 0;
|
||||
let currentRowTotalCells = 0; // 当前行的总格子数
|
||||
|
||||
rooms.forEach((room: any) => {
|
||||
const area = parseFloat(room.rentArea || room.area || 0);
|
||||
const cells = getRoomCells(area);
|
||||
|
||||
// 如果当前行加上这个房源会超过8格或2000平,则先处理当前行,然后换行
|
||||
if (
|
||||
currentRowTotalCells + cells > 8 ||
|
||||
currentRowArea + area > 2000
|
||||
) {
|
||||
if (currentRow.length > 0) {
|
||||
// 如果当前行的总格子数小于8,按比例放大到8格
|
||||
if (currentRowTotalCells < 8) {
|
||||
const scale = 8 / currentRowTotalCells;
|
||||
currentRow.forEach((cell) => {
|
||||
cell.cells = Math.round(cell.cells * scale);
|
||||
});
|
||||
}
|
||||
rows.push(currentRow);
|
||||
}
|
||||
currentRow = [];
|
||||
currentRowArea = 0;
|
||||
currentRowTotalCells = 0;
|
||||
}
|
||||
|
||||
// 添加房源到当前行
|
||||
currentRow.push({ room, cells });
|
||||
currentRowArea += area;
|
||||
currentRowTotalCells += cells;
|
||||
|
||||
// 如果当前行刚好8格,处理并换行
|
||||
if (currentRowTotalCells === 8) {
|
||||
rows.push(currentRow);
|
||||
currentRow = [];
|
||||
currentRowArea = 0;
|
||||
currentRowTotalCells = 0;
|
||||
} else if (currentRowTotalCells > 8) {
|
||||
// 如果超过8格,需要将最后一个房源移除,处理当前行,然后重新添加
|
||||
const lastCell = currentRow.pop();
|
||||
currentRowTotalCells -= lastCell.cells;
|
||||
currentRowArea -= parseFloat(
|
||||
lastCell.room.rentArea || lastCell.room.area || 0
|
||||
);
|
||||
|
||||
// 如果当前行的总格子数小于8,按比例放大到8格
|
||||
if (currentRowTotalCells < 8) {
|
||||
const scale = 8 / currentRowTotalCells;
|
||||
currentRow.forEach((cell) => {
|
||||
cell.cells = Math.round(cell.cells * scale);
|
||||
});
|
||||
}
|
||||
rows.push(currentRow);
|
||||
|
||||
// 重新开始一行,添加刚才移除的房源
|
||||
currentRow = [lastCell];
|
||||
currentRowArea = parseFloat(
|
||||
lastCell.room.rentArea || lastCell.room.area || 0
|
||||
);
|
||||
currentRowTotalCells = lastCell.cells;
|
||||
}
|
||||
});
|
||||
|
||||
// 添加最后一行
|
||||
if (currentRow.length > 0) {
|
||||
// 如果最后一行总格子数小于8,按比例放大到8格
|
||||
if (currentRowTotalCells < 8) {
|
||||
const scale = 8 / currentRowTotalCells;
|
||||
currentRow.forEach((cell) => {
|
||||
cell.cells = Math.round(cell.cells * scale);
|
||||
});
|
||||
}
|
||||
rows.push(currentRow);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
floorId,
|
||||
floorName,
|
||||
totalArea,
|
||||
rows,
|
||||
};
|
||||
})
|
||||
.sort((a: any, b: any) => b.floorId - a.floorId); // 按楼层ID排序(降序)
|
||||
});
|
||||
|
||||
// 获取房源单元格样式
|
||||
const getRoomCellStyle = (cell: any) => {
|
||||
if (!cell.room) return {};
|
||||
const cells = cell.cells || 1;
|
||||
return {
|
||||
gridColumn: `span ${cells}`,
|
||||
};
|
||||
};
|
||||
|
||||
// 获取房源单元格类名
|
||||
const getRoomCellClass = (cell: any) => {
|
||||
if (!cell.room) return 'bg-[#f7f8fa] border-[#e5e6eb]';
|
||||
const status = cell.room.status;
|
||||
const statusMap: Record<number, string> = {
|
||||
1: 'border-[#165dff] bg-[#f0f5ff]', // 招商
|
||||
2: 'border-[#ff7d00] bg-[#fff7e6]', // 保留
|
||||
3: 'border-[#86909c] bg-[#f2f3f5]', // 下架
|
||||
4: 'border-[#00b42a] bg-[#e8f8e8]', // 在租
|
||||
5: 'border-[#f53f3f] bg-[#ffece8]', // 即将到期
|
||||
6: 'border-[#f53f3f] bg-[#ffece8]', // 到期
|
||||
};
|
||||
return statusMap[status] || 'border-[#e5e6eb]';
|
||||
};
|
||||
|
||||
// 获取房源信息文本
|
||||
const getRoomInfo = (room: any) => {
|
||||
if (room.status === 4) {
|
||||
// 在租状态
|
||||
if (room.dateStr) {
|
||||
return room.dateStr;
|
||||
}
|
||||
if (room.days !== undefined && room.days > 0) {
|
||||
return `剩余${room.days}天`;
|
||||
}
|
||||
if (room.days !== undefined && room.days < 0) {
|
||||
return `到期${Math.abs(room.days)}天`;
|
||||
}
|
||||
return '租物';
|
||||
} else if (room.status === 1) {
|
||||
// 招商状态
|
||||
if (room.days !== undefined) {
|
||||
return `空置 ${room.days}天`;
|
||||
}
|
||||
return '空置';
|
||||
} else if (room.status === 2) {
|
||||
// 保留状态
|
||||
return '客户已下订,保留中';
|
||||
} else if (room.status === 3) {
|
||||
// 下架状态
|
||||
return '暂时下架,不出租了';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
emit('search', searchKeyword.value);
|
||||
};
|
||||
|
||||
// 筛选确认处理
|
||||
const handleFilterConfirm = (filters: {
|
||||
buildingIds: number[] | null;
|
||||
floorIds: number[] | null;
|
||||
statusIds: number[] | null;
|
||||
rentArea: number[];
|
||||
vacantDays: number[];
|
||||
contractStatuses: string[] | null;
|
||||
}) => {
|
||||
emit('filterChange', {
|
||||
buildingIds: filters.buildingIds,
|
||||
floorIds: filters.floorIds,
|
||||
status: filters.statusIds || undefined,
|
||||
rentArea: filters.rentArea,
|
||||
vacantDays: filters.vacantDays,
|
||||
contractStatuses: filters.contractStatuses,
|
||||
});
|
||||
};
|
||||
|
||||
// 点击房源查看详情
|
||||
const handleRoomClick = async (room: any) => {
|
||||
if (!room || !room.id) {
|
||||
Message.warning('房源信息不完整');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用 API 获取房源详情
|
||||
const response = await getRoom(room.id);
|
||||
if (response.code === 200 && response.data) {
|
||||
roomDetailInfo.value = response.data;
|
||||
showRoomDetailDrawer.value = true;
|
||||
} else {
|
||||
Message.error(response.message || '获取房源详情失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取房源详情失败:', error);
|
||||
Message.error('获取房源详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 新建房源
|
||||
const handleAddHouse = () => {
|
||||
showHouseFormDrawer.value = true;
|
||||
};
|
||||
|
||||
// 房源表单提交成功后的回调
|
||||
const handleHouseFormSubmitted = () => {
|
||||
// 触发父组件刷新数据
|
||||
emit('refresh');
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,630 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
:width="420"
|
||||
:visible="visible"
|
||||
title="筛选"
|
||||
unmount-on-close
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form :model="filterForm" layout="vertical">
|
||||
<!-- 项目(仅显示) -->
|
||||
<a-form-item label="项目">
|
||||
<span
|
||||
class="w-full bg-[#f2f3f5] h-[32px] px-[8px] leading-[32px] block rounded"
|
||||
>
|
||||
{{ props.projectName || '-' }}
|
||||
</span>
|
||||
</a-form-item>
|
||||
<!-- 楼栋(多选) -->
|
||||
<a-form-item label="楼栋">
|
||||
<a-select
|
||||
v-model="filterForm.buildingIds"
|
||||
multiple
|
||||
placeholder="请选择楼栋"
|
||||
:max-tag-count="3"
|
||||
>
|
||||
<a-option :value="ALL_BUILDING_ID">所有楼栋</a-option>
|
||||
<a-option
|
||||
v-for="item in buildingList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 楼层(多选) -->
|
||||
<a-form-item label="楼层">
|
||||
<a-select
|
||||
v-model="filterForm.floorIds"
|
||||
multiple
|
||||
placeholder="请选择楼层"
|
||||
:max-tag-count="3"
|
||||
>
|
||||
<a-option :value="ALL_FLOOR_ID">所有楼层</a-option>
|
||||
<a-option v-for="item in floorList" :key="item.id" :value="item.id">
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 房源状态(多选) -->
|
||||
<a-form-item label="房源状态">
|
||||
<a-select
|
||||
v-model="filterForm.statusIds"
|
||||
multiple
|
||||
placeholder="请选择房源状态"
|
||||
:max-tag-count="3"
|
||||
>
|
||||
<a-option
|
||||
v-for="item in HOUSE_STATUS"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.text }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 计租面积(滑块) -->
|
||||
<a-form-item label="计租面积(㎡)">
|
||||
<a-slider
|
||||
v-model="filterForm.rentArea"
|
||||
range
|
||||
:min="rentAreaMin"
|
||||
:max="rentAreaMax"
|
||||
:marks="rentAreaMarks"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 空置天数(滑块) -->
|
||||
<a-form-item label="空置天数(天)">
|
||||
<a-slider
|
||||
v-model="filterForm.vacantDays"
|
||||
range
|
||||
:min="vacantDaysMin"
|
||||
:max="vacantDaysMax"
|
||||
:marks="vacantDaysMarks"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 房源合同(多选) -->
|
||||
<a-form-item label="房源合同">
|
||||
<a-select
|
||||
v-model="filterForm.contractStatuses"
|
||||
multiple
|
||||
placeholder="请选择房源合同"
|
||||
:max-tag-count="3"
|
||||
>
|
||||
<a-option
|
||||
v-for="item in contractStatusList"
|
||||
:key="item"
|
||||
:value="item"
|
||||
>
|
||||
{{ item }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, computed, onMounted, nextTick } from 'vue';
|
||||
import { HOUSE_STATUS } from '@/constants';
|
||||
import { getRoomTagList } from '@/api/dictionary';
|
||||
|
||||
// 特殊值用于表示"所有"选项(使用负数避免与真实ID冲突)
|
||||
const ALL_BUILDING_ID = -1;
|
||||
const ALL_FLOOR_ID = -2;
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
roomList?: any[]; // 房源列表数据,用于提取筛选选项
|
||||
projectName?: string; // 项目名称(仅显示)
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
(
|
||||
e: 'confirm',
|
||||
payload: {
|
||||
buildingIds: number[] | null;
|
||||
floorIds: number[] | null;
|
||||
statusIds: number[] | null;
|
||||
rentArea: number[];
|
||||
vacantDays: number[];
|
||||
contractStatuses: string[] | null;
|
||||
}
|
||||
): void;
|
||||
}>();
|
||||
|
||||
// 保存的筛选状态
|
||||
const savedFilterState = ref<{
|
||||
buildingIds: number[];
|
||||
floorIds: number[];
|
||||
statusIds: number[];
|
||||
rentArea: number[];
|
||||
vacantDays: number[];
|
||||
contractStatuses: string[];
|
||||
} | null>(null);
|
||||
|
||||
// 筛选表单数据
|
||||
const filterForm = ref({
|
||||
buildingIds: [] as number[],
|
||||
floorIds: [] as number[],
|
||||
statusIds: [] as number[],
|
||||
rentArea: [0, 0] as number[],
|
||||
vacantDays: [0, 0] as number[],
|
||||
contractStatuses: [] as string[],
|
||||
});
|
||||
|
||||
// 楼栋列表(从房源列表中提取唯一值)
|
||||
const buildingList = computed(() => {
|
||||
if (!props.roomList || props.roomList.length === 0) return [];
|
||||
const buildingMap = new Map();
|
||||
props.roomList.forEach((floor) => {
|
||||
if (floor.rooms && Array.isArray(floor.rooms)) {
|
||||
floor.rooms.forEach((room: any) => {
|
||||
if (room.buildingId && room.buildingName) {
|
||||
if (!buildingMap.has(room.buildingId)) {
|
||||
buildingMap.set(room.buildingId, {
|
||||
id: room.buildingId,
|
||||
name: room.buildingName,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return Array.from(buildingMap.values());
|
||||
});
|
||||
|
||||
// 楼层列表(从房源列表中提取唯一值)
|
||||
const floorList = computed(() => {
|
||||
if (!props.roomList || props.roomList.length === 0) return [];
|
||||
const floorMap = new Map();
|
||||
props.roomList.forEach((floor) => {
|
||||
if (floor.id !== undefined && floor.floorName) {
|
||||
if (!floorMap.has(floor.id)) {
|
||||
floorMap.set(floor.id, {
|
||||
id: floor.id,
|
||||
name: floor.floorName,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return Array.from(floorMap.values());
|
||||
});
|
||||
|
||||
// 房源合同状态列表(从房源列表中提取唯一值)
|
||||
const contractStatusList = computed(() => {
|
||||
if (!props.roomList || props.roomList.length === 0) return [];
|
||||
const statusSet = new Set<string>();
|
||||
props.roomList.forEach((floor) => {
|
||||
if (floor.rooms && Array.isArray(floor.rooms)) {
|
||||
floor.rooms.forEach((room: any) => {
|
||||
if (room.contractStatusStr && room.contractStatusStr.trim()) {
|
||||
statusSet.add(room.contractStatusStr);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return Array.from(statusSet).sort();
|
||||
});
|
||||
|
||||
// 计租面积范围(从所有房源中计算)
|
||||
const rentAreaMin = computed(() => {
|
||||
if (!props.roomList || props.roomList.length === 0) return 0;
|
||||
const areas: number[] = [];
|
||||
props.roomList.forEach((floor) => {
|
||||
if (floor.rooms && Array.isArray(floor.rooms)) {
|
||||
floor.rooms.forEach((room: any) => {
|
||||
const area = parseFloat(room.rentArea || 0);
|
||||
if (!isNaN(area) && area > 0) {
|
||||
areas.push(area);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return areas.length > 0 ? Math.min(...areas) : 0;
|
||||
});
|
||||
|
||||
const rentAreaMax = computed(() => {
|
||||
if (!props.roomList || props.roomList.length === 0) return 100;
|
||||
const areas: number[] = [];
|
||||
props.roomList.forEach((floor) => {
|
||||
if (floor.rooms && Array.isArray(floor.rooms)) {
|
||||
floor.rooms.forEach((room: any) => {
|
||||
const area = parseFloat(room.rentArea || 0);
|
||||
if (!isNaN(area) && area > 0) {
|
||||
areas.push(area);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return areas.length > 0 ? Math.max(...areas) : 100;
|
||||
});
|
||||
|
||||
const rentAreaMarks = computed(() => {
|
||||
return {
|
||||
[rentAreaMin.value]: `${rentAreaMin.value}㎡`,
|
||||
[rentAreaMax.value]: `${rentAreaMax.value}㎡`,
|
||||
};
|
||||
});
|
||||
|
||||
// 空置天数范围(只从招商状态的房源中计算)
|
||||
const vacantDaysMin = computed(() => {
|
||||
if (!props.roomList || props.roomList.length === 0) return 0;
|
||||
const days: number[] = [];
|
||||
props.roomList.forEach((floor) => {
|
||||
if (floor.rooms && Array.isArray(floor.rooms)) {
|
||||
floor.rooms.forEach((room: any) => {
|
||||
// 只计算招商状态(status === 1)的房源
|
||||
if (
|
||||
room.status === 1 &&
|
||||
room.days !== undefined &&
|
||||
room.days !== null
|
||||
) {
|
||||
const day = Number(room.days);
|
||||
if (!isNaN(day) && day >= 0) {
|
||||
days.push(day);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return days.length > 0 ? Math.min(...days) : 0;
|
||||
});
|
||||
|
||||
const vacantDaysMax = computed(() => {
|
||||
if (!props.roomList || props.roomList.length === 0) return 100;
|
||||
const days: number[] = [];
|
||||
props.roomList.forEach((floor) => {
|
||||
if (floor.rooms && Array.isArray(floor.rooms)) {
|
||||
floor.rooms.forEach((room: any) => {
|
||||
// 只计算招商状态(status === 1)的房源
|
||||
if (
|
||||
room.status === 1 &&
|
||||
room.days !== undefined &&
|
||||
room.days !== null
|
||||
) {
|
||||
const day = Number(room.days);
|
||||
if (!isNaN(day) && day >= 0) {
|
||||
days.push(day);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return days.length > 0 ? Math.max(...days) : 100;
|
||||
});
|
||||
|
||||
const vacantDaysMarks = computed(() => {
|
||||
return {
|
||||
[vacantDaysMin.value]: `${vacantDaysMin.value}天`,
|
||||
[vacantDaysMax.value]: `${vacantDaysMax.value}天`,
|
||||
};
|
||||
});
|
||||
|
||||
// 用于防止 watch 循环更新的标志
|
||||
const isUpdatingBuilding = ref(false);
|
||||
const isUpdatingFloor = ref(false);
|
||||
|
||||
// 监听楼栋选择变化
|
||||
watch(
|
||||
() => filterForm.value.buildingIds,
|
||||
(newVal, oldVal) => {
|
||||
if (isUpdatingBuilding.value) return;
|
||||
if (!oldVal || oldVal.length === 0) return; // 初始化时跳过
|
||||
|
||||
const isAllSelected = newVal.includes(ALL_BUILDING_ID);
|
||||
const wasAllOnly = oldVal.length === 1 && oldVal[0] === ALL_BUILDING_ID;
|
||||
const hasSpecificSelected = newVal.some((id) => id !== ALL_BUILDING_ID);
|
||||
const specificIds = newVal.filter((id) => id !== ALL_BUILDING_ID);
|
||||
const allBuildingIds = buildingList.value.map((b) => b.id);
|
||||
|
||||
// 情况1:用户选择了"所有"选项(从非"所有"状态切换到包含"所有")
|
||||
if (isAllSelected && hasSpecificSelected && !wasAllOnly) {
|
||||
isUpdatingBuilding.value = true;
|
||||
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingBuilding.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况2:用户从"所有"切换到选择具体选项
|
||||
if (!isAllSelected && wasAllOnly && hasSpecificSelected) {
|
||||
if (
|
||||
specificIds.length === allBuildingIds.length &&
|
||||
allBuildingIds.every((id) => specificIds.includes(id))
|
||||
) {
|
||||
isUpdatingBuilding.value = true;
|
||||
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingBuilding.value = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况3:用户选择了具体选项,但当前值还包含"所有"
|
||||
if (hasSpecificSelected && isAllSelected && wasAllOnly) {
|
||||
isUpdatingBuilding.value = true;
|
||||
filterForm.value.buildingIds = specificIds;
|
||||
nextTick(() => {
|
||||
isUpdatingBuilding.value = false;
|
||||
if (
|
||||
specificIds.length === allBuildingIds.length &&
|
||||
allBuildingIds.every((id) => specificIds.includes(id))
|
||||
) {
|
||||
isUpdatingBuilding.value = true;
|
||||
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingBuilding.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况4:用户一个个勾选具体选项,当选中数量等于总数时,自动切换为"所有"
|
||||
if (
|
||||
!isAllSelected &&
|
||||
hasSpecificSelected &&
|
||||
specificIds.length === allBuildingIds.length
|
||||
) {
|
||||
if (allBuildingIds.every((id) => specificIds.includes(id))) {
|
||||
isUpdatingBuilding.value = true;
|
||||
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingBuilding.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听楼层选择变化
|
||||
watch(
|
||||
() => filterForm.value.floorIds,
|
||||
(newVal, oldVal) => {
|
||||
if (isUpdatingFloor.value) return;
|
||||
if (!oldVal || oldVal.length === 0) return; // 初始化时跳过
|
||||
|
||||
const isAllSelected = newVal.includes(ALL_FLOOR_ID);
|
||||
const wasAllOnly = oldVal.length === 1 && oldVal[0] === ALL_FLOOR_ID;
|
||||
const hasSpecificSelected = newVal.some((id) => id !== ALL_FLOOR_ID);
|
||||
const specificIds = newVal.filter((id) => id !== ALL_FLOOR_ID);
|
||||
const allFloorIds = floorList.value.map((f) => f.id);
|
||||
|
||||
// 情况1:用户选择了"所有"选项(从非"所有"状态切换到包含"所有")
|
||||
if (isAllSelected && hasSpecificSelected && !wasAllOnly) {
|
||||
isUpdatingFloor.value = true;
|
||||
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingFloor.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况2:用户从"所有"切换到选择具体选项
|
||||
if (!isAllSelected && wasAllOnly && hasSpecificSelected) {
|
||||
if (
|
||||
specificIds.length === allFloorIds.length &&
|
||||
allFloorIds.every((id) => specificIds.includes(id))
|
||||
) {
|
||||
isUpdatingFloor.value = true;
|
||||
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingFloor.value = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况3:用户选择了具体选项,但当前值还包含"所有"
|
||||
if (hasSpecificSelected && isAllSelected && wasAllOnly) {
|
||||
isUpdatingFloor.value = true;
|
||||
filterForm.value.floorIds = specificIds;
|
||||
nextTick(() => {
|
||||
isUpdatingFloor.value = false;
|
||||
if (
|
||||
specificIds.length === allFloorIds.length &&
|
||||
allFloorIds.every((id) => specificIds.includes(id))
|
||||
) {
|
||||
isUpdatingFloor.value = true;
|
||||
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingFloor.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况4:用户一个个勾选具体选项,当选中数量等于总数时,自动切换为"所有"
|
||||
if (
|
||||
!isAllSelected &&
|
||||
hasSpecificSelected &&
|
||||
specificIds.length === allFloorIds.length
|
||||
) {
|
||||
if (allFloorIds.every((id) => specificIds.includes(id))) {
|
||||
isUpdatingFloor.value = true;
|
||||
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||
nextTick(() => {
|
||||
isUpdatingFloor.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 初始化筛选表单
|
||||
const initFilterForm = () => {
|
||||
const minArea = rentAreaMin.value || 0;
|
||||
const maxArea = rentAreaMax.value || 100;
|
||||
const minDays = vacantDaysMin.value || 0;
|
||||
const maxDays = vacantDaysMax.value || 100;
|
||||
|
||||
// 如果有保存的状态,恢复它(但要过滤掉不存在的ID)
|
||||
if (savedFilterState.value) {
|
||||
const allBuildingIds = buildingList.value.map((b) => b.id);
|
||||
const allFloorIds = floorList.value.map((f) => f.id);
|
||||
|
||||
// 处理楼栋ID
|
||||
let buildingIds = savedFilterState.value.buildingIds;
|
||||
if (!buildingIds.includes(ALL_BUILDING_ID)) {
|
||||
const validBuildingIds = buildingIds.filter((id) =>
|
||||
allBuildingIds.includes(id)
|
||||
);
|
||||
if (
|
||||
validBuildingIds.length === allBuildingIds.length &&
|
||||
allBuildingIds.length > 0
|
||||
) {
|
||||
buildingIds = [ALL_BUILDING_ID];
|
||||
} else if (validBuildingIds.length > 0) {
|
||||
buildingIds = validBuildingIds;
|
||||
} else {
|
||||
buildingIds = [ALL_BUILDING_ID];
|
||||
}
|
||||
}
|
||||
|
||||
// 处理楼层ID
|
||||
let floorIds = savedFilterState.value.floorIds;
|
||||
if (!floorIds.includes(ALL_FLOOR_ID)) {
|
||||
const validFloorIds = floorIds.filter((id) => allFloorIds.includes(id));
|
||||
if (
|
||||
validFloorIds.length === allFloorIds.length &&
|
||||
allFloorIds.length > 0
|
||||
) {
|
||||
floorIds = [ALL_FLOOR_ID];
|
||||
} else if (validFloorIds.length > 0) {
|
||||
floorIds = validFloorIds;
|
||||
} else {
|
||||
floorIds = [ALL_FLOOR_ID];
|
||||
}
|
||||
}
|
||||
|
||||
filterForm.value = {
|
||||
buildingIds,
|
||||
floorIds,
|
||||
statusIds: [...savedFilterState.value.statusIds],
|
||||
rentArea: [...savedFilterState.value.rentArea],
|
||||
vacantDays: [...savedFilterState.value.vacantDays],
|
||||
contractStatuses: [...savedFilterState.value.contractStatuses],
|
||||
};
|
||||
} else {
|
||||
// 否则初始化,楼栋和楼层默认选中"所有"
|
||||
filterForm.value = {
|
||||
buildingIds: [ALL_BUILDING_ID],
|
||||
floorIds: [ALL_FLOOR_ID],
|
||||
statusIds: [],
|
||||
rentArea: [minArea, maxArea],
|
||||
vacantDays: [minDays, maxDays],
|
||||
contractStatuses: [],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
// 处理"所有"选项:如果同时选择了"所有"和具体选项,只保留"所有"
|
||||
let finalBuildingIds = [...filterForm.value.buildingIds];
|
||||
let finalFloorIds = [...filterForm.value.floorIds];
|
||||
|
||||
if (
|
||||
finalBuildingIds.includes(ALL_BUILDING_ID) &&
|
||||
finalBuildingIds.length > 1
|
||||
) {
|
||||
finalBuildingIds = [ALL_BUILDING_ID];
|
||||
}
|
||||
if (finalFloorIds.includes(ALL_FLOOR_ID) && finalFloorIds.length > 1) {
|
||||
finalFloorIds = [ALL_FLOOR_ID];
|
||||
}
|
||||
|
||||
// 保存当前筛选状态(保存处理后的值)
|
||||
savedFilterState.value = {
|
||||
buildingIds: finalBuildingIds,
|
||||
floorIds: finalFloorIds,
|
||||
statusIds: [...filterForm.value.statusIds],
|
||||
rentArea: [...filterForm.value.rentArea],
|
||||
vacantDays: [...filterForm.value.vacantDays],
|
||||
contractStatuses: [...filterForm.value.contractStatuses],
|
||||
};
|
||||
|
||||
// 标准化ID数组:如果选择了"所有"选项,则返回null(表示全部)
|
||||
const normalizeIds = (ids: number[], allOptionId: number) => {
|
||||
if (ids.length === 0) return null;
|
||||
// 如果选择了"所有"选项,则返回null
|
||||
if (ids.includes(allOptionId)) {
|
||||
return null;
|
||||
}
|
||||
return ids;
|
||||
};
|
||||
|
||||
emit('confirm', {
|
||||
buildingIds: normalizeIds(finalBuildingIds, ALL_BUILDING_ID),
|
||||
floorIds: normalizeIds(finalFloorIds, ALL_FLOOR_ID),
|
||||
statusIds:
|
||||
filterForm.value.statusIds.length === 0
|
||||
? null
|
||||
: filterForm.value.statusIds,
|
||||
rentArea: filterForm.value.rentArea,
|
||||
vacantDays: filterForm.value.vacantDays,
|
||||
contractStatuses:
|
||||
filterForm.value.contractStatuses.length === 0
|
||||
? null
|
||||
: filterForm.value.contractStatuses,
|
||||
});
|
||||
handleCancel();
|
||||
};
|
||||
|
||||
// 监听抽屉打开,初始化数据
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
// 延迟初始化,确保 roomList 已更新
|
||||
setTimeout(() => {
|
||||
initFilterForm();
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 监听楼栋和楼层列表变化,如果当前没有保存状态且列表为空,则默认选中"所有"
|
||||
watch(
|
||||
[buildingList, floorList],
|
||||
() => {
|
||||
if (props.visible && !savedFilterState.value) {
|
||||
// 如果楼栋列表为空,默认选中"所有楼栋"
|
||||
if (filterForm.value.buildingIds.length === 0) {
|
||||
filterForm.value.buildingIds = [ALL_BUILDING_ID];
|
||||
}
|
||||
// 如果楼层列表为空,默认选中"所有楼层"
|
||||
if (filterForm.value.floorIds.length === 0) {
|
||||
filterForm.value.floorIds = [ALL_FLOOR_ID];
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</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,351 @@
|
||||
<template>
|
||||
<div class="house_view_wrap">
|
||||
<Breadcrumb :items="['menu.business', 'menu.business.house-view']" />
|
||||
<div class="bg-[#fff] flex flex-col">
|
||||
<a-tabs
|
||||
v-if="topBuildList && topBuildList.length > 0"
|
||||
v-model="activeBuildId"
|
||||
type="card"
|
||||
class="flex-shrink-0"
|
||||
@change="handleTabChange"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="item in topBuildList"
|
||||
:key="item.buildingId"
|
||||
:title="item.buildingName"
|
||||
>
|
||||
<div class="house-view-scroll-container">
|
||||
<HouseViewContent
|
||||
v-if="
|
||||
currentProjectId &&
|
||||
activeBuildId &&
|
||||
Number(activeBuildId) === Number(item.buildingId)
|
||||
"
|
||||
:key="`building-${activeBuildId}-project-${currentProjectId}`"
|
||||
:statistics-data="statisticsData"
|
||||
:room-list="roomList"
|
||||
:loading="contentLoading"
|
||||
:project-name="currentProjectName"
|
||||
@search="handleSearch"
|
||||
@filter-change="handleFilterChange"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted, computed } from 'vue';
|
||||
import {
|
||||
getTopBuildingTagsByProjectId,
|
||||
getStatisticsData,
|
||||
getRoomOverList,
|
||||
} from '@/api/room';
|
||||
import { useUserStore } from '@/store';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import type { BuildingTag } from '@/types/business';
|
||||
import HouseViewContent from './components/house-view-content.vue';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const topBuildList = ref<BuildingTag[]>([]);
|
||||
const loading = ref(false);
|
||||
const activeBuildId = ref<number | null>(null);
|
||||
const statisticsData = ref<any>({});
|
||||
const roomList = ref<any[]>([]);
|
||||
const contentLoading = ref(false);
|
||||
const searchKeyword = ref('');
|
||||
const selectedStatus = ref<number[]>([]);
|
||||
const selectedBuildingIds = ref<number[] | null>(null);
|
||||
const selectedFloorIds = ref<number[] | null>(null);
|
||||
const rentAreaRange = ref<number[]>([0, 0]);
|
||||
const vacantDaysRange = ref<number[]>([0, 0]);
|
||||
const selectedContractStatuses = ref<string[] | null>(null);
|
||||
const isInitialized = ref(false); // 标记是否已经初始化加载过数据
|
||||
|
||||
// 计算当前项目ID
|
||||
const currentProjectId = computed(() => userStore.currentProjectId);
|
||||
const currentProjectName = computed(() => {
|
||||
const id = userStore.currentProjectId;
|
||||
const list = userStore.projects || [];
|
||||
const found = list.find((p: any) => p.id === id);
|
||||
return found?.name || '';
|
||||
});
|
||||
|
||||
// 加载统计数据
|
||||
const loadStatisticsData = async () => {
|
||||
if (!currentProjectId.value || !activeBuildId.value) return;
|
||||
|
||||
try {
|
||||
const response = await getStatisticsData({
|
||||
keyword: searchKeyword.value,
|
||||
projectId: currentProjectId.value,
|
||||
buildingId: activeBuildId.value,
|
||||
floorId: 0,
|
||||
rentAreaMin: rentAreaRange.value[0] || 0,
|
||||
rentAreaMax: rentAreaRange.value[1] || 0,
|
||||
status: selectedStatus.value.length > 0 ? selectedStatus.value : [],
|
||||
});
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
const data = response.data as { statisticsData?: any };
|
||||
if (data.statisticsData) {
|
||||
// statisticsData.value = data.statisticsData
|
||||
statisticsData.value = {
|
||||
roomNum: 25,
|
||||
roomAreaStr: '1000.㎡',
|
||||
rentAreaStr: '1380.00㎡',
|
||||
keepAreaStr: '600.00㎡',
|
||||
useAreaStr: '809.00㎡',
|
||||
removalAreaStr: '320.00㎡',
|
||||
rentRateStr: '67.80',
|
||||
totalRentRateStr: '79.10',
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载房源列表数据
|
||||
const loadRoomData = async () => {
|
||||
if (!currentProjectId.value || !activeBuildId.value) return;
|
||||
|
||||
contentLoading.value = true;
|
||||
try {
|
||||
const response = await getRoomOverList({
|
||||
keyword: searchKeyword.value,
|
||||
projectId: currentProjectId.value,
|
||||
buildingId: activeBuildId.value,
|
||||
floorId: 0,
|
||||
rentAreaMin: rentAreaRange.value[0] || 0,
|
||||
rentAreaMax: rentAreaRange.value[1] || 0,
|
||||
status: selectedStatus.value.length > 0 ? selectedStatus.value : [],
|
||||
// 注意:如果接口支持空置天数和标签筛选,可以在这里添加
|
||||
// vacantDaysMin: vacantDaysRange.value[0] || 0,
|
||||
// vacantDaysMax: vacantDaysRange.value[1] || 0,
|
||||
// tagIds: selectedTagIds.value.length > 0 ? selectedTagIds.value : [],
|
||||
});
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
const data = response.data as { list?: any[] };
|
||||
// 数据已经是按楼层分组的结构,直接使用
|
||||
roomList.value = Array.isArray(data.list) ? data.list : [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取房源列表失败:', error);
|
||||
Message.error('获取房源列表失败');
|
||||
} finally {
|
||||
contentLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载所有数据
|
||||
const loadAllData = async () => {
|
||||
if (!currentProjectId.value || !activeBuildId.value) return;
|
||||
await Promise.all([loadStatisticsData(), loadRoomData()]);
|
||||
};
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = (keyword: string) => {
|
||||
searchKeyword.value = keyword;
|
||||
loadAllData();
|
||||
};
|
||||
|
||||
// 筛选变化处理
|
||||
const handleFilterChange = (filters: {
|
||||
buildingIds?: number[] | null;
|
||||
floorIds?: number[] | null;
|
||||
status?: number[];
|
||||
rentArea?: number[];
|
||||
vacantDays?: number[];
|
||||
contractStatuses?: string[] | null;
|
||||
}) => {
|
||||
if (filters.buildingIds !== undefined) {
|
||||
selectedBuildingIds.value = filters.buildingIds;
|
||||
}
|
||||
if (filters.floorIds !== undefined) {
|
||||
selectedFloorIds.value = filters.floorIds;
|
||||
}
|
||||
if (filters.status !== undefined) {
|
||||
selectedStatus.value = filters.status;
|
||||
}
|
||||
if (filters.rentArea !== undefined) {
|
||||
rentAreaRange.value = filters.rentArea;
|
||||
}
|
||||
if (filters.vacantDays !== undefined) {
|
||||
vacantDaysRange.value = filters.vacantDays;
|
||||
}
|
||||
if (filters.contractStatuses !== undefined) {
|
||||
selectedContractStatuses.value = filters.contractStatuses;
|
||||
}
|
||||
loadAllData();
|
||||
};
|
||||
|
||||
// 刷新数据(新建房源成功后调用)
|
||||
const handleRefresh = () => {
|
||||
loadAllData();
|
||||
};
|
||||
|
||||
// Tab 切换处理
|
||||
const handleTabChange = (activeKey: string | number) => {
|
||||
console.log('handleTabChange 被调用', {
|
||||
activeKey,
|
||||
activeKeyType: typeof activeKey,
|
||||
activeBuildId: activeBuildId.value,
|
||||
activeBuildIdType: typeof activeBuildId.value,
|
||||
currentProjectId: currentProjectId.value,
|
||||
});
|
||||
// 确保 activeBuildId 被正确设置(转换为数字)
|
||||
const newBuildingId =
|
||||
typeof activeKey === 'string' ? Number(activeKey) : activeKey;
|
||||
// 确保 activeBuildId 也是数字类型
|
||||
if (newBuildingId && !isNaN(newBuildingId)) {
|
||||
activeBuildId.value = newBuildingId;
|
||||
console.log('Tab 切换,设置 activeBuildId 并加载数据', {
|
||||
newBuildingId,
|
||||
currentBuildId: activeBuildId.value,
|
||||
});
|
||||
// activeBuildId 变化会触发 watch,这里不需要再次调用 loadAllData
|
||||
}
|
||||
};
|
||||
|
||||
async function getTopBuildingTagsData() {
|
||||
const projectId = userStore.currentProjectId;
|
||||
|
||||
if (!projectId) {
|
||||
Message.warning('请先选择项目');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await getTopBuildingTagsByProjectId(projectId);
|
||||
if (response.code === 200 && response.data?.tags) {
|
||||
topBuildList.value = Array.isArray(response.data.tags)
|
||||
? response.data.tags
|
||||
: [];
|
||||
if (topBuildList.value.length > 0) {
|
||||
// 如果当前选中的楼栋不在新列表中,或者没有选中楼栋,则选择第一个
|
||||
const currentBuilding = topBuildList.value.find(
|
||||
(item) => item.buildingId === activeBuildId.value
|
||||
);
|
||||
if (!currentBuilding) {
|
||||
activeBuildId.value = topBuildList.value[0]?.buildingId;
|
||||
// activeBuildId 变化会触发 watch,不需要手动调用
|
||||
}
|
||||
// 如果楼栋ID没有变化(可能是同一个项目重新加载),但需要初始化加载数据
|
||||
// 这种情况下 watch 不会触发,需要手动调用一次
|
||||
else if (
|
||||
activeBuildId.value &&
|
||||
currentProjectId.value &&
|
||||
!isInitialized.value
|
||||
) {
|
||||
isInitialized.value = true;
|
||||
loadAllData();
|
||||
}
|
||||
} else {
|
||||
activeBuildId.value = null;
|
||||
}
|
||||
} else {
|
||||
topBuildList.value = [];
|
||||
activeBuildId.value = null;
|
||||
Message.error(response.message || '获取楼栋列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取楼栋列表失败:', error);
|
||||
topBuildList.value = [];
|
||||
activeBuildId.value = null;
|
||||
Message.error('获取楼栋列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 activeBuildId 变化,重新加载数据
|
||||
watch(
|
||||
activeBuildId,
|
||||
(newBuildingId, oldBuildingId) => {
|
||||
console.log('watch activeBuildId 触发', {
|
||||
newBuildingId,
|
||||
oldBuildingId,
|
||||
currentProjectId: currentProjectId.value,
|
||||
isEqual: newBuildingId === oldBuildingId,
|
||||
});
|
||||
// 如果 buildingId 有值且项目ID也有值
|
||||
if (newBuildingId && currentProjectId.value) {
|
||||
// 如果值变化了(从 null 变为有值,或者从一个值变为另一个值),就加载数据
|
||||
if (newBuildingId !== oldBuildingId) {
|
||||
console.log('activeBuildId 变化,准备加载数据', {
|
||||
newBuildingId,
|
||||
oldBuildingId,
|
||||
});
|
||||
isInitialized.value = true;
|
||||
loadAllData();
|
||||
} else {
|
||||
console.log('activeBuildId 没有变化,跳过加载', {
|
||||
newBuildingId,
|
||||
oldBuildingId,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log('条件不满足,清空数据', {
|
||||
newBuildingId,
|
||||
currentProjectId: currentProjectId.value,
|
||||
});
|
||||
statisticsData.value = {};
|
||||
roomList.value = [];
|
||||
isInitialized.value = false;
|
||||
}
|
||||
},
|
||||
{ immediate: false }
|
||||
);
|
||||
|
||||
// 监听项目ID变化,重新获取数据
|
||||
watch(
|
||||
() => userStore.currentProjectId,
|
||||
(newProjectId) => {
|
||||
if (newProjectId) {
|
||||
isInitialized.value = false; // 重置初始化标志
|
||||
getTopBuildingTagsData();
|
||||
} else {
|
||||
topBuildList.value = [];
|
||||
statisticsData.value = {};
|
||||
roomList.value = [];
|
||||
isInitialized.value = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
if (userStore.currentProjectId) {
|
||||
getTopBuildingTagsData();
|
||||
} else {
|
||||
// 如果没有选中的项目,尝试使用第一个项目
|
||||
if (userStore.projects && userStore.projects.length > 0) {
|
||||
userStore.setInfo({ currentProjectId: userStore.projects[0].id });
|
||||
getTopBuildingTagsData();
|
||||
} else {
|
||||
Message.warning('请先选择项目');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.house_view_wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.house-view-scroll-container {
|
||||
max-height: calc(100vh - 56px - 58px - 40px);
|
||||
padding-bottom: 20px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch; // iOS 平滑滚动
|
||||
}
|
||||
</style>
|
||||
@@ -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/tenants';
|
||||
|
||||
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?.filingName || '-' }}
|
||||
</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/tenants';
|
||||
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: 'filingName',
|
||||
key: 'filingName',
|
||||
slotName: 'filingType',
|
||||
},
|
||||
{
|
||||
title: '信用评分',
|
||||
dataIndex: 'creditScore',
|
||||
key: 'creditScore',
|
||||
slotName: 'creditScore',
|
||||
},
|
||||
{ title: '操作', key: 'operate', slotName: 'operate', width: 120 },
|
||||
];
|
||||
|
||||
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,526 @@
|
||||
<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 :model="filingForm" layout="vertical">
|
||||
<a-form-item label="请选择归档原因" field="filingType" required>
|
||||
<a-select
|
||||
v-model="filingForm.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/tenants';
|
||||
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 filingForm = ref<{ filingType?: number }>({ filingType: 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;
|
||||
filingForm.value.filingType = undefined;
|
||||
showFilingModal.value = true;
|
||||
};
|
||||
|
||||
// 确认归档
|
||||
const handleFilingConfirm = async () => {
|
||||
if (!filingForm.value.filingType) {
|
||||
Message.warning('请选择归档原因');
|
||||
return;
|
||||
}
|
||||
if (!currentTenantId.value) {
|
||||
Message.error('租客ID不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { code, message } = await filingTenant({
|
||||
id: currentTenantId.value,
|
||||
filingType: filingForm.value.filingType,
|
||||
});
|
||||
|
||||
if (code === 200) {
|
||||
Message.success(message || '归档成功');
|
||||
showFilingModal.value = false;
|
||||
filingForm.value.filingType = undefined;
|
||||
currentTenantId.value = null;
|
||||
// 重新加载列表数据
|
||||
await getTenantListData();
|
||||
} else {
|
||||
Message.error(message || '归档失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
Message.error(error.message || '归档失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 取消归档
|
||||
const handleFilingCancel = () => {
|
||||
showFilingModal.value = false;
|
||||
filingForm.value.filingType = 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>
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<div>monitor</div>
|
||||
</template>
|
||||
@@ -1,48 +0,0 @@
|
||||
export default {
|
||||
'menu.dashboard.monitor': 'Real-time Monitor',
|
||||
'monitor.title.chatPanel': 'Chat Window',
|
||||
'monitor.title.quickOperation': 'Quick Operation',
|
||||
'monitor.title.studioInfo': 'Studio Information',
|
||||
'monitor.title.studioPreview': 'Studio Preview',
|
||||
'monitor.chat.options.all': 'All',
|
||||
'monitor.chat.placeholder.searchCategory': 'Search Category',
|
||||
'monitor.chat.update': 'Update',
|
||||
'monitor.list.title.order': 'Order',
|
||||
'monitor.list.title.cover': 'Cover',
|
||||
'monitor.list.title.name': 'Name',
|
||||
'monitor.list.title.duration': 'Duration',
|
||||
'monitor.list.title.id': 'ID',
|
||||
'monitor.list.tip.rotations': 'Rotations ',
|
||||
'monitor.list.tip.rest': ', The program list is not visible to viewers',
|
||||
'monitor.list.tag.auditFailed': 'Audit Failed',
|
||||
'monitor.tab.title.liveMethod': 'Live Method',
|
||||
'monitor.tab.title.onlinePopulation': 'Online Population',
|
||||
'monitor.liveMethod.normal': 'Normal Live',
|
||||
'monitor.liveMethod.flowControl': 'Flow Control Live',
|
||||
'monitor.liveMethod.video': 'Video Live',
|
||||
'monitor.liveMethod.web': 'Web Live',
|
||||
'monitor.editCarousel': 'Edit',
|
||||
'monitor.startCarousel': 'Start',
|
||||
'monitor.quickOperation.changeClarity': 'Change the Clarity',
|
||||
'monitor.quickOperation.switchStream': 'Switch Stream',
|
||||
'monitor.quickOperation.removeClarity': 'Remove the Clarity',
|
||||
'monitor.quickOperation.pushFlowGasket': 'Push Flow Gasket',
|
||||
'monitor.studioInfo.label.studioTitle': 'Studio Title',
|
||||
'monitor.studioInfo.label.onlineNotification': 'Online Notification',
|
||||
'monitor.studioInfo.label.studioCategory': 'Studio Category',
|
||||
'monitor.studioInfo.placeholder.studioTitle': "'s Studio",
|
||||
'monitor.studioInfo.btn.fresh': 'Fresh',
|
||||
'monitor.studioStatus.title.studioStatus': 'Studio Status',
|
||||
'monitor.studioStatus.title.pictureInfo': 'Picture Information',
|
||||
'monitor.studioStatus.smooth': 'Smooth',
|
||||
'monitor.studioStatus.frameRate': 'Frame',
|
||||
'monitor.studioStatus.bitRate': 'Bit',
|
||||
'monitor.studioStatus.mainstream': 'Main',
|
||||
'monitor.studioStatus.hotStandby': 'Hot',
|
||||
'monitor.studioStatus.coldStandby': 'Cold',
|
||||
'monitor.studioStatus.line': 'Line',
|
||||
'monitor.studioStatus.play': 'Format',
|
||||
'monitor.studioStatus.pictureQuality': 'Quality',
|
||||
'monitor.studioPreview.studio': 'Studio',
|
||||
'monitor.studioPreview.watching': 'watching',
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
export default {
|
||||
'menu.dashboard.monitor': '实时监控',
|
||||
'monitor.title.chatPanel': '聊天窗口',
|
||||
'monitor.title.quickOperation': '快捷操作',
|
||||
'monitor.title.studioInfo': '直播信息',
|
||||
'monitor.title.studioPreview': '直播预览',
|
||||
'monitor.chat.options.all': '全部',
|
||||
'monitor.chat.placeholder.searchCategory': '搜索类目',
|
||||
'monitor.chat.update': '更新',
|
||||
'monitor.list.title.order': '序号',
|
||||
'monitor.list.title.cover': '封面',
|
||||
'monitor.list.title.name': '名称',
|
||||
'monitor.list.title.duration': '视频时长',
|
||||
'monitor.list.title.id': '视频Id',
|
||||
'monitor.list.tip.rotations': '轮播次数',
|
||||
'monitor.list.tip.rest': ',节目单观众不可见',
|
||||
'monitor.list.tag.auditFailed': '审核未通过',
|
||||
'monitor.tab.title.liveMethod': '直播方式',
|
||||
'monitor.tab.title.onlinePopulation': '在线人数',
|
||||
'monitor.liveMethod.normal': '普通直播',
|
||||
'monitor.liveMethod.flowControl': '控流直播',
|
||||
'monitor.liveMethod.video': '视频直播',
|
||||
'monitor.liveMethod.web': '网页开播',
|
||||
'monitor.editCarousel': '编辑轮播',
|
||||
'monitor.startCarousel': '开始轮播',
|
||||
'monitor.quickOperation.changeClarity': '切换清晰度',
|
||||
'monitor.quickOperation.switchStream': '主备流切换',
|
||||
'monitor.quickOperation.removeClarity': '摘除清晰度',
|
||||
'monitor.quickOperation.pushFlowGasket': '推流垫片',
|
||||
'monitor.studioInfo.label.studioTitle': '直播标题',
|
||||
'monitor.studioInfo.label.onlineNotification': '上线通知',
|
||||
'monitor.studioInfo.label.studioCategory': '直播类目',
|
||||
'monitor.studioInfo.placeholder.studioTitle': '的直播间',
|
||||
'monitor.studioInfo.btn.fresh': '更新',
|
||||
'monitor.studioStatus.title.studioStatus': '直播状态',
|
||||
'monitor.studioStatus.title.pictureInfo': '画面信息',
|
||||
'monitor.studioStatus.smooth': '流畅',
|
||||
'monitor.studioStatus.frameRate': '帧率',
|
||||
'monitor.studioStatus.bitRate': '码率',
|
||||
'monitor.studioStatus.mainstream': '主流',
|
||||
'monitor.studioStatus.hotStandby': '热备',
|
||||
'monitor.studioStatus.coldStandby': '冷备',
|
||||
'monitor.studioStatus.line': '线路',
|
||||
'monitor.studioStatus.play': '播放格式',
|
||||
'monitor.studioStatus.pictureQuality': '画质',
|
||||
'monitor.studioPreview.studio': '直播间',
|
||||
'monitor.studioPreview.watching': '在看',
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<div>workplace</div>
|
||||
</template>
|
||||
@@ -1,38 +0,0 @@
|
||||
export default {
|
||||
'menu.dashboard.workplace': 'Workplace',
|
||||
'workplace.welcome': 'Welcome!',
|
||||
'workplace.balance': 'Balance (CNY)',
|
||||
'workplace.order.pending': 'Pending',
|
||||
'workplace.order.pendingRenewal': 'Renewal Order',
|
||||
'workplace.onlineContent': 'Online Content',
|
||||
'workplace.putIn': 'Put In',
|
||||
'workplace.newDay': 'Daily Additional Comments',
|
||||
'workplace.newFromYesterday': 'New From Yesterday',
|
||||
'workplace.minute': 'Min',
|
||||
'workplace.docs': 'Documents',
|
||||
'workplace.docs.productOverview': 'Product Overview',
|
||||
'workplace.docs.userGuide': 'User Guide',
|
||||
'workplace.docs.workflow': 'Workflow',
|
||||
'workplace.docs.interfaceDocs': 'Interface Docs',
|
||||
//
|
||||
'workplace.contentManagement': 'Content Management',
|
||||
'workplace.contentStatistical': 'Content Statistical',
|
||||
'workplace.advanced': 'Advanced',
|
||||
'workplace.onlinePromotion': 'Online Promotion',
|
||||
'workplace.contentPutIn': 'Put In',
|
||||
'workplace.announcement': 'Announcement',
|
||||
'workplace.recently.visited': 'Recently Visited',
|
||||
'workplace.record.nodata': 'No data',
|
||||
'workplace.quick.operation': 'Quick Operation',
|
||||
'workplace.quickOperation.setup': 'Setup',
|
||||
'workplace.allProject': 'All',
|
||||
'workplace.loadMore': 'More',
|
||||
'workplace.viewMore': 'More',
|
||||
'workplace.contentData': 'Content Data',
|
||||
'workplace.popularContent': 'Popular Content',
|
||||
'workplace.popularContent.text': 'text',
|
||||
'workplace.popularContent.image': 'image',
|
||||
'workplace.popularContent.video': 'video',
|
||||
'workplace.categoriesPercent': 'Categories Percent',
|
||||
'workplace.pecs': 'pecs',
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
export default {
|
||||
'menu.dashboard.workplace': '工作台',
|
||||
'workplace.welcome': '欢迎回来!',
|
||||
'workplace.balance': '余额(元)',
|
||||
'workplace.order.pending': '待支付',
|
||||
'workplace.order.pendingRenewal': '待续费订单',
|
||||
'workplace.onlineContent': '线上总内容',
|
||||
'workplace.putIn': '投放中内容',
|
||||
'workplace.newDay': '日新增评论',
|
||||
'workplace.newFromYesterday': '较昨日新增',
|
||||
'workplace.minute': '分钟',
|
||||
'workplace.docs': '帮助文档',
|
||||
'workplace.docs.productOverview': '产品概要',
|
||||
'workplace.docs.userGuide': '使用指南',
|
||||
'workplace.docs.workflow': '接入流程',
|
||||
'workplace.docs.interfaceDocs': '接口文档',
|
||||
'workplace.contentManagement': '内容管理',
|
||||
'workplace.contentStatistical': '内容分析',
|
||||
'workplace.advanced': '高级管理',
|
||||
'workplace.onlinePromotion': '线上推广',
|
||||
'workplace.contentPutIn': '内容投放',
|
||||
'workplace.announcement': '公告',
|
||||
'workplace.recently.visited': '最近访问',
|
||||
'workplace.record.nodata': '暂无数据',
|
||||
'workplace.quick.operation': '快捷操作',
|
||||
'workplace.quickOperation.setup': '管理',
|
||||
'workplace.allProject': '所有项目',
|
||||
'workplace.loadMore': '加载更多',
|
||||
'workplace.viewMore': '查看更多',
|
||||
'workplace.contentData': '内容数据',
|
||||
'workplace.popularContent': '线上热门内容',
|
||||
'workplace.popularContent.text': '文本',
|
||||
'workplace.popularContent.image': '图片',
|
||||
'workplace.popularContent.video': '视频',
|
||||
'workplace.categoriesPercent': '内容类型占比',
|
||||
'workplace.pecs': '个',
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
<template>
|
||||
<div class="error-container">
|
||||
<a-result
|
||||
status="error"
|
||||
title="操作失败"
|
||||
sub-title="很抱歉,您的操作未能完成,请检查并重试"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button key="retry" type="primary">重试</a-button>
|
||||
<a-button key="back" @click="back">返回上一页</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ResultError',
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
|
||||
const back = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
return {
|
||||
back,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.error-container {
|
||||
padding: 24px;
|
||||
background-color: var(--color-bg-2);
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,51 +0,0 @@
|
||||
<template>
|
||||
<div class="success-container">
|
||||
<a-result
|
||||
status="success"
|
||||
title="操作成功"
|
||||
sub-title="操作已成功完成,您可以继续其他操作或返回首页"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button key="home" type="primary" @click="toHome"
|
||||
>返回首页</a-button
|
||||
>
|
||||
<a-button key="back" @click="back">返回上一页</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ResultSuccess',
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
|
||||
const toHome = () => {
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
const back = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
return {
|
||||
toHome,
|
||||
back,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.success-container {
|
||||
padding: 24px;
|
||||
background-color: var(--color-bg-2);
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,7 @@
|
||||
<Breadcrumb
|
||||
:items="['menu.setting', 'menu.setting.system', 'setting.contract']"
|
||||
/>
|
||||
<a-card class="full_height">
|
||||
<a-card>
|
||||
<a-row :gutter="24">
|
||||
<a-col
|
||||
v-if="contractFileInfo && Object.keys(contractFileInfo).length"
|
||||
@@ -13,18 +13,84 @@
|
||||
<div
|
||||
class="mt-[24px] rounded-[6px] border-solid border-[#efefef] border-[1px] px-[12px] py-[20px]"
|
||||
>
|
||||
<h4 class="text-[16px] font-bold">{{ contractFileInfo?.name }}</h4>
|
||||
<div class="flex items-center">
|
||||
<p class="text-[16px] font-bold mr-[10px]">{{
|
||||
contractFileInfo?.name
|
||||
}}</p>
|
||||
<p class="cursor-pointer px-[5px]" @click="openEditModal"
|
||||
><icon-edit
|
||||
/></p>
|
||||
</div>
|
||||
<p class="text-[14px] mt-[12px]"
|
||||
><span class="mr-[5px] text-[#101010]">适用范围</span
|
||||
>立思广州分公司</p
|
||||
>{{ departmentName }}</p
|
||||
>
|
||||
<p class="text-[14px] mt-[4px]"
|
||||
><span class="mr-[5px] text-[#101010]">适用项目</span
|
||||
>星河商务中心</p
|
||||
>{{ projectNames }}</p
|
||||
>
|
||||
</div>
|
||||
<a-modal
|
||||
v-model:visible="editModalVisible"
|
||||
title="编辑合同文档模板信息"
|
||||
unmount-on-close
|
||||
@ok="confirmEditModal"
|
||||
@cancel="editModalVisible = false"
|
||||
>
|
||||
<a-form
|
||||
ref="editFormRef"
|
||||
:model="editForm"
|
||||
:rules="editFormRules"
|
||||
layout="vertical"
|
||||
auto-label-width
|
||||
>
|
||||
<a-form-item label="模板名称" field="name" required>
|
||||
<a-input
|
||||
v-model="editForm.name"
|
||||
placeholder="请输入合同模板名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="适用范围" field="departmentId" required>
|
||||
<a-tree-select
|
||||
v-model="editForm.departmentId"
|
||||
:data="departmentListData"
|
||||
:field-names="{
|
||||
key: 'id',
|
||||
title: 'name',
|
||||
children: 'children',
|
||||
}"
|
||||
placeholder="请选择适用范围"
|
||||
allow-clear
|
||||
@change="onEditDepartmentChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="适用项目" field="projects" required>
|
||||
<a-select
|
||||
v-model="editForm.projects"
|
||||
multiple
|
||||
:disabled="!editForm.departmentId"
|
||||
placeholder="请选择适用项目"
|
||||
>
|
||||
<a-option
|
||||
v-for="item in editDeptProjectList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<div class="flex items-center mt-[24px]">
|
||||
<a-input-search class="w-[256px] h-[30px]" placeholder="搜索名称" />
|
||||
<a-input-search
|
||||
v-model="contractPlaceholderListParams.keyword"
|
||||
class="w-[256px] h-[30px]"
|
||||
placeholder="搜索名称"
|
||||
allow-clear
|
||||
@search="handlePlaceholderSearch"
|
||||
@press-enter="handlePlaceholderSearch"
|
||||
/>
|
||||
<div
|
||||
class="flex items-center border-[1px] border-solid border-[#165DFF] px-[4px] py-[6px] ml-[10px]"
|
||||
>
|
||||
@@ -32,20 +98,51 @@
|
||||
<p class="ml-[5px] text-[#165DFF]">AI助手</p>
|
||||
</div>
|
||||
</div>
|
||||
<a-tabs default-active-key="1" class="mt-[10px]">
|
||||
<a-tab-pane key="1" title="租赁条款"> 租赁条款 </a-tab-pane>
|
||||
<a-tab-pane key="2" title="物业条款"> w物业条款 </a-tab-pane>
|
||||
<a-tabs
|
||||
v-model:active-key="contractPlaceholderListParams.type"
|
||||
class="mt-[10px]"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="tab in placeholderTabs"
|
||||
:key="tab.key"
|
||||
class="cursor-pointer"
|
||||
:title="tab.title"
|
||||
>
|
||||
<a-descriptions bordered :column="1">
|
||||
<a-descriptions-item
|
||||
v-for="item in contractPlaceholderList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
>
|
||||
{{ item.keyword }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-col>
|
||||
<a-col :span="17">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<a-button>
|
||||
<template #icon>
|
||||
<icon-upload />
|
||||
<FileUpload
|
||||
v-model:object-keys="uploadObjectKeys"
|
||||
v-model:signed-put-u-r-l="uploadSignedPutURL"
|
||||
:max-count="1"
|
||||
:upload-type="1"
|
||||
accept=".doc,.docx"
|
||||
:show-file-list="false"
|
||||
:disabled="!contractFileInfo?.id"
|
||||
@upload-success="onUploadSuccess"
|
||||
@upload-error="onUploadError"
|
||||
>
|
||||
<template #trigger>
|
||||
<a-button :disabled="!contractFileInfo?.id">
|
||||
<template #icon>
|
||||
<icon-upload />
|
||||
</template>
|
||||
上传文档
|
||||
</a-button>
|
||||
</template>
|
||||
上传文档
|
||||
</a-button>
|
||||
</FileUpload>
|
||||
<a-button class="ml-[10px]">
|
||||
<template #icon>
|
||||
<icon-cloud-download />
|
||||
@@ -54,8 +151,8 @@
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<p>2025-09-10 00:25:30更新</p>
|
||||
<a-button type="primary" class="ml-[10px]">
|
||||
<p>{{ contractFileInfo?.updatedAt }}更新</p>
|
||||
<a-button type="primary" class="ml-[10px]" @click="handleSave">
|
||||
<template #icon>
|
||||
<icon-storage />
|
||||
</template>
|
||||
@@ -65,14 +162,28 @@
|
||||
</div>
|
||||
<!-- OnlyOffice 文档编辑器 -->
|
||||
<div class="onlyoffice-wrapper">
|
||||
<!-- <OnlyOfficeEditor v-if="contractFileInfo?.url" :document-url="contractFileInfo.url"
|
||||
:document-title="contractFileInfo.name || '合同文档'"
|
||||
:document-type="getDocumentType(contractFileInfo.url)" :height="'600px'" :editable="true"
|
||||
:mode="'edit'" @ready="onEditorReady" @error="onEditorError" @save="onDocumentSave"
|
||||
@close="onEditorClose" />
|
||||
<div v-else class="no-document">
|
||||
<a-empty description="暂无文档内容" />
|
||||
</div> -->
|
||||
<OnlyOfficeEditor
|
||||
v-if="contractFileInfo?.url"
|
||||
:document-url="contractFileInfo.url"
|
||||
:document-title="contractFileInfo?.name || '合同文档'"
|
||||
:document-type="getDocumentType(contractFileInfo.url)"
|
||||
height="600px"
|
||||
:editable="true"
|
||||
mode="edit"
|
||||
@ready="onEditorReady"
|
||||
@error="onEditorError"
|
||||
@save="onDocumentSave"
|
||||
@close="onEditorClose"
|
||||
/>
|
||||
<div v-else class="no-document">
|
||||
<a-empty
|
||||
:description="
|
||||
contractFileInfo
|
||||
? '暂无文档内容,请上传文档'
|
||||
: '请先选择左侧合同模板'
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -80,13 +191,136 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onMounted, ref, reactive, computed, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { getWordSettingFromId } from '@/api/setting';
|
||||
import { CreateFileSettingParam } from '@/types/setting';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import {
|
||||
getWordSettingFromId,
|
||||
getContractPlaceholderList,
|
||||
updateWordSetting,
|
||||
} from '@/api/contracts';
|
||||
import { getDepartmentList } from '@/api/department';
|
||||
import { getDeptProjectListFromDeptId } from '@/api/common';
|
||||
import { CreateFileSettingParam, DepartmentItem } from '@/types/setting';
|
||||
import FileUpload from '@/components/common/FileUpload.vue';
|
||||
|
||||
const contractFileInfo = ref<CreateFileSettingParam | null>(null);
|
||||
const contractPlaceholderListParams = ref({ keyword: '', type: 1 });
|
||||
|
||||
const placeholderTabs = [
|
||||
{ key: 1, title: '租赁条款' },
|
||||
{ key: 2, title: '物业条款' },
|
||||
];
|
||||
const contractPlaceholderList = ref<any[]>([]);
|
||||
const placeholderListLoading = ref(false);
|
||||
// 占位符列表按 tab type 缓存:1 租赁条款,2 物业条款
|
||||
const placeholderListCache = ref<Record<number, any[]>>({});
|
||||
const uploadObjectKeys = ref<string[]>([]);
|
||||
const uploadSignedPutURL = ref<string[]>([]);
|
||||
|
||||
// 编辑弹窗:仅改本地,不请求接口
|
||||
const editModalVisible = ref(false);
|
||||
const editFormRef = ref<FormInstance>();
|
||||
const departmentListData = ref<DepartmentItem[]>([]);
|
||||
const editDeptProjectList = ref<{ id: number; name: string }[]>([]);
|
||||
const editForm = reactive<{
|
||||
name: string;
|
||||
departmentId: number | null;
|
||||
projects: number[];
|
||||
}>({
|
||||
name: '',
|
||||
departmentId: null,
|
||||
projects: [],
|
||||
});
|
||||
const editFormRules = {
|
||||
name: [{ required: true, message: '请输入合同模板名称' }],
|
||||
departmentId: [{ required: true, message: '请选择适用范围' }],
|
||||
projects: [
|
||||
{ required: true, message: '请选择适用项目', type: 'array', min: 1 },
|
||||
],
|
||||
};
|
||||
|
||||
const departmentName = computed(() => {
|
||||
if (!contractFileInfo.value?.departmentId) return '-';
|
||||
const find = (list: DepartmentItem[], id: number): string | undefined => {
|
||||
for (const item of list) {
|
||||
if (item.id === id) return item.name;
|
||||
if (item.children?.length) {
|
||||
const n = find(item.children, id);
|
||||
if (n) return n;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
return (
|
||||
find(departmentListData.value, contractFileInfo.value.departmentId) ?? '-'
|
||||
);
|
||||
});
|
||||
|
||||
// 适用项目展示:有则用顿号拼接名称,无则显示 -
|
||||
const projectNames = computed(() => {
|
||||
const list = contractFileInfo.value?.projects;
|
||||
if (!list || !Array.isArray(list) || list.length === 0) return '-';
|
||||
const names = list
|
||||
.map((i: any) => (i?.name != null ? String(i.name).trim() : ''))
|
||||
.filter(Boolean);
|
||||
return names.length > 0 ? names.join('、') : '-';
|
||||
});
|
||||
|
||||
const openEditModal = () => {
|
||||
if (!contractFileInfo.value) return;
|
||||
editForm.name = contractFileInfo.value.name ?? '';
|
||||
editForm.departmentId = contractFileInfo.value.departmentId ?? null;
|
||||
editForm.projects = (contractFileInfo.value.projects || []).map((p: any) =>
|
||||
typeof p === 'object' && p?.id != null ? p.id : p
|
||||
);
|
||||
editModalVisible.value = true;
|
||||
if (editForm.departmentId) {
|
||||
getDeptProjectListFromDeptId(editForm.departmentId).then(
|
||||
({ code, data }) => {
|
||||
if (code === 200)
|
||||
editDeptProjectList.value = data?.deptProjectList ?? [];
|
||||
}
|
||||
);
|
||||
} else {
|
||||
editDeptProjectList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const onEditDepartmentChange = (id: number | undefined) => {
|
||||
editForm.projects = [];
|
||||
if (!id) {
|
||||
editDeptProjectList.value = [];
|
||||
return;
|
||||
}
|
||||
getDeptProjectListFromDeptId(id).then(({ code, data }) => {
|
||||
if (code === 200) editDeptProjectList.value = data?.deptProjectList ?? [];
|
||||
});
|
||||
};
|
||||
|
||||
const confirmEditModal = async () => {
|
||||
try {
|
||||
await editFormRef.value?.validate();
|
||||
} catch {
|
||||
return false; // 校验失败时不关闭弹窗
|
||||
}
|
||||
if (!contractFileInfo.value) return false;
|
||||
contractFileInfo.value.name = editForm.name.trim();
|
||||
contractFileInfo.value.departmentId = editForm.departmentId;
|
||||
contractFileInfo.value.projects = editForm.projects.map((id) => ({
|
||||
id,
|
||||
name: editDeptProjectList.value.find((p) => p.id === id)?.name ?? '',
|
||||
})) as any;
|
||||
editModalVisible.value = false;
|
||||
Message.success('请点击保存按钮提交更新');
|
||||
};
|
||||
|
||||
const getDepartmentListData = async () => {
|
||||
const { code, data } = await getDepartmentList();
|
||||
if (code === 200) departmentListData.value = data?.departmentList ?? [];
|
||||
};
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
// 获取文档数据
|
||||
@@ -96,16 +330,17 @@
|
||||
if (code === 200 && data) {
|
||||
const { contractWordSetting } = data;
|
||||
contractFileInfo.value = contractWordSetting || null;
|
||||
contractFileInfo.value.url =
|
||||
'http://oss.kola.work/documents%2F20250910%2F%E5%90%88%E5%90%8C%E6%A8%A1%E7%89%88.docx?Expires=1757479342&OSSAccessKeyId=LTAI5tDDNxXnZq7QcpZEf6hR&Signature=ydBzbF2yCyRWZ0G52s%2FPr%2BFIUzo%3D';
|
||||
// contractFileInfo.value.url =
|
||||
// 'http://oss.kola.work/documents%2F20250910%2F%E5%90%88%E5%90%8C%E6%A8%A1%E7%89%88.docx?Expires=1757479342&OSSAccessKeyId=LTAI5tDDNxXnZq7QcpZEf6hR&Signature=ydBzbF2yCyRWZ0G52s%2FPr%2BFIUzo%3D';
|
||||
}
|
||||
};
|
||||
|
||||
// 根据文件 URL 获取文档类型
|
||||
// 根据文件 URL 获取文档类型(兼容带 ? 的签名链接)
|
||||
const getDocumentType = (url: string): 'word' | 'excel' | 'powerpoint' => {
|
||||
if (!url) return 'word';
|
||||
|
||||
const extension = url.toLowerCase().split('.').pop();
|
||||
const pathWithoutQuery = url.split('?')[0];
|
||||
const extension = pathWithoutQuery.toLowerCase().split('.').pop() || '';
|
||||
switch (extension) {
|
||||
case 'xlsx':
|
||||
case 'xls':
|
||||
@@ -142,8 +377,92 @@
|
||||
Message.info('编辑器已关闭');
|
||||
};
|
||||
|
||||
// 上传成功:仅更新本地 url,不请求接口(统一由保存按钮提交)
|
||||
const onUploadSuccess = (payload: {
|
||||
objectKey: string;
|
||||
signedPutURL: string;
|
||||
}) => {
|
||||
if (!contractFileInfo.value?.id) return;
|
||||
contractFileInfo.value.url = payload.signedPutURL;
|
||||
Message.success('文档已上传,请点击保存按钮提交到服务器');
|
||||
};
|
||||
|
||||
const onUploadError = () => {
|
||||
Message.error('上传失败,请重试');
|
||||
};
|
||||
|
||||
// 占位符搜索:按当前关键词重新拉取列表
|
||||
const handlePlaceholderSearch = () => {
|
||||
getContractPlaceholderListData();
|
||||
};
|
||||
|
||||
// 获取合同占位符列表(type: 1 租赁,2 物业),请求成功后写入缓存
|
||||
const getContractPlaceholderListData = async () => {
|
||||
const { type, keyword } = contractPlaceholderListParams.value;
|
||||
placeholderListLoading.value = true;
|
||||
try {
|
||||
const { code, data } = await getContractPlaceholderList({
|
||||
type,
|
||||
keyword,
|
||||
});
|
||||
const list = code === 200 ? data?.placeholderList ?? [] : [];
|
||||
contractPlaceholderList.value = list;
|
||||
placeholderListCache.value[type] = [...list];
|
||||
} catch (e) {
|
||||
Message.error('获取占位符列表失败');
|
||||
contractPlaceholderList.value = [];
|
||||
} finally {
|
||||
placeholderListLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 切换 tab:有缓存则用缓存,无则请求并写入缓存(immediate 负责首屏请求)
|
||||
watch(
|
||||
() => contractPlaceholderListParams.value.type,
|
||||
(newType) => {
|
||||
const cached = placeholderListCache.value[newType];
|
||||
if (cached !== undefined) {
|
||||
contractPlaceholderList.value = cached;
|
||||
} else {
|
||||
getContractPlaceholderListData();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 保存按钮:统一调用更新接口
|
||||
const handleSave = async () => {
|
||||
if (!contractFileInfo.value?.id) {
|
||||
Message.warning('请先选择合同模板');
|
||||
return;
|
||||
}
|
||||
const departmentId = contractFileInfo.value.departmentId ?? 0;
|
||||
const projects = (contractFileInfo.value.projects || []).map((p: any) =>
|
||||
typeof p === 'object' && p?.id != null ? p.id : p
|
||||
);
|
||||
const url = contractFileInfo.value.url ?? '';
|
||||
const params = {
|
||||
id: contractFileInfo.value.id,
|
||||
name: contractFileInfo.value.name,
|
||||
departmentId,
|
||||
projects,
|
||||
url,
|
||||
};
|
||||
try {
|
||||
const { code, message } = await updateWordSetting(params);
|
||||
if (code === 200) {
|
||||
Message.success(message || '保存成功');
|
||||
} else {
|
||||
Message.error(message || '保存失败');
|
||||
}
|
||||
} catch (e) {
|
||||
Message.error('保存失败');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getWordSettingFromIdData();
|
||||
getDepartmentListData();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -152,7 +471,6 @@
|
||||
margin-top: 16px;
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
@@ -162,28 +480,7 @@
|
||||
background: #f7f8fa;
|
||||
}
|
||||
|
||||
.system-setting-contract-file {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.full_height {
|
||||
height: calc(100vh - 120px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.arco-card-body) {
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.arco-row) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.arco-col) {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* .system-setting-contract-file {
|
||||
height: 100vh;
|
||||
} */
|
||||
</style>
|
||||
|
||||
@@ -64,8 +64,8 @@
|
||||
delWordSetting,
|
||||
getWordSettingList,
|
||||
disableWordSetting,
|
||||
getDepartmentList,
|
||||
} from '@/api/setting';
|
||||
} from '@/api/contracts';
|
||||
import { getDepartmentList } from '@/api/department';
|
||||
import { ContractWordItem, DepartmentItem } from '@/types/setting';
|
||||
import { UserStatus } from '@/types/comm';
|
||||
import { camelToSnake } from '@/utils/caseConvert';
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
getCodeSettingFromId,
|
||||
disableCodeSetting,
|
||||
delCodeSetting,
|
||||
} from '@/api/setting';
|
||||
} from '@/api/contracts';
|
||||
import { ContractTermInfo } from '@/types/setting';
|
||||
import { UserStatus } from '@/types/comm';
|
||||
import { camelToSnake } from '@/utils/caseConvert';
|
||||
|
||||
@@ -256,13 +256,13 @@
|
||||
import {
|
||||
getTemplateSettingFromId,
|
||||
getTermsSettingFromId,
|
||||
getDepartmentList,
|
||||
getAllCodeSettingList,
|
||||
getContractTagsList,
|
||||
updateTemplateSetting,
|
||||
getAllTermsSettingList,
|
||||
getTermsDataFromId,
|
||||
} from '@/api/setting';
|
||||
} from '@/api/contracts';
|
||||
import { getDepartmentList } from '@/api/department';
|
||||
import contractTemplateForm from './modal/contract-template-form.vue';
|
||||
import { DepartmentItem } from '@/types/setting';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
|
||||
@@ -63,9 +63,9 @@
|
||||
import {
|
||||
delTemplateSetting,
|
||||
getTemplateSettingList,
|
||||
getDepartmentList,
|
||||
disableTemplateSetting,
|
||||
} from '@/api/setting';
|
||||
} from '@/api/contracts';
|
||||
import { getDepartmentList } from '@/api/department';
|
||||
import { ContractTemplateSettingItem, DepartmentItem } from '@/types/setting';
|
||||
import { UserStatus } from '@/types/comm';
|
||||
import contractTemplateForm from './modal/contract-template-form.vue';
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
getTermsSettingFromId,
|
||||
disableTermsSetting,
|
||||
delTermsSetting,
|
||||
} from '@/api/setting';
|
||||
} from '@/api/contracts';
|
||||
import { TermSettingItem } from '@/types/setting';
|
||||
import { UserStatus } from '@/types/comm';
|
||||
import contractNumberFormDrawer from './drawer/contract-terms-form-drawer.vue';
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
import { ref, watch, reactive, computed } from 'vue';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { createCodeSetting, updateCodeSetting } from '@/api/setting';
|
||||
import { createCodeSetting, updateCodeSetting } from '@/api/contracts';
|
||||
// import { CreateCodeSettingParam } from '@/types/setting'
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
|
||||
@@ -102,11 +102,8 @@
|
||||
import { ref, watch, reactive, onMounted } from 'vue';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import {
|
||||
createTermsSetting,
|
||||
updateTermsSetting,
|
||||
getUseFeeList,
|
||||
} from '@/api/setting';
|
||||
import { createTermsSetting, updateTermsSetting } from '@/api/contracts';
|
||||
import { getUseFeeList } from '@/api/finance-setting';
|
||||
import { feeTypeList, financeFeeFeriodicityList } from '@/utils/constant';
|
||||
import { CreateTermSettingParam, FeeManagement } from '@/types/setting';
|
||||
import { FeeManagementType } from '@/types/comm';
|
||||
|
||||
@@ -199,20 +199,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
const priceUnits = [
|
||||
{ value: 1, text: '元/㎡*月' },
|
||||
{ value: 2, text: '元/㎡*天' },
|
||||
{ value: 3, text: '元/天' },
|
||||
{ value: 4, text: '元/月' },
|
||||
{ value: 5, text: '元/年' },
|
||||
];
|
||||
const priceUnitMap = new Map([
|
||||
[1, '元/㎡*月'],
|
||||
[2, '元/㎡*天'],
|
||||
[3, '元/天'],
|
||||
[4, '元/月'],
|
||||
[5, '元/年'],
|
||||
]);
|
||||
import { PRICE_UNITS, PRICE_UNIT_MAP } from '@/constants';
|
||||
|
||||
const priceUnits = PRICE_UNITS;
|
||||
const priceUnitMap = PRICE_UNIT_MAP;
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
record?: any | null;
|
||||
@@ -259,7 +249,7 @@
|
||||
emit('update:visible', false);
|
||||
emit('submitted', form);
|
||||
} catch {
|
||||
console.warn('❌ 校验未通过');
|
||||
// 校验未通过
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import { createWordSetting, updateWordSetting } from '@/api/setting';
|
||||
import { createWordSetting, updateWordSetting } from '@/api/contracts';
|
||||
import { DepartmentItem } from '@/types/setting';
|
||||
import { getDeptProjectListFromDeptId } from '@/api/common';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
@@ -34,7 +34,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import { createTemplateSetting, updateTemplateSetting } from '@/api/setting';
|
||||
import {
|
||||
createTemplateSetting,
|
||||
updateTemplateSetting,
|
||||
} from '@/api/contracts';
|
||||
import { DepartmentItem, CreateTemplateSettingParam } from '@/types/setting';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import rentSettingDrawer from '../drawer/rent-setting-drawer.vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { getUseTurnoverAccountList } from '@/api/setting';
|
||||
import { getUseTurnoverAccountList } from '@/api/finance-setting';
|
||||
const isShowDrawer = ref<boolean>(false);
|
||||
// 与抽屉保持一致的默认表单,用于初始回显与编辑回填
|
||||
const defaultForm: any = {
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
createContractTags,
|
||||
updateContractTags,
|
||||
delContractTags,
|
||||
} from '@/api/setting';
|
||||
} from '@/api/dictionary';
|
||||
import { ProjectTypeChecked } from '@/types/comm';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
createProjectType,
|
||||
updateProjectType,
|
||||
delProjectType,
|
||||
} from '@/api/setting';
|
||||
} from '@/api/dictionary';
|
||||
import { projectTypeInfo, ProvinceItem } from '@/types/setting';
|
||||
import { ProjectTypeChecked } from '@/types/comm';
|
||||
import useLoading from '@/hooks/loading';
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
createRoomTag,
|
||||
updateRoomTag,
|
||||
delRoomTag,
|
||||
} from '@/api/setting';
|
||||
} from '@/api/dictionary';
|
||||
import { projectTypeInfo } from '@/types/setting';
|
||||
import { ProjectTypeChecked } from '@/types/comm';
|
||||
import useLoading from '@/hooks/loading';
|
||||
|
||||
@@ -202,7 +202,7 @@
|
||||
createTenantDictionary,
|
||||
updateTenantDictionary,
|
||||
delTenantDictionary,
|
||||
} from '@/api/setting';
|
||||
} from '@/api/dictionary';
|
||||
import { ProjectTypeChecked, TenantDictionaryType } from '@/types/comm';
|
||||
import { projectTypeInfo } from '@/types/setting';
|
||||
import useLoading from '@/hooks/loading';
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
import { ref, watch, reactive } from 'vue';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { createFee, updateFee } from '@/api/setting';
|
||||
import { createFee, updateFee } from '@/api/finance-setting';
|
||||
import { CreateFeeParams } from '@/types/setting';
|
||||
import { feeTypeList } from '@/utils/constant';
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import useDataTable from '@/composables/useDataTable';
|
||||
import type { TableColumnData } from '@arco-design/web-vue';
|
||||
import { getFeeList, disableFee, delFee } from '@/api/setting';
|
||||
import { getFeeList, disableFee, delFee } from '@/api/finance-setting';
|
||||
import { FeeManagement } from '@/types/setting';
|
||||
import { FeeType, UserStatus } from '@/types/comm';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { getFeeFromId } from '@/api/setting';
|
||||
import { getFeeFromId } from '@/api/finance-setting';
|
||||
import feeManagementTable from './fee-management-table.vue';
|
||||
import feeManagementDrawer from './drawer/fee-management-drawer.vue';
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import { createDepartment, updateDepartment } from '@/api/setting';
|
||||
import { createDepartment, updateDepartment } from '@/api/department';
|
||||
import { DepartmentItem, UserList } from '@/types/setting';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
// Props & Emits
|
||||
|
||||
@@ -129,15 +129,15 @@
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import useDepartmentList from '@/composables/useDepartmentList';
|
||||
import { getAllUserList } from '@/api/user';
|
||||
import {
|
||||
getAllUserList,
|
||||
departmentSetUp,
|
||||
departmentSetDown,
|
||||
delDepartment,
|
||||
saveDataPermission,
|
||||
} from '@/api/setting';
|
||||
} from '@/api/department';
|
||||
import { DepartmentItem, UserList } from '@/types/setting';
|
||||
import { getAllProjectList } from '@/api/business';
|
||||
import { getAllProjectList } from '@/api/project';
|
||||
import { debounce } from '@/utils';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import departmentFormModal from './components/department-form-modal.vue';
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
import { ref, watch, reactive } from 'vue';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { createWorkflows } from '@/api/setting';
|
||||
import { createWorkflows } from '@/api/workflows';
|
||||
import { getAllProjectIdNameList, getWorkflowsTypeList } from '@/api/common';
|
||||
const allProjetList = ref<any>([]);
|
||||
const isLoading = ref<boolean>(false);
|
||||
@@ -149,7 +149,7 @@
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
// 部门列表
|
||||
|
||||
async function getAllProjectListData() {
|
||||
try {
|
||||
const { code, data } = await getAllProjectIdNameList();
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
saveWorkflows,
|
||||
toggleWorkflowsDisable,
|
||||
delWorkflows,
|
||||
} from '@/api/setting';
|
||||
} from '@/api/workflows';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import addApproval from './components/add-approval.vue';
|
||||
import StartNode from './components/custom-nodes/StartNode.vue';
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { updateUserRole } from '@/api/setting';
|
||||
import { updateUserRole } from '@/api/role';
|
||||
import { UpdateUserRoleParam, RoleItem } from '@/types/setting';
|
||||
|
||||
const isLoading = ref<boolean>(false);
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
{
|
||||
"permission": [
|
||||
{
|
||||
"system_tenant_permission_id": 100,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 1,
|
||||
"id": 1,
|
||||
"parent_id": 0,
|
||||
"prem_name": "业务",
|
||||
"level": 1,
|
||||
"perm_key": "business",
|
||||
"prem_type": 1,
|
||||
"path": "/1/",
|
||||
"sort": 1,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 10:00:00",
|
||||
"updatedAt": "2024-06-20 10:00:00",
|
||||
"children": [
|
||||
{
|
||||
"system_tenant_permission_id": 101,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 2,
|
||||
"id": 2,
|
||||
"parent_id": 1,
|
||||
"prem_name": "项目管理",
|
||||
"level": 2,
|
||||
"perm_key": "project_manage",
|
||||
"prem_type": 1,
|
||||
"path": "/1/2/",
|
||||
"sort": 1,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 10:01:00",
|
||||
"updatedAt": "2024-06-20 10:01:00",
|
||||
"children": [
|
||||
{
|
||||
"system_tenant_permission_id": 102,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 3,
|
||||
"id": 3,
|
||||
"parent_id": 2,
|
||||
"prem_name": "查看项目",
|
||||
"level": 3,
|
||||
"perm_key": "view_project",
|
||||
"prem_type": 4,
|
||||
"path": "/1/2/3/",
|
||||
"sort": 1,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 10:02:00",
|
||||
"updatedAt": "2024-06-20 10:02:00",
|
||||
"children": [
|
||||
{
|
||||
"system_tenant_permission_id": 103,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 4,
|
||||
"id": 4,
|
||||
"parent_id": 3,
|
||||
"prem_name": "添加收入目标",
|
||||
"level": 4,
|
||||
"perm_key": "add_income_target",
|
||||
"prem_type": 2,
|
||||
"path": "/1/2/3/4/",
|
||||
"sort": 1,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 10:03:00",
|
||||
"updatedAt": "2024-06-20 10:03:00",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"system_tenant_permission_id": 104,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 5,
|
||||
"id": 5,
|
||||
"parent_id": 3,
|
||||
"prem_name": "编辑收入目标",
|
||||
"level": 4,
|
||||
"perm_key": "edit_income_target",
|
||||
"prem_type": 2,
|
||||
"path": "/1/2/3/5/",
|
||||
"sort": 2,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 10:04:00",
|
||||
"updatedAt": "2024-06-20 10:04:00",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"system_tenant_permission_id": 105,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 6,
|
||||
"id": 6,
|
||||
"parent_id": 2,
|
||||
"prem_name": "编辑项目",
|
||||
"level": 3,
|
||||
"perm_key": "edit_project",
|
||||
"prem_type": 4,
|
||||
"path": "/1/2/6/",
|
||||
"sort": 2,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 10:05:00",
|
||||
"updatedAt": "2024-06-20 10:05:00",
|
||||
"children": [
|
||||
{
|
||||
"system_tenant_permission_id": 106,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 7,
|
||||
"id": 7,
|
||||
"parent_id": 6,
|
||||
"prem_name": "删除收入目标",
|
||||
"level": 4,
|
||||
"perm_key": "delete_income_target",
|
||||
"prem_type": 2,
|
||||
"path": "/1/2/6/7/",
|
||||
"sort": 1,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 10:06:00",
|
||||
"updatedAt": "2024-06-20 10:06:00",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"system_tenant_permission_id": 107,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 8,
|
||||
"id": 8,
|
||||
"parent_id": 1,
|
||||
"prem_name": "合同管理",
|
||||
"level": 2,
|
||||
"perm_key": "contract_manage",
|
||||
"prem_type": 1,
|
||||
"path": "/1/8/",
|
||||
"sort": 2,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 10:07:00",
|
||||
"updatedAt": "2024-06-20 10:07:00",
|
||||
"children": [
|
||||
{
|
||||
"system_tenant_permission_id": 108,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 9,
|
||||
"id": 9,
|
||||
"parent_id": 8,
|
||||
"prem_name": "查看合同",
|
||||
"level": 3,
|
||||
"perm_key": "view_contract",
|
||||
"prem_type": 4,
|
||||
"path": "/1/8/9/",
|
||||
"sort": 1,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 10:08:00",
|
||||
"updatedAt": "2024-06-20 10:08:00",
|
||||
"children": [
|
||||
{
|
||||
"system_tenant_permission_id": 109,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 10,
|
||||
"id": 10,
|
||||
"parent_id": 9,
|
||||
"prem_name": "导出合同",
|
||||
"level": 4,
|
||||
"perm_key": "export_contract",
|
||||
"prem_type": 2,
|
||||
"path": "/1/8/9/10/",
|
||||
"sort": 1,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 10:09:00",
|
||||
"updatedAt": "2024-06-20 10:09:00",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"system_tenant_permission_id": 200,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 20,
|
||||
"id": 20,
|
||||
"parent_id": 0,
|
||||
"prem_name": "房源视图",
|
||||
"level": 1,
|
||||
"perm_key": "house_view",
|
||||
"prem_type": 1,
|
||||
"path": "/20/",
|
||||
"sort": 2,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 11:00:00",
|
||||
"updatedAt": "2024-06-20 11:00:00",
|
||||
"children": [
|
||||
{
|
||||
"system_tenant_permission_id": 201,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 21,
|
||||
"id": 21,
|
||||
"parent_id": 20,
|
||||
"prem_name": "新建房源",
|
||||
"level": 2,
|
||||
"perm_key": "add_house",
|
||||
"prem_type": 2,
|
||||
"path": "/20/21/",
|
||||
"sort": 1,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 11:01:00",
|
||||
"updatedAt": "2024-06-20 11:01:00",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"system_tenant_permission_id": 202,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 22,
|
||||
"id": 22,
|
||||
"parent_id": 20,
|
||||
"prem_name": "导入房源",
|
||||
"level": 2,
|
||||
"perm_key": "import_house",
|
||||
"prem_type": 3,
|
||||
"path": "/20/22/",
|
||||
"sort": 2,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 11:02:00",
|
||||
"updatedAt": "2024-06-20 11:02:00",
|
||||
"children": [
|
||||
{
|
||||
"system_tenant_permission_id": 203,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 23,
|
||||
"id": 23,
|
||||
"parent_id": 22,
|
||||
"prem_name": "批量导入",
|
||||
"level": 3,
|
||||
"perm_key": "batch_import",
|
||||
"prem_type": 3,
|
||||
"path": "/20/22/23/",
|
||||
"sort": 1,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 11:03:00",
|
||||
"updatedAt": "2024-06-20 11:03:00",
|
||||
"children": [
|
||||
{
|
||||
"system_tenant_permission_id": 204,
|
||||
"tenant_id": 1,
|
||||
"perm_id": 24,
|
||||
"id": 24,
|
||||
"parent_id": 23,
|
||||
"prem_name": "导入校验",
|
||||
"level": 4,
|
||||
"perm_key": "import_check",
|
||||
"prem_type": 2,
|
||||
"path": "/20/22/23/24/",
|
||||
"sort": 1,
|
||||
"visible": 1,
|
||||
"createdAt": "2024-06-20 11:04:00",
|
||||
"updatedAt": "2024-06-20 11:04:00",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { getRolePermission } from '@/api/setting';
|
||||
import { getRolePermission } from '@/api/role';
|
||||
import PermissionNode from './permission-node.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { updateRole, createRole } from '@/api/setting';
|
||||
import { updateRole, createRole } from '@/api/role';
|
||||
import { RoleFormParam, RoleItem } from '@/types/setting';
|
||||
|
||||
const isLoading = ref<boolean>(false);
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
import { ref, watch, reactive } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import { createUser, updateUser } from '@/api/setting';
|
||||
import { createUser, updateUser } from '@/api/user';
|
||||
import { DepartmentItem, RoleItem, FormUserItem } from '@/types/setting';
|
||||
|
||||
const isLoading = ref<boolean>(false);
|
||||
|
||||
@@ -119,13 +119,8 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import type { TableColumnData } from '@arco-design/web-vue';
|
||||
import {
|
||||
getUserList,
|
||||
getRoleList,
|
||||
getUserInfo,
|
||||
disableUser,
|
||||
delUser,
|
||||
} from '@/api/setting';
|
||||
import { getUserList, getUserById, disableUser, delUser } from '@/api/user';
|
||||
import { getRoleList } from '@/api/role';
|
||||
import { UserList, RoleItem, FormUserItem } from '@/types/setting';
|
||||
import { UserStatus } from '@/types/comm';
|
||||
import useDepartmentList from '@/composables/useDepartmentList';
|
||||
@@ -310,7 +305,7 @@
|
||||
};
|
||||
const getUserInfoData = async (id: number) => {
|
||||
try {
|
||||
const { code, data } = await getUserInfo(id);
|
||||
const { code, data } = await getUserById(id);
|
||||
if (code === 200) userInfo.value = data?.user;
|
||||
} catch (error) {
|
||||
// 处理错误
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user