Commit d9e89abe by lixinming

Merge branch 'master' of http://123.207.147.179:8888/node_server/zhangjiangzhenxcxserver

# Conflicts:
#	res/标记企业12_10.xlsx
#	src/config/enum.ts
#	src/config/errorEnum.ts
#	src/db/mongo/tableInit.ts
#	src/routers/public.ts
parents 8f823bf1 c1530a0a
......@@ -405,7 +405,7 @@ export async function enterpriseBusinessList(selectStr:string, industry:number,
/**
* 导出经营数据(支持动态列配置)todo
* 导出经营数据(支持动态列配置)
* @param param0
* @returns
*/
......
......@@ -408,4 +408,18 @@ export enum SIXLIST {
export enum YONGFANGTYPE {
自有用房 = 1,
租赁用房
}
\ No newline at end of file
}
export enum DELIVERYSTANDARD {
简装 = 1,
精装,
}
......@@ -18,7 +18,8 @@ export enum ERRORENUM {
政策不存在,
政策落实不存在,
本年度数据已存在,
智能体调用出错请联系管理员
智能体调用出错请联系管理员,
空间不存在,
}
......
......@@ -339,3 +339,32 @@ export const AdminUpdateOverallOperationConfig = {
}
/**
* 添加 空间
*/
export const AddSpaceConfig = {
name:{type:"String"}, //物业名称
address:{type:"String"}, // 地址
totalArea:{type:"Number"}, // 总面积(平方米)
leasingRate:{type:"Number"}, // 出租率
vacantAreas:{type:"[]"}, // 空置区域
supportPolicies:{type:"[]"}, // 支持政策
description:{type:"String"}, // 简介或项目描述
spaceType:{type:"String"}, // 空间属性
environmentalAssessment:{type:"String"}, // 环评
deliveryStandard:{type:"Number"}, // 交付标准
deliveryRemark:{type:"String", notMustHave:true}, // 交付备注说明
floorHeight:{type:"String"}, // 层高(m)
floorLoad:{type:"String"}, // 承重(KN/m²)
standardFloorArea:{type:"String"}, // 标准层面积(m²)
minLeasableArea:{type:"String"}, // 最小可租面积(m²)
powerSupply:{type:"String"}, // 电量(w/㎡)
parkingSpaces:{type:"String"}, // 停车位数量
parkingFee:{type:"String"}, // 停车费
deliveryTime:{type:"Number", notMustHave:true}, // 交付时间 时间戳
contactPerson:{type:"String", notMustHave:true}, // 项目联系人
surroundingFacilities:{type:"String", notMustHave:true}, // 周边配套
attachments:{type:"[]"}, // 附件信息 JSON格式(如:["1.jpg", "2.jpg"])
remark:{type:"String", notMustHave:true}, // 备注
}
/**
* 空间管理
*/
import {Schema} from 'mongoose';
import { baseDB } from '../db/mongo/dbInit';
/**
* 空置区域
*/
// const vacantAreaSchema = new Schema({
// vaId:String,
// area:Number, // 面积(平方米)(如:183.6m²)
// floor:Number, //楼层(如:4楼)
// },{_id:false});
/**
* 支持政策
*/
// const supportPoliciesSchema = new Schema({
// spId:String,
// name:String, // 对应政策
// remark:String, //备注说明
// },{_id:false});
const spaceSchema = new Schema({
// 基础信息
spaceId: {type:String, index:true}, // 空间唯一ID
name: String, // 物业名称
address: String, // 地址
totalArea: Number, // 总面积(平方米)
leasingRate: Number, // 出租率(0-100之间的数字)%
vacantAreas: {type:[String], default:[]}, // 空置区域 JSON格式(如:["183.6m²4楼", ""])
// vacantAreas:{type:[vacantAreaSchema], default:[]},//空置区域
supportPolicies: {type:[String], default:[]}, // 支持政策 JSON格式(如:["孵化器补贴", "青创15条"])
// supportPolicies:{type:[supportPoliciesSchema], default:[]},//支持政策
description: String, // 简介或项目描述
// 规格信息(来自第二张表)
spaceType: String, // 空间属性
environmentalAssessment: String, // 环评
deliveryStandard: Number, // 交付标准 DELIVERYSTANDARD
deliveryRemark: String, //交付备注说明
floorHeight: String, // 层高(m)
floorLoad: String, // 承重(KN/m²)
standardFloorArea: String, // 标准层面积(m²)可能是区间范围(如:1100-2200㎡)
minLeasableArea: String, // 最小可租面积(m²)
powerSupply: String, // 电量(w/㎡)
parkingSpaces: String, // 停车位数量
parkingFee: String, // 停车费 元/个
deliveryTime: Number, // 交付时间 时间戳
contactPerson: String, // 项目联系人
surroundingFacilities: String, // 周边配套
attachments: {type:[String], default:[]}, // 附件信息 JSON格式(如:["1.jpg", "2.jpg"])
remark: String, // 备注
// 系统字段
createdAt: Number,
updatedAt: Number
});
var spaceModel: any;
export function initModel(){
spaceModel = baseDB.model('space', spaceSchema);
// 扩展模型方法:选择单个数据并绑定save方法
spaceModel.selectOnceData = async function (parameter: object) {
let selectInfo = await spaceModel.findOne(parameter).exec();
if (selectInfo) {
if (!selectInfo.runSave) {
selectInfo.runSave = selectInfo.save;
selectInfo.save = save.bind(selectInfo)
}
}
return selectInfo;
}
return spaceModel;
}
// 自定义保存方法
export async function save(throwError=false) {
if (!this.isModified()) return;
this.updatedAt = Date.now();
await this.runSave({validateBeforeSave:false}).catch(err=>{
console.log(err);
if (throwError) throw err;
});
}
// ============ CRUD 操作方法 ============
/**
* 查询空间列表
* @param param 查询条件
*/
export async function findSpaceList(param: object) {
return await spaceModel.find(param);
}
/**
* 分页查询空间列表
* @param param 查询条件
* @param skipCount 页码
* @param pageSize 每页数量,默认10
*/
export async function findSpaceListPageByParam(param, skipCount) {
return await spaceModel.find(param).skip((skipCount-1)*10).limit(10);
}
/**
* 查询空间数量
* @param param 查询条件
*/
export async function findSpaceCount(param: object) {
return await spaceModel.countDocuments(param);
}
/**
* 查询单个空间详情
* @param param 查询条件
*/
export async function findOneSpace(param: object) {
return await spaceModel.selectOnceData(param);
}
/**
* 创建空间
* @param param 空间数据
*/
export async function addSpace(param: object) {
return await spaceModel.create(param);
}
/**
* 删除单个空间
* @param param 删除条件
*/
export async function deleteOneSpace(param: object) {
return await spaceModel.deleteOne(param);
}
/**
* 更新空间信息
* @param filter 查询条件
* @param updateData 更新数据
*/
export async function updateSpace(filter: object, updateData: object) {
return await spaceModel.updateOne(filter, updateData);
}
/**
* 批量更新空间
* @param filter 查询条件
* @param updateData 更新数据
*/
export async function updateManySpaces(filter: object, updateData: object) {
const data = {
...updateData,
updatedAt: Date.now()
};
return await spaceModel.updateMany(filter, data).exec();
}
/**
* 根据ID查询空间
* @param spaceId 空间ID
*/
export async function findSpaceById(spaceId: string) {
return await spaceModel.selectOnceData({ spaceId });
}
/**
* 根据名称模糊查询空间
* @param name 空间名称关键词
*/
export async function findSpaceByName(name: string) {
const regex = new RegExp(name, 'i'); // 不区分大小写
return await spaceModel.find({ name: regex }).exec();
}
/**
* 查询有空置区域的空间
*/
export async function findVacantSpaces() {
return await spaceModel.find({
$or: [
{ vacantAreas: { $exists: true, $ne: [] } },
{ leasingRate: { $lt: 100 } }
]
}).exec();
}
/**
* 根据出租率范围查询空间
* @param minRate 最小出租率
* @param maxRate 最大出租率
*/
export async function findSpaceByLeasingRate(minRate: number, maxRate: number) {
return await spaceModel.find({
leasingRate: { $gte: minRate, $lte: maxRate }
}).exec();
}
......@@ -12,7 +12,8 @@ import * as userModel from "../../data/user";
import * as dynamicModel from "../../data/service/dynamic";
import * as appealsModel from "../../data/service/appeals";
import * as visitPlanModel from "../../data/service/visitPlan";
import * as overallOperationModel from "../../data/overallOperation"
import * as overallOperationModel from "../../data/overallOperation";
import * as spaceModel from "../../data/space";
import * as yingShouModel from "../../data/enterprise/yingShou"
import * as zdzsfwModel from "../../data/enterprise/zdzsfw"
......@@ -37,4 +38,5 @@ export async function initTable() {
overallOperationModel.initModel();
yingShouModel.initModel();
zdzsfwModel.initModel();
spaceModel.initModel();
}
\ No newline at end of file
/**
* 空间管理
*/
import asyncHandler = require('express-async-handler');
import { eccReqParamater } from '../../util/verificationParam';
import * as spaceBiz from '../../biz/space';
import { checkAdminToken, checkToken } from '../../middleware/user';
export function setRouter(httpServer) {
httpServer.post('/zj/admin/service/space/list', checkAdminToken, asyncHandler(space_list));//空间管理-列表
httpServer.post('/zj/admin/service/space/add', checkAdminToken, asyncHandler(space_add));//空间管理-新增
httpServer.post('/zj/admin/service/space/info', checkAdminToken, asyncHandler(space_info));//空间管理-回显
httpServer.post('/zj/admin/service/space/update', checkAdminToken, asyncHandler(space_update));//空间管理-修改
httpServer.post('/zj/admin/service/space/del', checkAdminToken, asyncHandler(space_del));//空间管理-删除
}
async function space_list(req, res) {
let reqConf = {name:'String', page:'Number' };
const NotMustHaveKeys = ["name"];
let { name, page } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
let result = await spaceBiz.spaceList(name, page);
res.success(result);
}
async function space_add(req, res) {
let reqConf = {param:'Object'};
let { param } = eccReqParamater(reqConf, req.body);
let result = await spaceBiz.addSpace(param);
res.success(result);
}
async function space_info(req, res) {
let reqConf = {spaceId:'String'};
let { spaceId } = eccReqParamater(reqConf, req.body);
let result = await spaceBiz.getSpaceInfo(spaceId);
res.success(result);
}
async function space_update(req, res) {
let reqConf = { spaceId:"String", param:"Object" };
const NotMustHaveKeys = [""];
let { spaceId, param } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
let data = await spaceBiz.spaceUpdate(spaceId, param);
res.success(data);
}
async function space_del(req, res) {
let reqConf = {spaceId:'String'};
let { spaceId } = eccReqParamater(reqConf, req.body);
let result = await spaceBiz.spaceDel(spaceId);
res.success(result);
}
/**
* 空间管理
*/
import asyncHandler = require('express-async-handler');
import { eccReqParamater } from '../../util/verificationParam';
import * as spaceBiz from '../../biz/space';
import { checkToken } from '../../middleware/user';
export function setRouter(httpServer) {
httpServer.post('/zj/xcx/enterprisemanage/space/list', checkToken, asyncHandler(space_list));//空间管理-列表
httpServer.post('/zj/xcx/enterprisemanage/space/info', checkToken, asyncHandler(space_info));//空间管理-回显
httpServer.post('/zj/xcx/enterprisemanage/space/updaterate', checkToken, asyncHandler(space_updateLeasingRate));//空间管理-修改出租率
}
async function space_list(req, res) {
let reqConf = {name:'String' };
const NotMustHaveKeys = ["name"];
let { name } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
let result = await spaceBiz.spaceXCXList(name);
res.success(result);
}
async function space_info(req, res) {
let reqConf = {spaceId:'String'};
let { spaceId } = eccReqParamater(reqConf, req.body);
let result = await spaceBiz.getSpaceXCXInfo(spaceId);
res.success(result);
}
async function space_updateLeasingRate(req, res) {
let reqConf = { spaceId:"String", leasingRate:"Number" };
const NotMustHaveKeys = [""];
let { spaceId, leasingRate } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
let data = await spaceBiz.spaceUpdateLeasingRate(spaceId, leasingRate);
res.success(data);
}
......@@ -41,6 +41,7 @@ const config = {
"/zj/admin/public/implementclient":enumConfig.IMPLEMENTCLIENT,
"/zj/admin/public/sixlist":enumConfig.SIXLIST,//六清单类型
"/zj/admin/public/deliverystandard":enumConfig.DELIVERYSTANDARD,
/**导出数据列 */
"/zj/admin/public/outputcolumns/enterprise":outputConfig.ENTERPRISECOLUMNS,
......
......@@ -12,6 +12,8 @@ import * as serviceRouters from './enterpriseButler/service';
import * as userRouters from './enterpriseButler/user';
import * as adminUserRouters from './admin/user';
import * as adminRouters from './admin/admin';
import * as spaceRouters from './admin/space';
import * as xcxspaceRouters from './enterpriseButler/space';
export function setRouter(httpServer){
......@@ -27,4 +29,8 @@ export function setRouter(httpServer){
userRouters.setRouter(httpServer);
adminUserRouters.setRouter(httpServer);
adminRouters.setRouter(httpServer);
adminRouters.setRouter(httpServer);
adminRouters.setRouter(httpServer);
spaceRouters.setRouter(httpServer);
xcxspaceRouters.setRouter(httpServer);
}
\ No newline at end of file
......@@ -203,4 +203,188 @@ export function changeAddressListToStr(addressList) {
str += item
});
return str;
}
\ No newline at end of file
}
/**
* 安全解析 JSON 或处理 Mongoose 数组并转换为对象数组
* @param fileInput - 可以是 JSON 字符串、数组或 Mongoose 数组
* @returns
*/
export function convertFilesToObjectArray(fileInput: any) {
const defaultResult: Array<{ name: string; url: string }> = [];
// 如果值为 null、undefined 或空字符串,返回空数组
if (fileInput === null || fileInput === undefined || fileInput === '' || fileInput === '""') {
return defaultResult;
}
let fileUrls: string[];
try {
// 情况1:已经是数组
if (Array.isArray(fileInput)) {
fileUrls = fileInput;
}
// 情况2:是 Mongoose 数组(CoreMongooseArray)
else if (fileInput && typeof fileInput === 'object' && fileInput.toObject) {
// 将 Mongoose 数组转换为普通数组
fileUrls = fileInput.toObject();
}
// 情况3:是字符串(JSON)
else if (typeof fileInput === 'string') {
// 直接尝试解析 JSON
const parsed = JSON.parse(fileInput);
// 检查解析后是否为数组
if (!Array.isArray(parsed)) {
console.warn('解析后的 JSON 不是数组:', parsed);
return defaultResult;
}
fileUrls = parsed;
}
// 情况4:其他类型,尝试直接使用
else {
console.warn('不支持的文件输入类型:', typeof fileInput, fileInput);
return defaultResult;
}
// 再次确保是数组
if (!Array.isArray(fileUrls)) {
console.warn('最终结果不是数组:', fileUrls);
return defaultResult;
}
// 过滤掉 null/undefined/空字符串
const validUrls = fileUrls.filter(url =>
url !== null &&
url !== undefined &&
url !== '' &&
typeof url === 'string'
);
// 转换为对象数组
return validUrls.map((url, index) => {
const urlParts = url.split('/');
const originalFileName = urlParts[urlParts.length - 1];
// 提取文件名(根据您的示例数据格式)
let fileName = originalFileName;
// 提取扩展名
const extensionMatch = originalFileName.match(/\.\w+$/);
const extension = extensionMatch ? extensionMatch[0] : '';
// 如果文件名没有扩展名,直接返回
if (!extension) {
return {
name: originalFileName,
url: url
};
}
// 获取不带扩展名的文件名
const nameWithoutExt = fileName.slice(0, -extension.length);
// 针对你的具体格式进行智能处理
// 例如:三方协议2025.8.1820250902_163174.pdf
// 应该变成:三方协议2025.8.18.pdf
let cleanedName = nameWithoutExt;
// 方法1:智能识别并移除时间戳部分
// 查找类似 20250902_163174 这样的时间戳模式
const timestampPatterns = [
/\d{8}_\d{5,6}$/, // YYYYMMDD_HHMMSS 或 YYYYMMDD_HHMM
/\d{14}$/, // YYYYMMDDHHMMSS
/\d{12}$/, // YYYYMMDDHHMM
/\d{10}$/, // YYYYMMDDHH
/\d{8}$/, // YYYYMMDD(在末尾时)
];
// 检查文件名中是否有版本号+时间戳的组合
// 例如:2025.8.18 + 20250902_163174
for (const pattern of timestampPatterns) {
if (pattern.test(cleanedName)) {
// 找到时间戳的起始位置
const timestampMatch = cleanedName.match(pattern);
if (timestampMatch) {
const timestamp = timestampMatch[0];
const timestampIndex = cleanedName.lastIndexOf(timestamp);
// 检查时间戳前是否有重复的日期部分
const beforeTimestamp = cleanedName.substring(0, timestampIndex);
// 如果时间戳前有类似格式的内容,可能是重复的,移除时间戳
if (beforeTimestamp.endsWith('2025.8.18') ||
beforeTimestamp.endsWith('2025.8')) {
cleanedName = beforeTimestamp;
break;
}
}
}
}
// 方法2:处理特定的重复模式
// 对于 "2025.8.1820250902_163174" 这样的模式
const yearMonthDayRegex = /(20\d{2}\.\d{1,2}\.\d{1,2})\1?/;
if (yearMonthDayRegex.test(cleanedName)) {
// 找到第一个日期模式并保留它
const dateMatch = cleanedName.match(/(20\d{2}\.\d{1,2}\.\d{1,2})/);
if (dateMatch) {
const datePart = dateMatch[1];
// 提取日期后的所有内容
const afterDate = cleanedName.substring(dateMatch.index + datePart.length);
// 如果日期后跟着数字(可能是时间戳),只保留日期部分
if (/^\d{6,}/.test(afterDate)) {
cleanedName = cleanedName.substring(0, dateMatch.index + datePart.length);
}
}
}
// 方法3:简单的字符串处理(针对你的具体案例)
if (cleanedName.includes('2025.8.18')) {
// 找到 "2025.8.18" 的位置
const startIndex = cleanedName.indexOf('2025.8.18');
if (startIndex !== -1) {
// 只保留第一个 "2025.8.18" 和之前的内容
cleanedName = cleanedName.substring(0, startIndex + '2025.8.18'.length);
}
}
// 清理多余的分隔符,但保留点号作为版本号的一部分
// 只清理末尾可能多余的符号
cleanedName = cleanedName.replace(/[._\-]+$/g, '');
// 确保文件名不为空
if (!cleanedName) {
cleanedName = `文件${index + 1}`;
}
// 重新组合文件名和扩展名
const finalFileName = cleanedName + extension;
return {
name: finalFileName,
url: url
};
});
} catch (error) {
console.warn('文件转换失败:', {
input: fileInput,
error: error.message,
stack: error.stack
});
return defaultResult;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment