You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3820 lines
183 KiB
3820 lines
183 KiB
'use strict';
|
|
const superagent = require('superagent');
|
|
const XLSXS = require('xlsx-js-style');
|
|
const moment = require('moment');
|
|
const { markdownTableToArray } = require('../utils/mdUtils');
|
|
const { bridgePrompts, tunnelPrompts, slopePrompts } = require('../static/factorPrompts');
|
|
|
|
module.exports.getSchemeList = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const { page, pageSize, userId } = ctx.request.query;
|
|
const where = {};
|
|
if (userId) {
|
|
where.userId = userId;
|
|
}
|
|
if (page && pageSize) {
|
|
where.offset = (page - 1) * pageSize;
|
|
where.limit = parseInt(pageSize);
|
|
}
|
|
const schemeList = await models.SchemeList.findAndCountAll({
|
|
attributes: ['id', 'name', 'userText', 'status', 'createAt', 'updateAt', 'structureType', 'transmitMethod'],
|
|
where,
|
|
order: [['updateAt', 'DESC']],
|
|
raw: true,
|
|
});
|
|
ctx.body = schemeList;
|
|
ctx.status = 200;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '获取方案清单失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports.addScheme = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const { name, userText, userId, structureType, transmitMethod, curIp } = ctx.request.body;
|
|
|
|
if (!name || !userText || !structureType || !transmitMethod) { throw '缺少参数' };
|
|
|
|
// 检查是否缺少必要数据
|
|
if (Object.keys(userText).length === 0) {
|
|
throw '至少选择一个监测项';
|
|
}
|
|
|
|
// 新增数据,状态pending
|
|
const scheme = await models.SchemeList.create(
|
|
{ name, userText, status: 'pending', userId, structureType, transmitMethod },
|
|
{ returning: true }
|
|
);
|
|
|
|
await models.AiQueryRecord.create({
|
|
userId: userId,
|
|
ipAddress: curIp,
|
|
clientId: 'pep-feixiaoshang',
|
|
feature: '方案清单生成',
|
|
time: moment(),
|
|
});
|
|
|
|
ctx.body = scheme;
|
|
ctx.status = 200;
|
|
|
|
// 后台执行方案内容生成,不影响此接口返回
|
|
generationScheme(ctx, scheme.id, userText, structureType, transmitMethod);
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '新增方案失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports.retryGenerationScheme = async (ctx, next) => {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const id = ctx.params.schemeId;
|
|
if (!id) { throw '缺少参数' };
|
|
|
|
const scheme = await models.SchemeList.update(
|
|
{ status: 'pending' },
|
|
{
|
|
where: { id },
|
|
returning: true,
|
|
transaction
|
|
}
|
|
);
|
|
|
|
const { userText, structureType } = scheme[1][0].dataValues;
|
|
|
|
ctx.body = scheme;
|
|
ctx.status = 200;
|
|
|
|
transaction.commit();
|
|
|
|
generationScheme(ctx, id, userText, structureType);
|
|
} catch (error) {
|
|
transaction.rollback();
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '重新生成方案失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
// 调用AI生成方案 ---> 硬代码生成方案
|
|
async function generationScheme(ctx, schemeId, userText, structureType, transmitMethod) {
|
|
switch (structureType) {
|
|
case '桥梁':
|
|
generationBridgeScheme(ctx, schemeId, userText, structureType, transmitMethod);
|
|
break;
|
|
case '隧道':
|
|
generationTunnelScheme(ctx, schemeId, userText, structureType, transmitMethod);
|
|
break;
|
|
case '边坡':
|
|
generationSlopeScheme(ctx, schemeId, userText, structureType, transmitMethod);
|
|
break;
|
|
default:
|
|
console.error(`未知结构类型:${structureType}`);
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
await models.SchemeList.update(
|
|
{ status: 'fail' },
|
|
{ where: { id: schemeId } }
|
|
);
|
|
} catch (err) {
|
|
console.log(`修改状态失败,scheme.id=${schemeId}`);
|
|
ctx.logger.log(err);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 生成桥梁方案清单
|
|
async function generationBridgeScheme(ctx, schemeId, userText, structureType, transmitMethod) {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
const { models } = ctx.app.fs.dc;
|
|
try {
|
|
// const { schemeAppKey, apiUrl } = ctx.app.fs.config.fastGpt;
|
|
const { apiUrl } = ctx.app.fs.config.fastGpt;
|
|
const schemeAppKey = 'fastgpt-faESFm8XQk7ffnhtEUewoVoXIo16xCfmRrkToru6g2A86zEZPy22LSC8a4sg'
|
|
const regionAppKey = 'fastgpt-kzqnfXUusbIP6R42we1KMAST9SqBAPCtbAxTV1oQHWRuM7CEb1am2VF7z0'
|
|
|
|
/********** 1.请求数据 **********/
|
|
const allRequestKey = Object.keys(userText);
|
|
const request1Key = allRequestKey.filter(key => !['机箱配套', '通信系统', '电力系统(太阳能)'].includes(key));
|
|
|
|
async function readManifestFromFile(fileUrl) {
|
|
try {
|
|
const res = await superagent
|
|
.get(fileUrl)
|
|
.buffer(true)
|
|
.parse((response, cb) => {
|
|
const data = [];
|
|
response.on('data', (chunk) => {
|
|
data.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
});
|
|
response.on('end', () => cb(null, Buffer.concat(data)));
|
|
response.on('error', (err) => cb(err));
|
|
});
|
|
const buf = res.body;
|
|
const wb = XLSXS.read(buf, { type: 'buffer' });
|
|
const sheetNames = wb.SheetNames || [];
|
|
if (!sheetNames.length) return [];
|
|
const all = [];
|
|
for (let s = 0; s < sheetNames.length; s++) {
|
|
const ws = wb.Sheets[sheetNames[s]];
|
|
if (!ws) continue;
|
|
const rows = XLSXS.utils.sheet_to_json(ws, { header: 1, blankrows: false, defval: '' }) || [];
|
|
let nameIdx = -1, modelIdx = -1, countIdx = -1, startIdx = -1, typeIdx = -1, unitIdx = -1, codeIdx = -1, priceIdx = -1, lineLenIdx = -1;
|
|
const headerMaxScan = Math.min(rows.length, 20);
|
|
for (let i = 0; i < headerMaxScan; i++) {
|
|
const row = rows[i] || [];
|
|
for (let j = 0; j < row.length; j++) {
|
|
const cell = String(row[j] || '').replace(/\s+/g, '');
|
|
if (nameIdx < 0 && (cell === '名称' || cell.indexOf('设备名称') > -1 || cell.indexOf('物料名称') > -1 || cell.indexOf('品名') > -1)) nameIdx = j;
|
|
if (modelIdx < 0 && (cell === '规格/型号' || cell.indexOf('规格') > -1 || cell.indexOf('型号') > -1)) modelIdx = j;
|
|
if (countIdx < 0 && (cell === '数量' || cell.indexOf('数量') > -1 || cell.indexOf('总数') > -1)) countIdx = j;
|
|
if (typeIdx < 0 && (cell === '类型' || cell.indexOf('类型') > -1)) typeIdx = j;
|
|
if (unitIdx < 0 && (cell === '单位' || cell.indexOf('单位') > -1)) unitIdx = j;
|
|
if (codeIdx < 0 && (cell === '物料代码' || cell.indexOf('物料编码') > -1 || cell.indexOf('物料代码') > -1)) codeIdx = j;
|
|
if (priceIdx < 0 && (cell === '单价' || cell.indexOf('单价') > -1 || cell.indexOf('价格') > -1)) priceIdx = j;
|
|
if (lineLenIdx < 0 && (cell.indexOf('线长') > -1)) lineLenIdx = j;
|
|
}
|
|
if (nameIdx >= 0 && countIdx >= 0) { startIdx = i + 1; break; }
|
|
}
|
|
if (startIdx < 0) continue;
|
|
for (let i = startIdx; i < rows.length; i++) {
|
|
const row = rows[i] || [];
|
|
const nameCell = row[nameIdx];
|
|
const rawName = nameCell == null ? '' : String(nameCell);
|
|
const nameStr = rawName.trim();
|
|
if (!nameStr) continue;
|
|
const lower = nameStr.toLowerCase();
|
|
if (nameStr.indexOf('小计') > -1 || nameStr.indexOf('合计') > -1 || lower.indexOf('total') > -1) break;
|
|
const modelStr = modelIdx >= 0 ? String(row[modelIdx] || '').trim() : '';
|
|
const countVal = row[countIdx];
|
|
const countStr = String(countVal == null ? '' : countVal).trim();
|
|
const match = countStr.match(/-?\d+(\.\d+)?/);
|
|
const countNum = match ? Number(match[0]) : Number(countStr || 0);
|
|
if (!isNaN(countNum) && countNum > 0) {
|
|
const typeStr = typeIdx >= 0 ? String(row[typeIdx] || '').trim() : '';
|
|
const unitStr = unitIdx >= 0 ? String(row[unitIdx] || '').trim() : '';
|
|
const codeStr = codeIdx >= 0 ? String(row[codeIdx] || '').trim() : '';
|
|
const priceVal = priceIdx >= 0 ? String(row[priceIdx] || '').trim() : '';
|
|
const lineLenVal = lineLenIdx >= 0 ? String(row[lineLenIdx] || '').trim() : '';
|
|
all.push({ name: nameStr, model: modelStr, count: countNum, type: typeStr, unit: unitStr, code: codeStr, price: priceVal ? Number(priceVal) : undefined, lineLength: lineLenVal });
|
|
}
|
|
}
|
|
}
|
|
return all;
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
function mergeManifest(primary, fallback) {
|
|
const map = new Map();
|
|
const normKey = (n, m) => `${String(n || '').trim().toLowerCase()}|${String(m || '').trim().toLowerCase()}`;
|
|
for (const it of primary || []) {
|
|
map.set(normKey(it.name, it.model), { name: String(it.name || '').trim(), model: String(it.model || '').trim(), count: Number(it.count || 0), type: it.type, unit: it.unit, code: it.code, price: it.price, lineLength: it.lineLength });
|
|
}
|
|
for (const it of fallback || []) {
|
|
const k = normKey(it.name, it.model);
|
|
if (map.has(k)) {
|
|
const v = map.get(k);
|
|
if (!v.type && it.type) v.type = it.type;
|
|
if (!v.unit && it.unit) v.unit = it.unit;
|
|
if (!v.code && it.code) v.code = it.code;
|
|
if (v.price == null && it.price != null) v.price = it.price;
|
|
if (v.lineLength == null && it.lineLength != null) v.lineLength = it.lineLength;
|
|
map.set(k, v);
|
|
}
|
|
}
|
|
return Array.from(map.values());
|
|
}
|
|
const request1Data = await Promise.all(request1Key.map(async key => {
|
|
try {
|
|
const expectSolarParamKeys = new Set([
|
|
'桥墩/桥塔位移监测(北斗)',
|
|
'视频监控(有线)',
|
|
'视频监控(无线1)',
|
|
'机箱配套',
|
|
]);
|
|
|
|
const isSolarSelected = !!userText['电力系统(太阳能)'];
|
|
const params = [].concat(
|
|
bridgePrompts[key].sensors.map(sensor => {
|
|
const v = userText[key]?.[sensor];
|
|
if (v === undefined && /数量|长度|桥长|车道|天数|弦数/.test(sensor)) return 0;
|
|
return v;
|
|
}),
|
|
[expectSolarParamKeys.has(key) ? isSolarSelected : transmitMethod]
|
|
);
|
|
let data = bridgePrompts[key].getManifest(params) || [];
|
|
data = data.map(it => ({ ...it, name: String(it.name || '').trim(), model: String(it.model || '').trim() }));
|
|
const fileUrl = bridgePrompts[key]?.file;
|
|
if (fileUrl) {
|
|
const fileData = await readManifestFromFile(fileUrl);
|
|
data = mergeManifest(data, fileData);
|
|
}
|
|
return { factor: key, data: data || [] };
|
|
} catch (error) {
|
|
console.error(`Calculation failed for ${key}`, error);
|
|
return { factor: key, data: [] };
|
|
}
|
|
}));
|
|
const groundBlacklist = new Set(['接地桩(镀锌角铁)', '镀锌扁铁', '降阻剂']);
|
|
for (let i = 0; i < request1Data.length; i++) {
|
|
const it = request1Data[i];
|
|
it.data = (it.data || []).filter(x => !groundBlacklist.has(String(x?.name || '')));
|
|
}
|
|
const opsSelected = String(userText['运维服务']?.['选择子服务'] || '').split('、').map(x => x.trim()).filter(Boolean);
|
|
for (let i = 0; i < request1Data.length; i++) {
|
|
const it = request1Data[i];
|
|
if (it.factor === '运维服务' && Array.isArray(it.data)) {
|
|
it.data = opsSelected.length > 0 ? it.data.filter(row => opsSelected.includes(String(row?.name || ''))) : [];
|
|
}
|
|
}
|
|
|
|
// 机箱配套
|
|
let request2Data = {
|
|
factor: "机箱配套",
|
|
data: []
|
|
};
|
|
let otherInstrumentCount = 0; // 其他模块采集仪总数
|
|
let vibratingWireSensorCount = 0; // 振弦信号传感器总数
|
|
let signal485SensorCount = 0; // 485信号传感器总数
|
|
for (const item of request1Data) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
if (sensor.name.includes("采集仪")) {
|
|
otherInstrumentCount += Number(sensor.count || 0);
|
|
}
|
|
if (["表面式应变计", "内埋式应变计", "温度传感器"].includes(sensor.name)) {
|
|
vibratingWireSensorCount += Number(sensor.count || 0);
|
|
}
|
|
if (["储液罐(压差水箱)", "裂缝计", "直线位移传感器", "拉线位移传感器", "盒式固定测斜仪", "温湿度传感器", "超声波风速风向仪"].includes(sensor.name)) {
|
|
signal485SensorCount += Number(sensor.count || 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (userText['机箱配套']) {
|
|
try {
|
|
const isSolar = true;
|
|
const vw = Number(vibratingWireSensorCount || 0);
|
|
const explicitOrFallbackCount = (factorKey, itemName, model, fallback) => {
|
|
const direct = Number(userText[factorKey]?.[itemName]);
|
|
if (!isNaN(direct) && direct > 0) return direct;
|
|
const modelKey = `${itemName}|${model || ''}`;
|
|
const byModel = Number(userText[factorKey]?.[modelKey] || userText[factorKey]?.[model]);
|
|
if (!isNaN(byModel) && byModel > 0) return byModel;
|
|
return Number(fallback || 0);
|
|
};
|
|
let items = [];
|
|
let hubs = [];
|
|
if (vw > 0) {
|
|
let chosenName = '';
|
|
let chosenModel = '';
|
|
let chosenSystemName = '';
|
|
let chosenSystemModel = '';
|
|
let chosenCount = 0;
|
|
if (vw <= 2) {
|
|
chosenName = '单通道振弦采集模块';
|
|
chosenModel = 'FS-FD01';
|
|
chosenSystemName = '单通道振弦采集系统V1.0';
|
|
chosenSystemModel = 'FS-FD01-内置软件';
|
|
chosenCount = vw;
|
|
} else if (vw <= 32) {
|
|
chosenName = '多通道振弦采集仪';
|
|
chosenModel = 'FS-F08';
|
|
chosenSystemName = '多通道振弦采集仪原位监测系统V1.0';
|
|
chosenSystemModel = 'FS-F08-内置软件';
|
|
chosenCount = Math.ceil(vw / 8);
|
|
} else if (vw <= 64) {
|
|
chosenName = '多通道振弦采集仪';
|
|
chosenModel = 'FS-F16';
|
|
chosenSystemName = '多通道振弦采集仪原位监测系统V1.0';
|
|
chosenSystemModel = 'FS-F16-内置软件';
|
|
chosenCount = Math.ceil(vw / 16);
|
|
} else {
|
|
chosenName = '多通道振弦采集仪';
|
|
chosenModel = 'FS-F32';
|
|
chosenSystemName = '多通道振弦采集仪原位监测系统V1.0';
|
|
chosenSystemModel = 'FS-F32-内置软件';
|
|
chosenCount = Math.ceil(vw / 32);
|
|
}
|
|
const cMain = explicitOrFallbackCount('机箱配套', chosenName, chosenModel, chosenCount);
|
|
if (cMain > 0) {
|
|
items.push({ name: chosenName, model: chosenModel, count: cMain });
|
|
}
|
|
const cSys = explicitOrFallbackCount('机箱配套', chosenSystemName, chosenSystemModel, cMain);
|
|
if (cSys > 0) {
|
|
items.push({ name: chosenSystemName, model: chosenSystemModel, count: cSys });
|
|
}
|
|
const s485 = Number(signal485SensorCount || 0);
|
|
if (s485 > 0) {
|
|
if (s485 <= 8) {
|
|
const c4 = Math.ceil(s485 / 4);
|
|
const hc = explicitOrFallbackCount('机箱配套', 'RS485集线器', 'FS-485JXQ-04-A', c4);
|
|
if (hc > 0) hubs.push({ name: 'RS485集线器', model: 'FS-485JXQ-04-A', count: hc });
|
|
} else {
|
|
const c8 = Math.floor(s485 / 8);
|
|
const hc8 = explicitOrFallbackCount('机箱配套', 'RS485集线器', 'FS-485JXQ-08-A', c8);
|
|
if (hc8 > 0) hubs.push({ name: 'RS485集线器', model: 'FS-485JXQ-08-A', count: hc8 });
|
|
const r = s485 % 8;
|
|
if (r > 0) {
|
|
const c4 = Math.ceil(r / 4);
|
|
const hc4 = explicitOrFallbackCount('机箱配套', 'RS485集线器', 'FS-485JXQ-04-A', c4);
|
|
if (hc4 > 0) hubs.push({ name: 'RS485集线器', model: 'FS-485JXQ-04-A', count: hc4 });
|
|
}
|
|
}
|
|
}
|
|
const instrumentCountForBreaker = items
|
|
.filter(x => x.name === '多通道振弦采集仪' || x.name === '单通道振弦采集模块')
|
|
.reduce((sum, x) => sum + Number(x.count || 0), 0);
|
|
const basics = [
|
|
{ name: '断路器', model: 'DZ47-60', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '12V直流电源防雷器', model: '12V', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '数据采集箱', model: 'FS-CJX03-A', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '接地电缆', model: 'BVR 1*16', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 10 },
|
|
{ name: 'GPRS无线模块', model: 'FS-DTU-4G-W-V1.00', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '物联网卡', model: '20G', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
// { name: '接地桩(镀锌角铁)', model: 'FS-DXJT-2', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 3 },
|
|
// { name: '镀锌扁铁', model: 'FS-DXBL-40*40', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 10 },
|
|
// { name: '降阻剂', model: '25kg/袋', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 10 },
|
|
];
|
|
const mains = isSolar ? [] : [
|
|
{ name: '电源电涌保护器', model: 'AM2-40', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '开关电源1', model: '25-12-A', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '导轨插座', model: '10A', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '220V交流供电电缆', model: 'RVV 3*2.5', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 100 },
|
|
];
|
|
const breakerCount = Number(otherInstrumentCount || 0) + instrumentCountForBreaker;
|
|
request2Data = { factor: "机箱配套", data: breakerCount > 0 ? items.concat(hubs).concat(basics).concat(mains) : [] };
|
|
} else {
|
|
request2Data = { factor: "机箱配套", data: [] };
|
|
}
|
|
} catch (error) {
|
|
console.error('机箱配套 calculation failed', error);
|
|
}
|
|
}
|
|
|
|
let collectingBoxCount = 0; // 采集箱总数
|
|
let vibratingWireAcquisitionInstrumentCount = 0; // 振弦采集仪总数
|
|
let dataAcquisitionInstrumentCount = otherInstrumentCount; // 数据采集仪总数
|
|
let GNSSCount = Number(userText['桥墩/桥塔位移监测(北斗)']?.['测地型GNSS接收机数量'] || 0); // GNSS接收机总数
|
|
let PVCNum = Number(userText['通信系统']?.['PVC管长度'] || 0); // PVC管总数
|
|
let fiberOpticsLength = Number(userText['通信系统']?.['通信光缆(光纤)长度'] || 0); // 通信光缆(光纤)长度
|
|
for (const item of request1Data.concat([request2Data])) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
if (sensor.name === "数据采集箱") {
|
|
collectingBoxCount += Number(sensor.count || 0);
|
|
}
|
|
if (sensor.name.includes("振弦采集仪")) {
|
|
vibratingWireAcquisitionInstrumentCount += Number(sensor.count || 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 通信系统
|
|
let request3Data = {
|
|
factor: "通信系统",
|
|
data: []
|
|
};
|
|
if (userText['通信系统']) {
|
|
try {
|
|
let data = bridgePrompts['通信系统'].getManifest([collectingBoxCount, vibratingWireAcquisitionInstrumentCount, dataAcquisitionInstrumentCount, GNSSCount, PVCNum, fiberOpticsLength]);
|
|
request3Data = { factor: "通信系统", data: data || [] };
|
|
} catch (error) {
|
|
console.error('通信系统 calculation failed', error);
|
|
request3Data = { factor: "通信系统", data: [] };
|
|
}
|
|
}
|
|
|
|
// 太阳能计算
|
|
let request5Data = null;
|
|
let solarEnergy = {
|
|
solarEnergyTitle: null,
|
|
solarEnergyData: null,
|
|
powerTitle: null,
|
|
powerData: null,
|
|
};
|
|
const materials = await models.Materials.findAll({ raw: true, where: { structureType: structureType } });
|
|
if (userText['电力系统(太阳能)']) {
|
|
const request4Params = {
|
|
'拉线位移传感器': 0,
|
|
'静力水准仪': 0,
|
|
'裂缝计': 0,
|
|
'盒式固定测斜仪': 0,
|
|
'温湿度传感器': 0,
|
|
'超声波风速风向仪': 0,
|
|
'多通道振弦采集仪': 0,
|
|
'数据采集系统': 0,
|
|
'磁通量采集系统v1.0': 0,
|
|
'云振动采集仪': 0,
|
|
'振动采集仪': 0,
|
|
'GPRS无线模块': 0,
|
|
'光纤收发器1310': 0,
|
|
'光纤收发器1550': 0,
|
|
'称重系统平台': 0,
|
|
'串口服务器': 0,
|
|
'工业级交换机': 0,
|
|
'光电挠度仪': 0,
|
|
'红外标靶': 0,
|
|
'无线路由器': 0,
|
|
'直线位移传感器': 0,
|
|
'4G网络球机': 0,
|
|
'硬盘录像机': 0,
|
|
'测地型GNSS接收机': 0,
|
|
}
|
|
for (const item of request1Data.concat([request2Data, request3Data])) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
for (const key in request4Params) {
|
|
if (sensor.name === key) {
|
|
request4Params[key] += Number(sensor.count || 0);
|
|
}
|
|
if (key === '光纤收发器1310' && sensor.name === '光纤收发器' && sensor.model === '1310') {
|
|
request4Params[key] += Number(sensor.count || 0);
|
|
}
|
|
if (key === '光纤收发器1550' && sensor.name === '光纤收发器' && sensor.model === '1550') {
|
|
request4Params[key] += Number(sensor.count || 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const regionName = String(userText['电力系统(太阳能)']?.['地区名称'] || '');
|
|
const days = Number(userText['电力系统(太阳能)']?.['连续阴雨天数'] || 0) || 0;
|
|
const region = await superagent
|
|
.post(`${apiUrl}/api/v1/chat/completions`)
|
|
.send({
|
|
"stream": false,
|
|
"detail": false,
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": regionName
|
|
}
|
|
]
|
|
}
|
|
]
|
|
})
|
|
.set({
|
|
Authorization: `Bearer ${regionAppKey}`,
|
|
"Content-Type": "application/json",
|
|
})
|
|
.timeout({
|
|
response: 1000 * 60 * 30, // 等待服务器发送数据的超时时间(毫秒)
|
|
deadline: 1000 * 60 * 30, // 整个请求完成的最大时间(毫秒)
|
|
})
|
|
const regionType = JSON.parse(region?.body?.choices[0]?.message?.content?.trim()).region;
|
|
const batterySafetyFactorMap = { '北方地区': 1.1, '南方平原': 1.1, '南方山区': 1.4 };
|
|
const tempCorrectionFactorMap = { '北方地区': 1.2, '南方平原': 1.0, '南方山区': 1.1 };
|
|
const batterySafetyFactor = batterySafetyFactorMap[regionType];
|
|
const tempCorrectionFactor = tempCorrectionFactorMap[regionType];
|
|
const deviceList = [
|
|
{ name: '拉线位移传感器', model: 'FS-LXWY-Z-500', power: 0.24, key: '拉线位移传感器' },
|
|
{ name: '静力水准仪', model: 'FS-JLSZ-20-V1.00', power: 0.24, key: '静力水准仪' },
|
|
{ name: '裂缝计', model: 'FS-LF10-Z', power: 0.24, key: '裂缝计' },
|
|
{ name: '盒式固定测斜仪', model: 'FS-HGC01-V2.00', power: 0.6, key: '盒式固定测斜仪' },
|
|
{ name: '温湿度传感器', model: 'FS-BDS-WSD', power: 0.24, key: '温湿度传感器' },
|
|
{ name: '超声波风速风向仪', model: 'FS-FSFXY-S', power: 0.144, key: '超声波风速风向仪' },
|
|
{ name: '多通道振弦采集仪', model: 'FS-F08', power: 2.56, key: '多通道振弦采集仪' },
|
|
{ name: '数据采集系统V1.0', model: 'FS-D04', power: 1.7, key: '数据采集系统' },
|
|
{ name: '磁通量采集系统v1.0', model: 'FS-CTL', power: 52, key: '磁通量采集系统v1.0' },
|
|
{ name: '云振动采集仪', model: 'FS-iZD08', power: 18, key: '云振动采集仪' },
|
|
{ name: '振动采集仪', model: 'FS-ZD08', power: 17, key: '振动采集仪' },
|
|
{ name: 'GPRS无线模块', model: 'FS-DTU-4G-W-V1.00', power: 0.96, key: 'GPRS无线模块' },
|
|
{ name: '光纤收发器', model: '1310', power: 6, key: '光纤收发器1310' },
|
|
{ name: '光纤收发器', model: '1550', power: 6, key: '光纤收发器1550' },
|
|
{ name: '称重系统平台', model: '', power: 30, key: '称重系统平台' },
|
|
{ name: '串口服务器', model: '1D(RS485/232)', power: 0.84, key: '串口服务器' },
|
|
{ name: '工业级交换机', model: '', power: 2.5, key: '工业级交换机' },
|
|
{ name: '光电挠度仪', model: 'FS-GDND-125-V1.0', power: 6, key: '光电挠度仪' },
|
|
{ name: '红外标靶', model: 'FS-HWBB-6', power: 15, key: '红外标靶' },
|
|
{ name: '无线路由器', model: '工业级', power: 1.2, key: '无线路由器' },
|
|
{ name: '直线位移传感器', model: 'FS-ZWY-500', power: 1.92, key: '直线位移传感器' },
|
|
{ name: '4G网络球机', model: '3寸 400万', power: 15, key: '4G网络球机' },
|
|
{ name: '硬盘录像机', model: '4路1盘位', power: 18, key: '硬盘录像机' },
|
|
{ name: '测地型GNSS接收机', model: 'A300', power: 0, key: '测地型GNSS接收机' },
|
|
];
|
|
solarEnergy.powerTitle = '功率统计表';
|
|
const powerHeaders = ['序号', '设备名称', '默认型号', '单位', '功率(W)', '数量', '总功率(W)'];
|
|
const powerRows = [];
|
|
const unitOverride = {
|
|
'多通道振弦采集仪|FS-F08': '台(8口)',
|
|
'数据采集系统V1.0|FS-D04': '台(4口)',
|
|
'云振动采集仪|FS-iZD08': '台(8口)',
|
|
'振动采集仪|FS-ZD08': '台(8口)',
|
|
'磁通量采集系统v1.0|FS-CTL': '台'
|
|
};
|
|
const getUnit = (name, model) => {
|
|
const overrideKey = `${name}|${model || ''}`;
|
|
if (unitOverride[overrideKey]) return unitOverride[overrideKey];
|
|
let m = materials.find(m => m.name === name && (model ? m.model === model : true));
|
|
if (!m && model) m = materials.find(m => m.model === model);
|
|
if (!m) m = materials.find(m => m.name === name);
|
|
return m?.unit || '';
|
|
};
|
|
let totalPower = 0;
|
|
for (let i = 0; i < deviceList.length; i++) {
|
|
const d = deviceList[i];
|
|
const qty = Number(request4Params[d.key] || 0);
|
|
const rowPower = Number(d.power || 0);
|
|
const rowTotalPower = Number((rowPower * qty).toFixed(6));
|
|
totalPower += rowTotalPower;
|
|
const unit = getUnit(d.name, d.model);
|
|
powerRows.push([
|
|
String(i + 1),
|
|
d.name,
|
|
d.model,
|
|
unit,
|
|
String(rowPower),
|
|
String(qty),
|
|
String(rowTotalPower)
|
|
]);
|
|
}
|
|
solarEnergy.powerData = { headers: powerHeaders, rows: powerRows };
|
|
const dailyConsumption = totalPower * 24;
|
|
const averageDailyConsumption = totalPower * 2;
|
|
const batteryCapacity = batterySafetyFactor * averageDailyConsumption * days * tempCorrectionFactor / 0.7;
|
|
let batteryCount = Math.ceil(batteryCapacity / 120);
|
|
const singlePanelPower = 200;
|
|
const sunlightHours = 4;
|
|
const singlePanelDailySupply = singlePanelPower * sunlightHours * 0.63;
|
|
const deviceDailyConsumption = dailyConsumption / 0.9;
|
|
let solarPanelCount = Math.ceil(deviceDailyConsumption / singlePanelDailySupply);
|
|
const gnssCount = Number(request4Params['测地型GNSS接收机'] || 0);
|
|
if (gnssCount > 0) {
|
|
solarPanelCount += gnssCount;
|
|
batteryCount += 2 * gnssCount;
|
|
}
|
|
solarEnergy.solarEnergyTitle = regionType;
|
|
const solarHeaders = ['总功率', '日耗电量', '日平均耗电量', '连续阴雨天', '蓄电池容量安全系数', '温度修正系数', '电池容量', '电池数量', '单块太阳能功率', '日平均光照时间', '设备单日耗电量', '太阳能板单日供电量', '太阳能板数量'];
|
|
const solarRow = [
|
|
// regionName,
|
|
String(Number(totalPower.toFixed(6))),
|
|
String(Number(dailyConsumption.toFixed(6))),
|
|
String(Number(averageDailyConsumption.toFixed(6))),
|
|
String(days),
|
|
String(batterySafetyFactor),
|
|
String(tempCorrectionFactor),
|
|
String(Number(batteryCapacity.toFixed(6))),
|
|
String(batteryCount),
|
|
String(singlePanelPower),
|
|
String(sunlightHours),
|
|
String(Number(deviceDailyConsumption.toFixed(6))),
|
|
String(Number(singlePanelDailySupply.toFixed(6))),
|
|
String(solarPanelCount)
|
|
];
|
|
solarEnergy.solarEnergyData = { headers: solarHeaders, rows: [solarRow] };
|
|
try {
|
|
const data = bridgePrompts['电力系统(太阳能)'].getManifest([solarPanelCount, batteryCount]) || [];
|
|
const controllerLoad = (200 / 18) * solarPanelCount;
|
|
const controllerModel = controllerLoad < 10 ? '12V10A' : controllerLoad < 30 ? '12V30A' : '12V50A';
|
|
const controllerCount = Math.ceil((200 * solarPanelCount) / 900);
|
|
const controllerDesc = '太阳能控制器:根据太阳能控制器选型计算公式从“12V10A、12V30A、12V50A”中选择一个合适的型号。默认太阳能板功率为200,太阳能控制器选型计算公式:(太阳能板功率/18)*太阳能板数量,根据该公式的计算结果选择对应型号:如果计算结果<10,选择太阳能控制器12V10A;如果计算结果>=10且<30选择太阳能控制器12V30A;如果计算结果>=30选择太阳能控制器12V50A。太阳能控制器数量=太阳能板功率*太阳能板数量/900(向上取整)。';
|
|
for (let k = 0; k < data.length; k++) {
|
|
const it = data[k];
|
|
if (String(it?.name) === '太阳能控制器') {
|
|
it.model = controllerModel;
|
|
it.count = controllerCount;
|
|
}
|
|
}
|
|
request5Data = { data };
|
|
} catch (error) {
|
|
console.error('电力系统(太阳能) calculation failed', error);
|
|
}
|
|
}
|
|
|
|
const allListData = request1Data.concat([request2Data, request3Data]);
|
|
if (request5Data) { allListData.push(request5Data) }
|
|
|
|
// 工期核算清单
|
|
const request6Params = {
|
|
'表面式应变计': 0,
|
|
'内埋式应变计': 0,
|
|
'温度传感器': 0,
|
|
'温湿度传感器': 0,
|
|
'盒式固定测斜仪': 0,
|
|
'裂缝计': 0,
|
|
'静力水准仪': 0,
|
|
'拉线位移传感器': 0,
|
|
'超声波风速风向仪': 0,
|
|
'光电挠度仪': 0,
|
|
'测地型GNSS接收机': 0,
|
|
'4G网络球机': 0,
|
|
'两车道(全套)': 0,
|
|
'太阳能控制器': 0,
|
|
'接地桩(镀锌角铁)': 0,
|
|
'通信电缆': 0,
|
|
'接地电缆': 0,
|
|
'桥架': 0,
|
|
'PVC管': 0,
|
|
}
|
|
for (const item of allListData) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
for (const key in request6Params) {
|
|
if (sensor.name === key) {
|
|
request6Params[key] += Number(sensor.count || 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const totalCountMap = {};
|
|
for (const item of allListData) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
const k = sensor.name;
|
|
const v = Number(sensor.count || 0);
|
|
totalCountMap[k] = (totalCountMap[k] || 0) + v;
|
|
}
|
|
}
|
|
}
|
|
const cableSum = Object.entries(totalCountMap).reduce((s, [k, v]) => s + ((String(k).includes('通信电缆') || String(k).includes('接地电缆')) ? Number(v || 0) : 0), 0);
|
|
const installQtyMap = {
|
|
'表面式应变计': 8,
|
|
'内埋式应变计': 8,
|
|
'钢筋计': 8,
|
|
'土压力计': 6.4,
|
|
'孔隙水压计': 8,
|
|
'轴力计': 2,
|
|
'锚索计': 2,
|
|
'温度传感器': 8,
|
|
'温湿度传感器': 4,
|
|
'土壤温湿度传感器': 4,
|
|
'闭环磁通量标定': 8,
|
|
'开环磁通量绕线(小)': 3,
|
|
'开环磁通量绕线(中)': 1.6,
|
|
'开环磁通量绕线(大)': 0.7,
|
|
'人工磁通量温补': 5.3,
|
|
'盒式固定测斜仪': 5.3,
|
|
'串联式固定测斜仪': 6,
|
|
'裂缝计': 5.3,
|
|
'静力水准仪': 6.4,
|
|
'物位计': 6.4,
|
|
'雷达液位计': 2,
|
|
'拉线位移传感器': 3.2,
|
|
'多点位移计(振弦式)': 2.7,
|
|
'加速度计': 5.3,
|
|
'激光测距仪': 5,
|
|
'投入式水位计': 1.5,
|
|
'超声波水位计': 2,
|
|
'雨量计': 2,
|
|
'超声波风速风向仪': 2.5,
|
|
'光电挠度仪': 0.7,
|
|
'全站仪': 0.3,
|
|
'钢尺水位计': 2,
|
|
'一体化裂缝计(成品)': 2.7,
|
|
'一体化地灾(加速度计)': 4,
|
|
'一体化地灾倾角计': 4,
|
|
'GNSS接收机': 0.9,
|
|
'4G球机': 2,
|
|
'管式含水率仪': 4,
|
|
'分布式节点': 4,
|
|
'称重系统(1套2车道)': 0.3,
|
|
'拼接屏(4*6)': 0.3,
|
|
'太阳能供电': 1,
|
|
'监控中心(3天)': 3.2,
|
|
'墩座1': 4,
|
|
'墩座2': 4,
|
|
'墩座3': 2.7,
|
|
'墩座4': 2,
|
|
'基础1(具备挖机、吊车)': 0.7,
|
|
'基础2': 2.7,
|
|
'基础3': 2.7,
|
|
'基础4': 2,
|
|
'地网': 1,
|
|
'线缆(h/100米)': 8.4,
|
|
'桥架1(h/100米)': 1.3,
|
|
'桥架2(h/100米)': 1.3,
|
|
'PVC管1(h/100米)': 5.3,
|
|
'PVC管2(h/100米)': 5.3
|
|
};
|
|
// Object.assign(installQtyMap, getHoursPerPerson());
|
|
const inputQtyMap = {
|
|
'表面式应变计': Number(totalCountMap['表面式应变计'] || 0),
|
|
'内埋式应变计': Number(totalCountMap['内埋式应变计'] || 0),
|
|
'钢筋计': Number(totalCountMap['钢筋计'] || 0),
|
|
'土压力计': Number(totalCountMap['土压力计'] || 0),
|
|
'孔隙水压计': Number(totalCountMap['孔隙水压计'] || 0),
|
|
'轴力计': Number(totalCountMap['轴力计'] || 0),
|
|
'锚索计': Number(totalCountMap['锚索计'] || 0),
|
|
'温度传感器': Number(totalCountMap['温度传感器'] || 0),
|
|
'温湿度传感器': Number(totalCountMap['温湿度传感器'] || 0),
|
|
'土壤温湿度传感器': Number(totalCountMap['土壤温湿度传感器'] || 0),
|
|
'闭环磁通量标定': 0,
|
|
'开环磁通量绕线(小)': 0,
|
|
'开环磁通量绕线(中)': 0,
|
|
'开环磁通量绕线(大)': 0,
|
|
'人工磁通量温补': 0,
|
|
'盒式固定测斜仪': Number(totalCountMap['盒式固定测斜仪'] || 0),
|
|
'串联式固定测斜仪': Number(totalCountMap['串联式固定测斜仪'] || 0),
|
|
'裂缝计': Number(totalCountMap['裂缝计'] || 0),
|
|
'静力水准仪': Number(totalCountMap['静力水准仪'] || 0),
|
|
'物位计': Number(totalCountMap['物位计'] || 0),
|
|
'雷达液位计': Number(totalCountMap['雷达液位计'] || 0),
|
|
'拉线位移传感器': Number(totalCountMap['拉线位移传感器'] || 0),
|
|
'多点位移计(振弦式)': Number(totalCountMap['多点位移计(振弦式)'] || 0),
|
|
'加速度计': Number(totalCountMap['加速度计'] || 0),
|
|
'激光测距仪': Number(totalCountMap['激光测距仪'] || 0),
|
|
'投入式水位计': Number(totalCountMap['投入式水位计'] || 0),
|
|
'超声波水位计': Number(totalCountMap['超声波水位计'] || 0),
|
|
'雨量计': Number(totalCountMap['雨量计'] || 0),
|
|
'超声波风速风向仪': Number(totalCountMap['超声波风速风向仪'] || 0),
|
|
'光电挠度仪': Number(totalCountMap['光电挠度仪'] || 0),
|
|
'全站仪': Number(totalCountMap['全站仪'] || 0),
|
|
'钢尺水位计': Number(totalCountMap['钢尺水位计'] || 0),
|
|
'一体化裂缝计(成品)': Number(totalCountMap['一体化裂缝计(成品)'] || 0),
|
|
'一体化地灾(加速度计)': Number(totalCountMap['一体化地灾(加速度计)'] || 0),
|
|
'一体化地灾倾角计': Number(totalCountMap['一体化地灾倾角计'] || 0),
|
|
'GNSS接收机': Number(totalCountMap['测地型GNSS接收机'] || 0),
|
|
'4G球机': Number(totalCountMap['4G网络球机'] || 0),
|
|
'管式含水率仪': Number(totalCountMap['管式含水率仪'] || 0),
|
|
'分布式节点': Number(totalCountMap['分布式节点'] || 0),
|
|
'称重系统(1套2车道)': Number(totalCountMap['两车道(全套)'] || 0),
|
|
'拼接屏(4*6)': Number(totalCountMap['拼接屏(4*6)'] || 0),
|
|
'太阳能供电': Number(totalCountMap['太阳能控制器'] || 0),
|
|
'监控中心(3天)': 0,
|
|
'墩座1': Number(totalCountMap['墩座1'] || 0),
|
|
'墩座2': Number(totalCountMap['墩座2'] || 0),
|
|
'墩座3': Number(totalCountMap['墩座3'] || 0),
|
|
'墩座4': Number(totalCountMap['墩座4'] || 0),
|
|
'基础1(具备挖机、吊车)': Number(totalCountMap['基础1(具备挖机、吊车)'] || 0),
|
|
'基础2': Number(totalCountMap['基础2'] || 0),
|
|
'基础3': Number(totalCountMap['基础3'] || 0),
|
|
'基础4': Number(totalCountMap['基础4'] || 0),
|
|
'地网': Number(totalCountMap['接地桩(镀锌角铁)'] || 0),
|
|
'线缆(h/100米)': Number(cableSum || 0),
|
|
'桥架1(h/100米)': Number(totalCountMap['桥架'] || 0),
|
|
'桥架2(h/100米)': 0,
|
|
'PVC管1(h/100米)': Number(PVCNum || 0),
|
|
'PVC管2(h/100米)': 0
|
|
};
|
|
const sensorsForDebug = [
|
|
'表面式应变计',
|
|
'内埋式应变计',
|
|
'温度传感器',
|
|
'温湿度传感器',
|
|
'盒式固定测斜仪',
|
|
'裂缝计',
|
|
'静力水准仪',
|
|
'拉线位移传感器',
|
|
'超声波风速风向仪',
|
|
'光电挠度仪',
|
|
'GNSS接收机',
|
|
'4G球机'
|
|
];
|
|
const sensorTotal = sensorsForDebug.reduce((sum, k) => sum + Number(inputQtyMap[k] || 0), 0);
|
|
const systemDebugDays = sensorTotal <= 50 ? 2 : sensorTotal <= 100 ? 3 : 5;
|
|
const acceptanceTrainingDays = sensorTotal <= 100 ? 3 : 5;
|
|
const projectRiskDays = sensorTotal <= 100 ? 2 : 3;
|
|
const headers6 = ['序号', '分类', '事项', '耗时(小时/人)', '安装数量/个(2人8小时计)', '备注', '数量输入', '工期(2人天)'];
|
|
const orderedItems = [
|
|
'表面式应变计',
|
|
'内埋式应变计',
|
|
'钢筋计',
|
|
'土压力计',
|
|
'孔隙水压计',
|
|
'轴力计',
|
|
'锚索计',
|
|
'温度传感器',
|
|
'温湿度传感器',
|
|
'土壤温湿度传感器',
|
|
'闭环磁通量标定',
|
|
'开环磁通量绕线(小)',
|
|
'开环磁通量绕线(中)',
|
|
'开环磁通量绕线(大)',
|
|
'人工磁通量温补',
|
|
'盒式固定测斜仪',
|
|
'串联式固定测斜仪',
|
|
'裂缝计',
|
|
'静力水准仪',
|
|
'物位计',
|
|
'雷达液位计',
|
|
'拉线位移传感器',
|
|
'多点位移计(振弦式)',
|
|
'加速度计',
|
|
'激光测距仪',
|
|
'投入式水位计',
|
|
'超声波水位计',
|
|
'雨量计',
|
|
'超声波风速风向仪',
|
|
'光电挠度仪',
|
|
'全站仪',
|
|
'钢尺水位计',
|
|
'一体化裂缝计(成品)',
|
|
'一体化地灾(加速度计)',
|
|
'一体化地灾倾角计',
|
|
'GNSS接收机',
|
|
'4G球机',
|
|
'管式含水率仪',
|
|
'分布式节点',
|
|
'称重系统(1套2车道)',
|
|
'拼接屏(4*6)',
|
|
'太阳能供电',
|
|
'监控中心(3天)',
|
|
'墩座1',
|
|
'墩座2',
|
|
'墩座3',
|
|
'墩座4',
|
|
'基础1(具备挖机、吊车)',
|
|
'基础2',
|
|
'基础3',
|
|
'基础4',
|
|
'地网',
|
|
'线缆(h/100米)',
|
|
'桥架1(h/100米)',
|
|
'桥架2(h/100米)',
|
|
'PVC管1(h/100米)',
|
|
'PVC管2(h/100米)',
|
|
'系统调试',
|
|
'外包(天)',
|
|
'验收、培训',
|
|
'项目风险天数(设备转场、开路、技术难点等)',
|
|
'项目经理(天)'
|
|
];
|
|
function getCategory(name) {
|
|
const sensorSet = new Set([
|
|
'表面式应变计', '内埋式应变计', '钢筋计', '土压力计', '孔隙水压计', '轴力计', '锚索计',
|
|
'温度传感器', '温湿度传感器', '土壤温湿度传感器', '闭环磁通量标定', '开环磁通量绕线(小)',
|
|
'开环磁通量绕线(中)', '开环磁通量绕线(大)', '人工磁通量温补', '盒式固定测斜仪', '串联式固定测斜仪',
|
|
'裂缝计', '静力水准仪', '物位计', '雷达液位计', '拉线位移传感器', '多点位移计(振弦式)',
|
|
'加速度计', '激光测距仪', '投入式水位计', '超声波水位计', '雨量计', '超声波风速风向仪',
|
|
'光电挠度仪', '全站仪', '钢尺水位计', '一体化裂缝计(成品)', '一体化地灾(加速度计)',
|
|
'一体化地灾倾角计', '振动传感器', 'GNSS接收机', '4G球机'
|
|
]);
|
|
const deviceSet = new Set(['分布式节点', '称重系统(1套2车道)', '拼接屏(4*6)', '太阳能供电', '监控中心(3天)']);
|
|
const civilSet = new Set(['墩座1', '墩座2', '墩座3', '墩座4', '基础1(具备挖机、吊车)', '基础2', '基础3', '基础4', '地网']);
|
|
if (sensorSet.has(name)) return '传感器类';
|
|
if (deviceSet.has(name)) return '设备类';
|
|
if (civilSet.has(name)) return '土建类(地笼、地网、基座、立杆、熔纤)';
|
|
if (name.endsWith('(h/100米)') || name === '系统调试') return '集成';
|
|
if (name === '外包(天)') return '外包(天)';
|
|
if (name === '验收、培训') return '验收及培训';
|
|
if (name === '项目风险天数(设备转场、开路、技术难点等)') return '风险天数';
|
|
if (name === '项目经理(天)') return '项目经理(天)';
|
|
return '其他类';
|
|
}
|
|
const hoursPerPersonMap = {
|
|
'表面式应变计': '2',
|
|
'内埋式应变计': '2',
|
|
'钢筋计': '2',
|
|
'土压力计': '2.5',
|
|
'孔隙水压计': '2',
|
|
'轴力计': '8',
|
|
'锚索计': '8',
|
|
'温度传感器': '2',
|
|
'温湿度传感器': '4',
|
|
'土壤温湿度传感器': '4',
|
|
'闭环磁通量标定': '2',
|
|
'开环磁通量绕线(小)': '4',
|
|
'开环磁通量绕线(中)': '10',
|
|
'开环磁通量绕线(大)': '24',
|
|
'人工磁通量温补': '3',
|
|
'盒式固定测斜仪': '3',
|
|
'串联式固定测斜仪': '2',
|
|
'裂缝计': '3',
|
|
'静力水准仪': '2.5',
|
|
'拉线位移传感器': '5',
|
|
'多点位移计(振弦式)': '6',
|
|
'振动传感器': '3',
|
|
'激光测距仪': '3',
|
|
'投入式水位计': '12',
|
|
'超声波水位计': '8',
|
|
'雨量计': '8',
|
|
'超声波风速风向仪': '6',
|
|
'光电挠度仪': '24',
|
|
'全站仪': '48',
|
|
'钢尺水位计': '8',
|
|
'GNSS接收机': '18',
|
|
'4G球机': '8',
|
|
'分布式节点': '4',
|
|
'称重系统(1套2车道)': '64',
|
|
'拼接屏(4*6)': '48',
|
|
'太阳能供电': '16',
|
|
'监控中心(3天)': '5',
|
|
'墩座1': '4',
|
|
'墩座2': '4',
|
|
'墩座3': '6',
|
|
'墩座4': '8',
|
|
'基础1(具备挖机、吊车)': '24',
|
|
'基础2': '6',
|
|
'基础3': '6',
|
|
'基础4': '8',
|
|
'地网': '16',
|
|
'线缆(h/100米)': '1.9',
|
|
'桥架1(h/100米)': '12',
|
|
'桥架2(h/100米)': '16',
|
|
'PVC管1(h/100米)': '3',
|
|
'PVC管2(h/100米)': '8',
|
|
};
|
|
function getHoursPerPerson(name) {
|
|
return hoursPerPersonMap[name] ?? '';
|
|
}
|
|
const remarksMap = {
|
|
'土压力计': '根据挖槽深度、难度',
|
|
'轴力计': '含加预应力过程时间',
|
|
'锚索计': '含张拉过程时间',
|
|
'闭环磁通量标定': '需另外加上集成调试时间,固定传感器时间',
|
|
'开环磁通量绕线(小)': '内径50以下',
|
|
'开环磁通量绕线(中)': '内径50-100左右',
|
|
'开环磁通量绕线(大)': '内径100以上',
|
|
'人工磁通量温补': '距离近可转场按照1天6个计算,不需要外包',
|
|
'串联式固定测斜仪': '单位:串',
|
|
'多点位移计(振弦式)': '含打孔注浆',
|
|
'光电挠度仪': '需要参考标靶数量',
|
|
'全站仪': '全站仪安装3天计(含基础及电脑),含棱镜',
|
|
'钢尺水位计': '含混凝土立柱',
|
|
'GNSS接收机': '含基础浇筑',
|
|
'4G球机': '混凝土凝固,折返时间',
|
|
'监控中心(3天)': '监控中心整体考虑2人3天',
|
|
'墩座1': '雨量计:300*300*250mm',
|
|
'墩座2': '测斜保护墩:250*250*200mm',
|
|
'墩座3': '采集箱底座:600*300*200mm',
|
|
'墩座4': 'GNSS混凝土墩:1800*315mm(高*直径)',
|
|
'基础1(具备挖机、吊车)': '12米横臂拍立杆:1800*1800*2000mm',
|
|
'基础2': '视频立杆:300*300*800mm',
|
|
'基础3': '雷达液位计立杆基础:300*300*800mm',
|
|
'基础4': 'GNSS钢立柱基础:600*600*800mm',
|
|
'地网': '标准防雷地网(土质)',
|
|
'线缆(h/100米)': '需要机械,线缆及水管气管',
|
|
'桥架1(h/100米)': '不需机械脚手架',
|
|
'桥架2(h/100米)': '需机械脚手架',
|
|
'PVC管1(h/100米)': '不需机械脚手架',
|
|
'PVC管2(h/100米)': '需机械脚手架',
|
|
'系统调试': '传感器≤50,按2天;100≥传感器数量>50,按4天;传感器数量>100,按5天',
|
|
'验收、培训': '传感器≤100,按3天;传感器数量>100,按4天',
|
|
'项目风险天数(设备转场、开路、技术难点等)': '传感器≤100,按2天;传感器数量>100,按5天',
|
|
};
|
|
function getInstallDisplay(name, installQty, qty) {
|
|
// if (name.endsWith('(h/100米)')) {
|
|
// const v = Number(((installQty || 0) * (qty || 0) / 100).toFixed(3));
|
|
// return String(v);
|
|
// }
|
|
if (name === '地网') return '1';
|
|
if (name === '系统调试') return '2-5天';
|
|
if (name === '验收、培训') return '2-4天';
|
|
if (name === '项目风险天数(设备转场、开路、技术难点等)') return '2-5天';
|
|
if (name === '外包(天)' || name === '项目经理(天)') return '';
|
|
return String(installQty ?? '');
|
|
}
|
|
function installRuleText(name) {
|
|
if (name === '地网') return '安装数量×数量输入÷3';
|
|
if (name.endsWith('(h/100米)')) return '安装数量×数量输入÷100';
|
|
if (name === '监控中心(3天)') return '固定3天';
|
|
if (name === '系统调试') return '总数≤50→2;≤100→3;>100→5';
|
|
if (name === '验收、培训') return '总数≤100→3;>100→5';
|
|
if (name === '项目风险天数(设备转场、开路、技术难点等)') return '总数≤100→2;>100→3';
|
|
return '数量输入÷安装数量';
|
|
}
|
|
function computeDuration(name, installQty, qty) {
|
|
if (name === '地网') return Number(((qty || 0) / 3).toFixed(3));
|
|
if (name.endsWith('(h/100米)')) {
|
|
if (!qty) return 0;
|
|
const installDisplay = (installQty || 0) ? (installQty * qty / 100) : 0;
|
|
if (!installDisplay) return 0;
|
|
return Number(((qty || 0) / installDisplay).toFixed(3));
|
|
}
|
|
if (name === '监控中心(3天)') return 3;
|
|
if (name === '系统调试') return Number(systemDebugDays.toFixed(3));
|
|
if (name === '验收、培训') return Number(acceptanceTrainingDays.toFixed(3));
|
|
if (name === '项目风险天数(设备转场、开路、技术难点等)') return Number(projectRiskDays.toFixed(3));
|
|
if (name === '外包(天)') return 0;
|
|
if (!installQty) return 0;
|
|
return Number(((qty || 0) / installQty).toFixed(3));
|
|
}
|
|
const rows6 = [];
|
|
for (let i = 0; i < orderedItems.length; i++) {
|
|
const name = orderedItems[i];
|
|
const installQty = installQtyMap[name] ?? 0;
|
|
const qty = inputQtyMap[name] ?? 0;
|
|
const durationVal = computeDuration(name, installQty, qty);
|
|
const displayName = (name === '外包(天)' || name === '项目经理(天)') ? '' : name;
|
|
const installDisplay = getInstallDisplay(name, installQty, qty);
|
|
const qtyStr = (name === '外包(天)' || name === '项目经理(天)') ? '' : String(Number((qty || 0).toFixed(3)));
|
|
rows6.push([
|
|
String(i + 1),
|
|
getCategory(name),
|
|
displayName,
|
|
getHoursPerPerson(name),
|
|
installDisplay,
|
|
remarksMap[name] ?? '',
|
|
qtyStr,
|
|
String(durationVal)
|
|
]);
|
|
}
|
|
const epiboleLimitIdx = orderedItems.indexOf('系统调试') + 1;
|
|
const epiboleDays = rows6.reduce((acc, row) => {
|
|
const idx = parseInt(row[0], 10);
|
|
if (isNaN(idx) || idx > epiboleLimitIdx) return acc;
|
|
return acc + Number(row[row.length - 1] || 0);
|
|
}, 0);
|
|
const managerHoursPerDay = Number(userText['工期核算清单']?.['项目经理每日工时'] || 8);
|
|
const projectManagerDays = Number((((epiboleDays + acceptanceTrainingDays + projectRiskDays) * 8) / (managerHoursPerDay || 8)).toFixed(6));
|
|
for (let i = 0; i < rows6.length; i++) {
|
|
const category = rows6[i][1];
|
|
if (category === '外包(天)') {
|
|
const v = String(Number(epiboleDays.toFixed(3)));
|
|
rows6[i][6] = '';
|
|
rows6[i][7] = v;
|
|
} else if (category === '项目经理(天)') {
|
|
const v = String(Number(projectManagerDays.toFixed(3)));
|
|
rows6[i][6] = '';
|
|
rows6[i][7] = v;
|
|
}
|
|
}
|
|
const durationData = { headers: headers6, rows: rows6 };
|
|
|
|
let hasSolar = false
|
|
let request7Power = '';
|
|
let hasFiber = false;
|
|
for (const item of allListData) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
if (sensor.name.includes('太阳能') && !request7Power) {
|
|
request7Power = ',太阳能供电';
|
|
hasSolar = true;
|
|
}
|
|
if (sensor.name.includes('光纤')) {
|
|
request7Power = ',光纤通信';
|
|
hasFiber = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (hasFiber) break;
|
|
}
|
|
|
|
const mainPointData = getMainPointData(userText, epiboleDays, hasSolar, hasFiber);
|
|
let sheet1Data = request1Data;
|
|
let sheet1Key = request1Key;
|
|
if (userText['机箱配套']) {
|
|
sheet1Data.push(request2Data);
|
|
sheet1Key.push('机箱配套');
|
|
}
|
|
if (userText['通信系统']) {
|
|
sheet1Data.push(request3Data);
|
|
sheet1Key.push('通信系统');
|
|
}
|
|
if (request5Data) {
|
|
sheet1Data.push(request5Data);
|
|
sheet1Key.push('电力系统(太阳能)');
|
|
}
|
|
/********** 2.生成excel **********/
|
|
const tableData = generationXlsxData({
|
|
materials, sheet1Data, sheet1Key,
|
|
solarEnergy: userText['电力系统(太阳能)'] ? solarEnergy : null,
|
|
duration: {
|
|
durationData,
|
|
epiboleDays,
|
|
projectManagerDays
|
|
},
|
|
mainPointData
|
|
});
|
|
|
|
/********** 3.保存数据 **********/
|
|
await models.SchemeList.update(
|
|
{
|
|
status: 'success',
|
|
updateAt: new Date(),
|
|
tableData,
|
|
},
|
|
{ where: { id: schemeId } }
|
|
);
|
|
ctx.logger.log(`方案清单数据生成成功,schemeId:${schemeId}`);
|
|
|
|
await transaction.commit();
|
|
} catch (error) {
|
|
console.log('完整的 schemeAppKey:', JSON.stringify(ctx.app.fs.config.fastGpt));
|
|
|
|
// 或者在环境变量中
|
|
console.log('环境变量中的API Key:', process.env.FASTGPT_API_KEY);
|
|
await transaction.rollback();
|
|
ctx.logger.log(error);
|
|
try {
|
|
await models.SchemeList.update(
|
|
{ status: 'fail' },
|
|
{ where: { id: schemeId } }
|
|
);
|
|
} catch (err) {
|
|
console.log(`修改状态失败,scheme.id=${schemeId}`);
|
|
ctx.logger.log(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 生成隧道方案清单
|
|
async function generationTunnelScheme(ctx, schemeId, userText, structureType, transmitMethod) {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
const { models } = ctx.app.fs.dc;
|
|
try {
|
|
const { apiUrl } = ctx.app.fs.config.fastGpt;
|
|
const regionAppKey = 'fastgpt-kzqnfXUusbIP6R42we1KMAST9SqBAPCtbAxTV1oQHWRuM7CEb1am2VF7z0'
|
|
const allRequestKey = Object.keys(userText);
|
|
|
|
const request1Key = allRequestKey.filter(key => !['机箱配套', '通信系统', '电力系统(太阳能)'].includes(key));
|
|
const request1Data = await Promise.all(request1Key.map(async key => {
|
|
try {
|
|
const expectSolarParamKeys = new Set([
|
|
'桥墩/桥塔位移监测(北斗)',
|
|
'视频监控(有线)',
|
|
'视频监控(无线1)',
|
|
'机箱配套',
|
|
]);
|
|
|
|
const isSolarSelected = !!userText['电力系统(太阳能)'];
|
|
const params = [].concat(
|
|
tunnelPrompts[key].sensors.map(sensor => {
|
|
const v = userText[key]?.[sensor];
|
|
if (v === undefined && /数量|长度|桥长|车道|天数|弦数/.test(sensor)) return 0;
|
|
return v;
|
|
}),
|
|
[expectSolarParamKeys.has(key) ? isSolarSelected : transmitMethod]
|
|
);
|
|
let data = tunnelPrompts[key].getManifest(params) || [];
|
|
data = data.map(it => ({ ...it, name: String(it.name || '').trim(), model: String(it.model || '').trim() }));
|
|
const fileUrl = tunnelPrompts[key]?.file;
|
|
if (fileUrl) {
|
|
const fileData = await readManifestFromFile(fileUrl);
|
|
const map = new Map();
|
|
const keyFn = (n, m) => `${String(n || '').trim().toLowerCase()}|${String(m || '').trim().toLowerCase()}`;
|
|
for (const it of data || []) map.set(keyFn(it.name, it.model), { name: String(it.name || '').trim(), model: String(it.model || '').trim(), count: Number(it.count || 0), type: it.type, unit: it.unit, code: it.code, price: it.price, lineLength: it.lineLength });
|
|
for (const it of fileData || []) {
|
|
const k = keyFn(it.name, it.model);
|
|
if (map.has(k)) {
|
|
const v = map.get(k);
|
|
if (!v.type && it.type) v.type = it.type;
|
|
if (!v.unit && it.unit) v.unit = it.unit;
|
|
if (!v.code && it.code) v.code = it.code;
|
|
if (v.price == null && it.price != null) v.price = it.price;
|
|
if (v.lineLength == null && it.lineLength != null) v.lineLength = it.lineLength;
|
|
map.set(k, v);
|
|
}
|
|
}
|
|
data = Array.from(map.values());
|
|
}
|
|
return { factor: key, data: data || [] };
|
|
} catch (error) {
|
|
console.error(`Calculation failed for ${key}`, error);
|
|
return { factor: key, data: [] };
|
|
}
|
|
}));
|
|
const opsSelected2 = String(userText['运维服务']?.['选择子服务'] || '').split('、').map(x => x.trim()).filter(Boolean);
|
|
for (let i = 0; i < request1Data.length; i++) {
|
|
const it = request1Data[i];
|
|
if (it.factor === '运维服务' && Array.isArray(it.data)) {
|
|
it.data = opsSelected2.length > 0 ? it.data.filter(row => opsSelected2.includes(String(row?.name || ''))) : [];
|
|
}
|
|
}
|
|
|
|
async function readManifestFromFile(fileUrl) {
|
|
try {
|
|
const res = await superagent
|
|
.get(fileUrl)
|
|
.buffer(true)
|
|
.parse((response, cb) => {
|
|
const data = [];
|
|
response.on('data', (chunk) => {
|
|
data.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
});
|
|
response.on('end', () => cb(null, Buffer.concat(data)));
|
|
response.on('error', (err) => cb(err));
|
|
});
|
|
const buf = res.body;
|
|
const wb = XLSXS.read(buf, { type: 'buffer' });
|
|
const sheetNames = wb.SheetNames || [];
|
|
if (!sheetNames.length) return [];
|
|
const all = [];
|
|
for (let s = 0; s < sheetNames.length; s++) {
|
|
const ws = wb.Sheets[sheetNames[s]];
|
|
if (!ws) continue;
|
|
const rows = XLSXS.utils.sheet_to_json(ws, { header: 1, blankrows: false, defval: '' }) || [];
|
|
let nameIdx = -1, modelIdx = -1, countIdx = -1, startIdx = -1, typeIdx = -1, unitIdx = -1, codeIdx = -1, priceIdx = -1, lineLenIdx = -1;
|
|
const headerMaxScan = Math.min(rows.length, 20);
|
|
for (let i = 0; i < headerMaxScan; i++) {
|
|
const row = rows[i] || [];
|
|
for (let j = 0; j < row.length; j++) {
|
|
const cell = String(row[j] || '').replace(/\s+/g, '');
|
|
if (nameIdx < 0 && (cell === '名称' || cell.indexOf('设备名称') > -1 || cell.indexOf('物料名称') > -1 || cell.indexOf('品名') > -1)) nameIdx = j;
|
|
if (modelIdx < 0 && (cell === '规格/型号' || cell.indexOf('规格') > -1 || cell.indexOf('型号') > -1)) modelIdx = j;
|
|
if (countIdx < 0 && (cell === '数量' || cell.indexOf('数量') > -1 || cell.indexOf('总数') > -1)) countIdx = j;
|
|
if (typeIdx < 0 && (cell === '类型' || cell.indexOf('类型') > -1)) typeIdx = j;
|
|
if (unitIdx < 0 && (cell === '单位' || cell.indexOf('单位') > -1)) unitIdx = j;
|
|
if (codeIdx < 0 && (cell === '物料代码' || cell.indexOf('物料编码') > -1 || cell.indexOf('物料代码') > -1)) codeIdx = j;
|
|
if (priceIdx < 0 && (cell === '单价' || cell.indexOf('单价') > -1 || cell.indexOf('价格') > -1)) priceIdx = j;
|
|
if (lineLenIdx < 0 && (cell.indexOf('线长') > -1)) lineLenIdx = j;
|
|
}
|
|
if (nameIdx >= 0 && countIdx >= 0) { startIdx = i + 1; break; }
|
|
}
|
|
if (startIdx < 0) continue;
|
|
for (let i = startIdx; i < rows.length; i++) {
|
|
const row = rows[i] || [];
|
|
const nameCell = row[nameIdx];
|
|
const rawName = nameCell == null ? '' : String(nameCell);
|
|
const nameStr = rawName.trim();
|
|
if (!nameStr) continue;
|
|
const lower = nameStr.toLowerCase();
|
|
if (nameStr.indexOf('小计') > -1 || nameStr.indexOf('合计') > -1 || lower.indexOf('total') > -1) break;
|
|
const modelStr = modelIdx >= 0 ? String(row[modelIdx] || '').trim() : '';
|
|
const countVal = row[countIdx];
|
|
const countStr = String(countVal == null ? '' : countVal).trim();
|
|
const match = countStr.match(/-?\d+(\.\d+)?/);
|
|
const countNum = match ? Number(match[0]) : Number(countStr || 0);
|
|
if (!isNaN(countNum) && countNum > 0) {
|
|
const typeStr = typeIdx >= 0 ? String(row[typeIdx] || '').trim() : '';
|
|
const unitStr = unitIdx >= 0 ? String(row[unitIdx] || '').trim() : '';
|
|
const codeStr = codeIdx >= 0 ? String(row[codeIdx] || '').trim() : '';
|
|
const priceVal = priceIdx >= 0 ? String(row[priceIdx] || '').trim() : '';
|
|
const lineLenVal = lineLenIdx >= 0 ? String(row[lineLenIdx] || '').trim() : '';
|
|
all.push({ name: nameStr, model: modelStr, count: countNum, type: typeStr, unit: unitStr, code: codeStr, price: priceVal ? Number(priceVal) : undefined, lineLength: lineLenVal });
|
|
}
|
|
}
|
|
}
|
|
return all;
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// 机箱配套
|
|
let request2Data = {
|
|
factor: "机箱配套",
|
|
data: []
|
|
};
|
|
let otherInstrumentCount = 0; // 其他模块采集仪总数
|
|
let vibratingWireSensorCount = 0; // 振弦信号传感器总数
|
|
let signal485SensorCount = 0; // 485信号传感器总数
|
|
let multiChannelVibratingWireRecorder = 0 // 多通道振弦采集仪
|
|
let singleChannelVibratingWireAcquisitionInstrumentCount = 0 // 单通道振弦采集仪
|
|
for (const item of request1Data) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
if (sensor.name.includes("采集仪")) {
|
|
otherInstrumentCount += Number(sensor.count || 0);
|
|
}
|
|
if (["锚索计", "钢筋计", "土压力计(单膜)", "位移计", "表面式应变计", "内埋式应变计", "孔隙水压计"].includes(sensor.name)) {
|
|
vibratingWireSensorCount += Number(sensor.count || 0);
|
|
}
|
|
if (["激光测距仪", "裂缝计", "静力水准仪", "土壤温湿度传感器", "温湿度传感器", "雨量计"].includes(sensor.name)) {
|
|
signal485SensorCount += Number(sensor.count || 0);
|
|
}
|
|
if (["多通道振弦采集仪"].includes(sensor.name)) {
|
|
multiChannelVibratingWireRecorder += Number(sensor.count || 0);
|
|
}
|
|
if (["单通道振弦采集仪"].includes(sensor.name)) {
|
|
singleChannelVibratingWireAcquisitionInstrumentCount += Number(sensor.count || 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let items = [];
|
|
let hubs = [];
|
|
if (userText['机箱配套']) {
|
|
try {
|
|
const isSolar = true;
|
|
const vw = Number(vibratingWireSensorCount || 0);
|
|
let fd01 = 0, f08 = 0, f16 = 0, f32 = 0;
|
|
const explicitOrFallbackCount = (factorKey, itemName, model, fallback) => {
|
|
const direct = Number(userText[factorKey]?.[itemName]);
|
|
if (!isNaN(direct) && direct > 0) return direct;
|
|
const modelKey = `${itemName}|${model || ''}`;
|
|
const byModel = Number(userText[factorKey]?.[modelKey] || userText[factorKey]?.[model]);
|
|
if (!isNaN(byModel) && byModel > 0) return byModel;
|
|
return Number(fallback || 0);
|
|
};
|
|
if (vw > 0) {
|
|
if (vw <= 2) {
|
|
fd01 = vw;
|
|
} else if (vw <= 32) {
|
|
f08 = Math.ceil(vw / 8);
|
|
} else if (vw <= 64) {
|
|
f16 = Math.floor(vw / 16);
|
|
const r = vw % 16;
|
|
if (r > 0) {
|
|
if (r <= 8) {
|
|
f08 += Math.ceil(r / 8);
|
|
} else {
|
|
f16 += Math.ceil(r / 16);
|
|
}
|
|
}
|
|
} else {
|
|
f32 = Math.floor(vw / 32);
|
|
const r = vw % 32;
|
|
if (r > 0) {
|
|
if (r <= 8) {
|
|
f08 += Math.ceil(r / 8);
|
|
} else {
|
|
f16 += Math.ceil(r / 16);
|
|
}
|
|
}
|
|
}
|
|
singleChannelVibratingWireAcquisitionInstrumentCount = fd01;
|
|
multiChannelVibratingWireRecorder = f08 + f16 + f32;
|
|
if (fd01 > 0) {
|
|
const c = explicitOrFallbackCount('机箱配套', '单通道振弦采集模块', 'FS-FD01', fd01);
|
|
if (c > 0) {
|
|
items.push({ name: '单通道振弦采集模块', model: 'FS-FD01', count: c });
|
|
// items.push({ name: '单通道振弦采集系统V1.0', model: 'FS-FD01-内置软件', count: c });
|
|
}
|
|
}
|
|
if (f08 > 0) {
|
|
const c = explicitOrFallbackCount('机箱配套', '多通道振弦采集仪', 'FS-F08', f08);
|
|
if (c > 0) {
|
|
items.push({ name: '多通道振弦采集仪', model: 'FS-F08', count: c });
|
|
// items.push({ name: '多通道振弦采集仪原位监测系统V1.0', model: 'FS-F08-内置软件', count: c });
|
|
}
|
|
}
|
|
if (f16 > 0) {
|
|
const c = explicitOrFallbackCount('机箱配套', '多通道振弦采集仪', 'FS-F16', f16);
|
|
if (c > 0) {
|
|
items.push({ name: '多通道振弦采集仪', model: 'FS-F16', count: c });
|
|
// items.push({ name: '多通道振弦采集仪原位监测系统V1.0', model: 'FS-F16-内置软件', count: c });
|
|
}
|
|
}
|
|
if (f32 > 0) {
|
|
const c = explicitOrFallbackCount('机箱配套', '多通道振弦采集仪', 'FS-F32', f32);
|
|
if (c > 0) {
|
|
items.push({ name: '多通道振弦采集仪', model: 'FS-F32', count: c });
|
|
// items.push({ name: '多通道振弦采集仪原位监测系统V1.0', model: 'FS-F32-内置软件', count: c });
|
|
}
|
|
}
|
|
const s485 = Number(signal485SensorCount || 0);
|
|
if (s485 > 0) {
|
|
if (s485 <= 8) {
|
|
const c4 = Math.ceil(s485 / 4);
|
|
const hc = explicitOrFallbackCount('机箱配套', 'RS485集线器', 'FS-485JXQ-04-A', c4);
|
|
if (hc > 0) hubs.push({ name: 'RS485集线器', model: 'FS-485JXQ-04-A', count: hc });
|
|
} else {
|
|
const c8 = Math.floor(s485 / 8);
|
|
const hc8 = explicitOrFallbackCount('机箱配套', 'RS485集线器', 'FS-485JXQ-08-A', c8);
|
|
if (hc8 > 0) hubs.push({ name: 'RS485集线器', model: 'FS-485JXQ-08-A', count: hc8 });
|
|
const r = s485 % 8;
|
|
if (r > 0) {
|
|
const c4 = Math.ceil(r / 4);
|
|
const hc4 = explicitOrFallbackCount('机箱配套', 'RS485集线器', 'FS-485JXQ-04-A', c4);
|
|
if (hc4 > 0) hubs.push({ name: 'RS485集线器', model: 'FS-485JXQ-04-A', count: hc4 });
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
}
|
|
const instrumentCountForBreaker = items
|
|
.filter(x => x.name === '多通道振弦采集仪' || x.name === '单通道振弦采集模块')
|
|
.reduce((sum, x) => sum + Number(x.count || 0), 0);
|
|
const basics = [
|
|
{ name: '断路器', model: 'DZ47-60', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '12V直流电源防雷器', model: '12V', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '数据采集箱', model: 'FS-CJX03-A', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '接地电缆', model: 'BVR 1*16', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 10 },
|
|
{ name: 'GPRS无线模块', model: 'FS-DTU-4G-W-V1.00', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '物联网卡', model: '20G', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '接地桩(镀锌角铁)', model: 'FS-DXJT-2', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 3 },
|
|
{ name: '镀锌扁铁', model: 'FS-DXBL-40*40', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 10 },
|
|
{ name: '降阻剂', model: '25kg/袋', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 10 },
|
|
];
|
|
const mains = isSolar ? [] : [
|
|
{ name: '电源电涌保护器', model: 'AM2-40', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '开关电源1', model: '25-12-A', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '导轨插座', model: '10A', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '220V交流供电电缆', model: 'RVV 3*2.5', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 100 },
|
|
];
|
|
const breakerCount = Number(otherInstrumentCount || 0) + instrumentCountForBreaker;
|
|
request2Data = { factor: "机箱配套", data: breakerCount > 0 ? items.concat(hubs).concat(basics).concat(mains) : [] };
|
|
} catch (error) {
|
|
console.error('机箱配套 calculation failed', error);
|
|
}
|
|
}
|
|
|
|
let collectingBoxCount = 0; // 采集箱总数
|
|
let vibratingWireAcquisitionInstrumentCount = 0; // 振弦采集仪总数
|
|
let dataAcquisitionInstrumentCount = otherInstrumentCount; // 数据采集仪总数
|
|
let GNSSCount = 0; // GNSS接收机总数
|
|
let PVCNum = Number(userText['通信系统']?.['PVC管长度'] || 0); // PVC管总数
|
|
let fiberOpticsLength = Number(userText['通信系统']?.['通信光缆(光纤)长度'] || 0); // 通信光缆(光纤)长度
|
|
for (const item of request1Data.concat([request2Data])) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
if (sensor.name === "数据采集箱") {
|
|
collectingBoxCount += Number(sensor.count || 0);
|
|
}
|
|
const n1 = String(sensor.name || '');
|
|
const m1 = String(sensor.model || '');
|
|
if (n1.includes("振弦采集仪") && !(n1.includes('原位监测系统') && n1.includes('V1.0')) && !m1.includes('内置软件')) {
|
|
vibratingWireAcquisitionInstrumentCount += Number(sensor.count || 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (vibratingWireAcquisitionInstrumentCount === 0) {
|
|
vibratingWireAcquisitionInstrumentCount = Number(multiChannelVibratingWireRecorder || 0) + Number(singleChannelVibratingWireAcquisitionInstrumentCount || 0);
|
|
}
|
|
|
|
// 通信系统
|
|
let request3Data = {
|
|
factor: "通信系统",
|
|
data: []
|
|
};
|
|
if (userText['通信系统']) {
|
|
try {
|
|
let data = tunnelPrompts['通信系统'].getManifest([collectingBoxCount, vibratingWireAcquisitionInstrumentCount, dataAcquisitionInstrumentCount, GNSSCount, PVCNum, fiberOpticsLength]);
|
|
request3Data = { factor: "通信系统", data: data || [] };
|
|
} catch (error) {
|
|
console.error('通信系统 calculation failed', error);
|
|
request3Data = { factor: "通信系统", data: [] };
|
|
}
|
|
}
|
|
|
|
// 太阳能计算
|
|
let request5Data = null;
|
|
let solarEnergy = {
|
|
solarEnergyTitle: null,
|
|
solarEnergyData: null,
|
|
powerTitle: null,
|
|
powerData: null,
|
|
};
|
|
const materials = await models.Materials.findAll({ raw: true, where: { structureType: structureType } });
|
|
if (userText['电力系统(太阳能)']) {
|
|
const request4Params = {
|
|
'静力水准仪': 0,
|
|
'裂缝计': 0,
|
|
'土壤温湿度传感器': 0,
|
|
'温湿度传感器': 0,
|
|
'雨量计': 0,
|
|
'激光测距仪': 0,
|
|
'多通道振弦采集仪': 0,
|
|
'数据采集系统V1.0': 0,
|
|
'GPRS无线模块': 0,
|
|
'工业级光纤收发器': 0,
|
|
'串口服务器': 0,
|
|
'工业级交换机': 0,
|
|
'4G网络球机': 0,
|
|
'硬盘录像机': 0,
|
|
}
|
|
for (const item of request1Data.concat([request2Data, request3Data])) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
for (const key in request4Params) {
|
|
if (sensor.name === key) {
|
|
request4Params[key] += Number(sensor.count || 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const regionName = String(userText['电力系统(太阳能)']?.['地区名称'] || '');
|
|
const region = await superagent
|
|
.post(`${apiUrl}/api/v1/chat/completions`)
|
|
.send({
|
|
"stream": false,
|
|
"detail": false,
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": regionName
|
|
}
|
|
]
|
|
}
|
|
]
|
|
})
|
|
.set({
|
|
Authorization: `Bearer ${regionAppKey}`,
|
|
"Content-Type": "application/json",
|
|
})
|
|
.timeout({
|
|
response: 1000 * 60 * 30, // 等待服务器发送数据的超时时间(毫秒)
|
|
deadline: 1000 * 60 * 30, // 整个请求完成的最大时间(毫秒)
|
|
})
|
|
const regionType = JSON.parse(region?.body?.choices[0]?.message?.content?.trim()).region;
|
|
const batterySafetyFactorMap = { '北方地区': 1.1, '南方平原': 1.1, '南方山区': 1.4 };
|
|
const tempCorrectionFactorMap = { '北方地区': 1.2, '南方平原': 1.0, '南方山区': 1.1 };
|
|
const batterySafetyFactor = batterySafetyFactorMap[regionType];
|
|
const tempCorrectionFactor = tempCorrectionFactorMap[regionType];
|
|
const deviceList = [
|
|
{ name: '静力水准仪', model: 'FS-JLSZ-20-V1.00', power: 0.24, key: '静力水准仪' },
|
|
{ name: '裂缝计', model: 'FS-LF10-Z', power: 0.24, key: '裂缝计' },
|
|
{ name: '土壤温湿度传感器', model: 'FS-TRWSD', power: 0.576, key: '土壤温湿度传感器' },
|
|
{ name: '温湿度传感器', model: 'FS-BDS-WSD', power: 0.24, key: '温湿度传感器' },
|
|
{ name: '雨量计', model: 'FS-FDYLJ', power: 1.2, key: '雨量计' },
|
|
{ name: '激光测距仪', model: 'FS-LRF-V1.00', power: 0.4, key: '激光测距仪' },
|
|
{ name: '多通道振弦采集仪', model: 'FS-F08', power: 2.56, key: '多通道振弦采集仪' },
|
|
{ name: '数据采集系统V1.0', model: 'FS-D04', power: 1.7, key: '数据采集系统V1.0' },
|
|
{ name: 'GPRS无线模块', model: 'FS-DTU-4G-W-V1.00', power: 0.96, key: 'GPRS无线模块' },
|
|
{ name: '工业级光纤收发器', model: 'SKMSW-02011L', power: 6, key: '工业级光纤收发器' },
|
|
{ name: '串口服务器', model: '1D(RS485/232)', power: 0.84, key: '串口服务器' },
|
|
{ name: '工业级交换机', model: '', power: 2.5, key: '工业级交换机' },
|
|
{ name: '4G网络球机', model: '3寸 400万', power: 15, key: '4G网络球机' },
|
|
{ name: '硬盘录像机', model: '4路1盘位', power: 18, key: '硬盘录像机' },
|
|
];
|
|
solarEnergy.powerTitle = '功率统计表';
|
|
const powerHeaders = ['序号', '设备名称', '默认型号', '单位', '功率(W)', '数量', '总功率(W)'];
|
|
const powerRows = [];
|
|
const unitOverride = {
|
|
'多通道振弦采集仪|FS-F08': '台(8口)',
|
|
'数据采集系统V1.0|FS-D04': '台(4口)',
|
|
};
|
|
const getUnit = (name, model) => {
|
|
const overrideKey = `${name}|${model || ''}`;
|
|
if (unitOverride[overrideKey]) return unitOverride[overrideKey];
|
|
let m = materials.find(m => m.name === name && (model ? m.model === model : true));
|
|
if (!m && model) m = materials.find(m => m.model === model);
|
|
if (!m) m = materials.find(m => m.name === name);
|
|
return m?.unit || '';
|
|
};
|
|
let totalPower = 0;
|
|
for (let i = 0; i < deviceList.length; i++) {
|
|
const d = deviceList[i];
|
|
const qty = Number(request4Params[d.key] || 0);
|
|
const rowPower = Number(d.power || 0);
|
|
const rowTotalPower = Number((rowPower * qty).toFixed(6));
|
|
totalPower += rowTotalPower;
|
|
const unit = getUnit(d.name, d.model);
|
|
powerRows.push([
|
|
String(i + 1),
|
|
d.name,
|
|
d.model,
|
|
unit,
|
|
String(rowPower),
|
|
String(qty),
|
|
String(rowTotalPower)
|
|
]);
|
|
}
|
|
solarEnergy.powerData = { headers: powerHeaders, rows: powerRows };
|
|
const days = Number(userText['电力系统(太阳能)']?.['连续阴雨天数'] || 0) || 0;
|
|
const dailyConsumption = totalPower * 24;
|
|
const averageDailyConsumption = totalPower * 2;
|
|
const batteryCapacity = batterySafetyFactor * averageDailyConsumption * days * tempCorrectionFactor / 0.7;
|
|
let batteryCount = Math.ceil(batteryCapacity / 120);
|
|
const singlePanelPower = 200;
|
|
const sunlightHours = 4;
|
|
const singlePanelDailySupply = singlePanelPower * sunlightHours * 0.63;
|
|
const deviceDailyConsumption = dailyConsumption / 0.9;
|
|
let solarPanelCount = Math.ceil(deviceDailyConsumption / singlePanelDailySupply);
|
|
solarEnergy.solarEnergyTitle = regionType;
|
|
const solarHeaders = ['总功率', '日耗电量', '日平均耗电量', '连续阴雨天', '蓄电池容量安全系数', '温度修正系数', '电池容量', '电池数量', '单块太阳能功率', '日平均光照时间', '设备单日耗电量', '太阳能板单日供电量', '太阳能板数量'];
|
|
const solarRow = [
|
|
String(Number(totalPower.toFixed(6))),
|
|
String(Number(dailyConsumption.toFixed(6))),
|
|
String(Number(averageDailyConsumption.toFixed(6))),
|
|
String(days),
|
|
String(batterySafetyFactor),
|
|
String(tempCorrectionFactor),
|
|
String(Number(batteryCapacity.toFixed(6))),
|
|
String(batteryCount),
|
|
String(singlePanelPower),
|
|
String(sunlightHours),
|
|
String(Number(deviceDailyConsumption.toFixed(6))),
|
|
String(Number(singlePanelDailySupply.toFixed(6))),
|
|
String(solarPanelCount)
|
|
];
|
|
solarEnergy.solarEnergyData = { headers: solarHeaders, rows: [solarRow] };
|
|
try {
|
|
const data = tunnelPrompts['电力系统(太阳能)'].getManifest([solarPanelCount, batteryCount]) || [];
|
|
const controllerLoad = (200 / 18) * solarPanelCount;
|
|
const controllerModel = controllerLoad < 10 ? '12V10A' : controllerLoad < 30 ? '12V30A' : '12V50A';
|
|
const controllerCount = Math.ceil((200 * solarPanelCount) / 900);
|
|
const controllerDesc = '太阳能控制器:根据太阳能控制器选型计算公式从“12V10A、12V30A、12V50A”中选择一个合适的型号。默认太阳能板功率为200,太阳能控制器选型计算公式:(太阳能板功率/18)*太阳能板数量,根据该公式的计算结果选择对应型号:如果计算结果<10,选择太阳能控制器12V10A;如果计算结果>=10且<30选择太阳能控制器12V30A;如果计算结果>=30选择太阳能控制器12V50A。太阳能控制器数量=太阳能板功率*太阳能板数量/900(向上取整)。';
|
|
for (let k = 0; k < data.length; k++) {
|
|
const it = data[k];
|
|
if (String(it?.name) === '太阳能控制器') {
|
|
it.model = controllerModel;
|
|
it.count = controllerCount;
|
|
}
|
|
}
|
|
request5Data = { data };
|
|
} catch (error) {
|
|
console.error('电力系统(太阳能) calculation failed', error);
|
|
}
|
|
}
|
|
|
|
const allListData = request1Data.concat([request2Data, request3Data]);
|
|
if (request5Data) { allListData.push(request5Data) }
|
|
|
|
// 工期核算清单
|
|
const totalCountMap = {};
|
|
for (const item of allListData) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
const k = sensor.name;
|
|
const v = Number(sensor.count || 0);
|
|
totalCountMap[k] = (totalCountMap[k] || 0) + v;
|
|
}
|
|
}
|
|
}
|
|
const cableSum = Object.entries(totalCountMap).reduce((s, [k, v]) => s + ((String(k).includes('通信电缆') || String(k).includes('接地电缆')) ? Number(v || 0) : 0), 0);
|
|
const installQtyMap = {
|
|
'表面式应变计': 8,
|
|
'内埋式应变计': 8,
|
|
'钢筋计': 8,
|
|
'土压力计': 6.4,
|
|
'孔隙水压计': 8,
|
|
'轴力计': 2,
|
|
'锚索计': 2,
|
|
'温度传感器': 8,
|
|
'温湿度传感器': 4,
|
|
'土壤温湿度传感器': 4,
|
|
'闭环磁通量标定': 8,
|
|
'开环磁通量绕线(小)': 3,
|
|
'开环磁通量绕线(中)': 1.6,
|
|
'开环磁通量绕线(大)': 0.7,
|
|
'人工磁通量温补': 5.3,
|
|
'盒式固定测斜仪': 5.3,
|
|
'串联式固定测斜仪': 6,
|
|
'裂缝计': 5.3,
|
|
'静力水准仪': 6.4,
|
|
'物位计': 6.4,
|
|
'雷达液位计': 2,
|
|
'拉线位移传感器': 3.2,
|
|
'多点位移计(振弦式)': 2.7,
|
|
'加速度计': 5.3,
|
|
'激光测距仪': 5,
|
|
'投入式水位计': 1.5,
|
|
'超声波水位计': 2,
|
|
'雨量计': 2,
|
|
'超声波风速风向仪': 2.5,
|
|
'光电挠度仪': 0.7,
|
|
'全站仪': 0.3,
|
|
'钢尺水位计': 2,
|
|
'一体化裂缝计(成品)': 2.7,
|
|
'一体化地灾(加速度计)': 4,
|
|
'一体化地灾倾角计': 4,
|
|
'GNSS接收机': 0.9,
|
|
'4G球机': 2,
|
|
'管式含水率仪': 4,
|
|
'分布式节点': 4,
|
|
'称重系统(1套2车道)': 0.3,
|
|
'拼接屏(4*6)': 0.3,
|
|
'太阳能供电': 1,
|
|
'监控中心(3天)': 3.2,
|
|
'墩座1': 4,
|
|
'墩座2': 4,
|
|
'墩座3': 2.7,
|
|
'墩座4': 2,
|
|
'基础1(具备挖机、吊车)': 0.7,
|
|
'基础2': 2.7,
|
|
'基础3': 2.7,
|
|
'基础4': 2,
|
|
'地网': 1,
|
|
'线缆(h/100米)': 8.4,
|
|
'桥架1(h/100米)': 1.3,
|
|
'桥架2(h/100米)': 1.3,
|
|
'PVC管1(h/100米)': 5.3,
|
|
'PVC管2(h/100米)': 5.3
|
|
};
|
|
const inputQtyMap = {
|
|
'表面式应变计': Number(totalCountMap['表面式应变计'] || 0),
|
|
'内埋式应变计': Number(totalCountMap['内埋式应变计'] || 0),
|
|
'钢筋计': Number(totalCountMap['钢筋计'] || 0),
|
|
'土压力计': Number(totalCountMap['土压力计(单膜)'] || 0),
|
|
'孔隙水压计': Number(totalCountMap['孔隙水压计'] || 0),
|
|
'轴力计': Number(totalCountMap['轴力计'] || 0),
|
|
'锚索计': Number(totalCountMap['锚索计'] || 0),
|
|
'温度传感器': Number(totalCountMap['温度传感器'] || 0),
|
|
'温湿度传感器': Number(totalCountMap['温湿度传感器'] || 0),
|
|
'土壤温湿度传感器': Number(totalCountMap['土壤温湿度传感器'] || 0),
|
|
'闭环磁通量标定': 0,
|
|
'开环磁通量绕线(小)': 0,
|
|
'开环磁通量绕线(中)': 0,
|
|
'开环磁通量绕线(大)': 0,
|
|
'人工磁通量温补': 0,
|
|
'盒式固定测斜仪': Number(totalCountMap['盒式固定测斜仪'] || 0),
|
|
'串联式固定测斜仪': Number(totalCountMap['串联式固定测斜仪'] || 0),
|
|
'裂缝计': Number(totalCountMap['裂缝计'] || 0),
|
|
'静力水准仪': Number(totalCountMap['静力水准仪'] || 0),
|
|
'物位计': Number(totalCountMap['物位计'] || 0),
|
|
'雷达液位计': Number(totalCountMap['雷达液位计'] || 0),
|
|
'拉线位移传感器': Number(totalCountMap['拉线位移传感器'] || 0),
|
|
'多点位移计(振弦式)': Number(totalCountMap['多点位移计(振弦式)'] || 0),
|
|
'加速度计': Number(totalCountMap['加速度计'] || 0),
|
|
'激光测距仪': Number(totalCountMap['激光测距仪'] || 0),
|
|
'投入式水位计': Number(totalCountMap['投入式水位计'] || 0),
|
|
'超声波水位计': Number(totalCountMap['超声波水位计'] || 0),
|
|
'雨量计': Number(totalCountMap['雨量计'] || 0),
|
|
'超声波风速风向仪': Number(totalCountMap['超声波风速风向仪'] || 0),
|
|
'光电挠度仪': Number(totalCountMap['光电挠度仪'] || 0),
|
|
'全站仪': Number(totalCountMap['全站仪'] || 0),
|
|
'钢尺水位计': Number(totalCountMap['钢尺水位计'] || 0),
|
|
'一体化裂缝计(成品)': Number(totalCountMap['一体化裂缝计(成品)'] || 0),
|
|
'一体化地灾(加速度计)': Number(totalCountMap['一体化地灾(加速度计)'] || 0),
|
|
'一体化地灾倾角计': Number(totalCountMap['一体化地灾倾角计'] || 0),
|
|
'GNSS接收机': Number(totalCountMap['测地型GNSS接收机'] || 0),
|
|
'4G球机': Number(totalCountMap['4G网络球机'] || 0),
|
|
'管式含水率仪': Number(totalCountMap['管式含水率仪'] || 0),
|
|
'分布式节点': Number(totalCountMap['分布式节点'] || 0),
|
|
'称重系统(1套2车道)': Number(totalCountMap['两车道(全套)'] || 0),
|
|
'拼接屏(4*6)': Number(totalCountMap['拼接屏(4*6)'] || 0),
|
|
'太阳能供电': Number(totalCountMap['太阳能控制器'] || 0),
|
|
'监控中心(3天)': 0,
|
|
'墩座1': Number(totalCountMap['墩座1'] || 0),
|
|
'墩座2': Number(totalCountMap['墩座2'] || 0),
|
|
'墩座3': Number(totalCountMap['墩座3'] || 0),
|
|
'墩座4': Number(totalCountMap['墩座4'] || 0),
|
|
'基础1(具备挖机、吊车)': Number(totalCountMap['基础1(具备挖机、吊车)'] || 0),
|
|
'基础2': Number(totalCountMap['基础2'] || 0),
|
|
'基础3': Number(totalCountMap['基础3'] || 0),
|
|
'基础4': Number(totalCountMap['基础4'] || 0),
|
|
'地网': Number(totalCountMap['接地桩(镀锌角铁)'] || 0),
|
|
'线缆(h/100米)': Number(cableSum || 0),
|
|
'桥架1(h/100米)': Number(totalCountMap['桥架'] || 0),
|
|
'桥架2(h/100米)': 0,
|
|
'PVC管1(h/100米)': Number(PVCNum || 0),
|
|
'PVC管2(h/100米)': 0
|
|
};
|
|
const sensorsForDebug = [
|
|
'表面式应变计',
|
|
'内埋式应变计',
|
|
'温度传感器',
|
|
'温湿度传感器',
|
|
'盒式固定测斜仪',
|
|
'裂缝计',
|
|
'静力水准仪',
|
|
'拉线位移传感器',
|
|
'超声波风速风向仪',
|
|
'光电挠度仪',
|
|
'GNSS接收机',
|
|
'4G球机'
|
|
];
|
|
const sensorTotal = sensorsForDebug.reduce((sum, k) => sum + Number(inputQtyMap[k] || 0), 0);
|
|
const systemDebugDays = sensorTotal <= 50 ? 2 : sensorTotal <= 100 ? 3 : 5;
|
|
const acceptanceTrainingDays = sensorTotal <= 100 ? 3 : 5;
|
|
const projectRiskDays = sensorTotal <= 100 ? 2 : 3;
|
|
const headers6 = ['序号', '分类', '事项', '耗时(小时/人)', '安装数量/个(2人8小时计)', '备注', '数量输入', '工期(2人天)'];
|
|
const orderedItems = [
|
|
'表面式应变计',
|
|
'内埋式应变计',
|
|
'钢筋计',
|
|
'土压力计',
|
|
'孔隙水压计',
|
|
'轴力计',
|
|
'锚索计',
|
|
'温度传感器',
|
|
'温湿度传感器',
|
|
'土壤温湿度传感器',
|
|
'闭环磁通量标定',
|
|
'开环磁通量绕线(小)',
|
|
'开环磁通量绕线(中)',
|
|
'开环磁通量绕线(大)',
|
|
'人工磁通量温补',
|
|
'盒式固定测斜仪',
|
|
'串联式固定测斜仪',
|
|
'裂缝计',
|
|
'静力水准仪',
|
|
'物位计',
|
|
'雷达液位计',
|
|
'拉线位移传感器',
|
|
'多点位移计(振弦式)',
|
|
'加速度计',
|
|
'激光测距仪',
|
|
'投入式水位计',
|
|
'超声波水位计',
|
|
'雨量计',
|
|
'超声波风速风向仪',
|
|
'光电挠度仪',
|
|
'全站仪',
|
|
'钢尺水位计',
|
|
'一体化裂缝计(成品)',
|
|
'一体化地灾(加速度计)',
|
|
'一体化地灾倾角计',
|
|
'GNSS接收机',
|
|
'4G球机',
|
|
'管式含水率仪',
|
|
'分布式节点',
|
|
'称重系统(1套2车道)',
|
|
'拼接屏(4*6)',
|
|
'太阳能供电',
|
|
'监控中心(3天)',
|
|
'墩座1',
|
|
'墩座2',
|
|
'墩座3',
|
|
'墩座4',
|
|
'基础1(具备挖机、吊车)',
|
|
'基础2',
|
|
'基础3',
|
|
'基础4',
|
|
'地网',
|
|
'线缆(h/100米)',
|
|
'桥架1(h/100米)',
|
|
'桥架2(h/100米)',
|
|
'PVC管1(h/100米)',
|
|
'PVC管2(h/100米)',
|
|
'系统调试',
|
|
'外包(天)',
|
|
'验收、培训',
|
|
'项目风险天数(设备转场、开路、技术难点等)',
|
|
'项目经理(天)'
|
|
];
|
|
function getCategory(name) {
|
|
const sensorSet = new Set([
|
|
'表面式应变计', '内埋式应变计', '钢筋计', '土压力计', '孔隙水压计', '轴力计', '锚索计',
|
|
'温度传感器', '温湿度传感器', '土壤温湿度传感器', '闭环磁通量标定', '开环磁通量绕线(小)',
|
|
'开环磁通量绕线(中)', '开环磁通量绕线(大)', '人工磁通量温补', '盒式固定测斜仪', '串联式固定测斜仪',
|
|
'裂缝计', '静力水准仪', '物位计', '雷达液位计', '拉线位移传感器', '多点位移计(振弦式)',
|
|
'加速度计', '激光测距仪', '投入式水位计', '超声波水位计', '雨量计', '超声波风速风向仪',
|
|
'光电挠度仪', '全站仪', '钢尺水位计', '一体化裂缝计(成品)', '一体化地灾(加速度计)',
|
|
'一体化地灾倾角计', '振动传感器', 'GNSS接收机', '4G球机'
|
|
]);
|
|
const deviceSet = new Set(['分布式节点', '称重系统(1套2车道)', '拼接屏(4*6)', '太阳能供电', '监控中心(3天)']);
|
|
const civilSet = new Set(['墩座1', '墩座2', '墩座3', '墩座4', '基础1(具备挖机、吊车)', '基础2', '基础3', '基础4', '地网']);
|
|
if (sensorSet.has(name)) return '传感器类';
|
|
if (deviceSet.has(name)) return '设备类';
|
|
if (civilSet.has(name)) return '土建类(地笼、地网、基座、立杆、熔纤)';
|
|
if (name.endsWith('(h/100米)') || name === '系统调试') return '集成';
|
|
if (name === '外包(天)') return '外包(天)';
|
|
if (name === '验收、培训') return '验收及培训';
|
|
if (name === '项目风险天数(设备转场、开路、技术难点等)') return '风险天数';
|
|
if (name === '项目经理(天)') return '项目经理(天)';
|
|
return '其他类';
|
|
}
|
|
const hoursPerPersonMap = {
|
|
'表面式应变计': '2',
|
|
'内埋式应变计': '2',
|
|
'钢筋计': '2',
|
|
'土压力计': '2.5',
|
|
'孔隙水压计': '2',
|
|
'轴力计': '8',
|
|
'锚索计': '8',
|
|
'温度传感器': '2',
|
|
'温湿度传感器': '4',
|
|
'土壤温湿度传感器': '4',
|
|
'闭环磁通量标定': '2',
|
|
'开环磁通量绕线(小)': '4',
|
|
'开环磁通量绕线(中)': '10',
|
|
'开环磁通量绕线(大)': '24',
|
|
'人工磁通量温补': '3',
|
|
'盒式固定测斜仪': '3',
|
|
'串联式固定测斜仪': '2',
|
|
'裂缝计': '3',
|
|
'静力水准仪': '2.5',
|
|
'拉线位移传感器': '5',
|
|
'多点位移计(振弦式)': '6',
|
|
'振动传感器': '3',
|
|
'激光测距仪': '3',
|
|
'投入式水位计': '12',
|
|
'超声波水位计': '8',
|
|
'雨量计': '8',
|
|
'超声波风速风向仪': '6',
|
|
'光电挠度仪': '24',
|
|
'全站仪': '48',
|
|
'钢尺水位计': '8',
|
|
'GNSS接收机': '18',
|
|
'4G球机': '8',
|
|
'分布式节点': '4',
|
|
'称重系统(1套2车道)': '64',
|
|
'拼接屏(4*6)': '48',
|
|
'太阳能供电': '16',
|
|
'监控中心(3天)': '5',
|
|
'墩座1': '4',
|
|
'墩座2': '4',
|
|
'墩座3': '6',
|
|
'墩座4': '8',
|
|
'基础1(具备挖机、吊车)': '24',
|
|
'基础2': '6',
|
|
'基础3': '6',
|
|
'基础4': '8',
|
|
'地网': '16',
|
|
'线缆(h/100米)': '1.9',
|
|
'桥架1(h/100米)': '12',
|
|
'桥架2(h/100米)': '16',
|
|
'PVC管1(h/100米)': '3',
|
|
'PVC管2(h/100米)': '8',
|
|
};
|
|
function getHoursPerPerson(name) {
|
|
return hoursPerPersonMap[name] ?? '';
|
|
}
|
|
const remarksMap = {
|
|
'土压力计': '根据挖槽深度、难度',
|
|
'轴力计': '含加预应力过程时间',
|
|
'锚索计': '含张拉过程时间',
|
|
'闭环磁通量标定': '需另外加上集成调试时间,固定传感器时间',
|
|
'开环磁通量绕线(小)': '内径50以下',
|
|
'开环磁通量绕线(中)': '内径50-100左右',
|
|
'开环磁通量绕线(大)': '内径100以上',
|
|
'人工磁通量温补': '距离近可转场按照1天6个计算,不需要外包',
|
|
'串联式固定测斜仪': '单位:串',
|
|
'多点位移计(振弦式)': '含打孔注浆',
|
|
'光电挠度仪': '需要参考标靶数量',
|
|
'全站仪': '全站仪安装3天计(含基础及电脑),含棱镜',
|
|
'钢尺水位计': '含混凝土立柱',
|
|
'GNSS接收机': '含基础浇筑',
|
|
'4G球机': '混凝土凝固,折返时间',
|
|
'监控中心(3天)': '监控中心整体考虑2人3天',
|
|
'墩座1': '雨量计:300*300*250mm',
|
|
'墩座2': '测斜保护墩:250*250*200mm',
|
|
'墩座3': '采集箱底座:600*300*200mm',
|
|
'墩座4': 'GNSS混凝土墩:1800*315mm(高*直径)',
|
|
'基础1(具备挖机、吊车)': '12米横臂拍立杆:1800*1800*2000mm',
|
|
'基础2': '视频立杆:300*300*800mm',
|
|
'基础3': '雷达液位计立杆基础:300*300*800mm',
|
|
'基础4': 'GNSS钢立柱基础:600*600*800mm',
|
|
'地网': '标准防雷地网(土质)',
|
|
'线缆(h/100米)': '需要机械,线缆及水管气管',
|
|
'桥架1(h/100米)': '不需机械脚手架',
|
|
'桥架2(h/100米)': '需机械脚手架',
|
|
'PVC管1(h/100米)': '不需机械脚手架',
|
|
'PVC管2(h/100米)': '需机械脚手架',
|
|
'系统调试': '传感器≤50,按2天;100≥传感器数量>50,按4天;传感器数量>100,按5天',
|
|
'验收、培训': '传感器≤100,按3天;传感器数量>100,按4天',
|
|
'项目风险天数(设备转场、开路、技术难点等)': '传感器≤100,按2天;传感器数量>100,按5天',
|
|
};
|
|
function getInstallDisplay(name, installQty, qty) {
|
|
// if (name.endsWith('(h/100米)')) {
|
|
// const v = Number(((installQty || 0) * (qty || 0) / 100).toFixed(3));
|
|
// return String(v);
|
|
// }
|
|
if (name === '地网') return '1';
|
|
if (name === '系统调试') return '2-5天';
|
|
if (name === '验收、培训') return '2-4天';
|
|
if (name === '项目风险天数(设备转场、开路、技术难点等)') return '2-5天';
|
|
if (name === '外包(天)' || name === '项目经理(天)') return '';
|
|
return String(installQty ?? '');
|
|
}
|
|
function installRuleText(name) {
|
|
if (name === '地网') return '安装数量×数量输入÷3';
|
|
if (name.endsWith('(h/100米)')) return '安装数量×数量输入÷100';
|
|
if (name === '监控中心(3天)') return '固定3天';
|
|
if (name === '系统调试') return '总数≤50→2;≤100→3;>100→5';
|
|
if (name === '验收、培训') return '总数≤100→3;>100→5';
|
|
if (name === '项目风险天数(设备转场、开路、技术难点等)') return '总数≤100→2;>100→3';
|
|
return '数量输入÷安装数量';
|
|
}
|
|
function computeDuration(name, installQty, qty) {
|
|
if (name === '地网') return Number(((qty || 0) / 3).toFixed(3));
|
|
if (name.endsWith('(h/100米)')) {
|
|
if (!qty) return 0;
|
|
const installDisplay = (installQty || 0) ? (installQty * qty / 100) : 0;
|
|
if (!installDisplay) return 0;
|
|
return Number(((qty || 0) / installDisplay).toFixed(3));
|
|
}
|
|
if (name === '监控中心(3天)') return 3;
|
|
if (name === '系统调试') return Number(systemDebugDays.toFixed(3));
|
|
if (name === '验收、培训') return Number(acceptanceTrainingDays.toFixed(3));
|
|
if (name === '项目风险天数(设备转场、开路、技术难点等)') return Number(projectRiskDays.toFixed(3));
|
|
if (name === '外包(天)') return 0;
|
|
if (!installQty) return 0;
|
|
return Number(((qty || 0) / installQty).toFixed(3));
|
|
}
|
|
const rows6 = [];
|
|
for (let i = 0; i < orderedItems.length; i++) {
|
|
const name = orderedItems[i];
|
|
const installQty = installQtyMap[name] ?? 0;
|
|
const qty = inputQtyMap[name] ?? 0;
|
|
const durationVal = computeDuration(name, installQty, qty);
|
|
const displayName = (name === '外包(天)' || name === '项目经理(天)') ? '' : name;
|
|
const installDisplay = getInstallDisplay(name, installQty, qty);
|
|
rows6.push([
|
|
String(i + 1),
|
|
getCategory(name),
|
|
displayName,
|
|
getHoursPerPerson(name),
|
|
installDisplay,
|
|
remarksMap[name] ?? '',
|
|
(name === '外包(天)' || name === '项目经理(天)') ? '' : String(Number((qty || 0).toFixed(3))),
|
|
String(durationVal)
|
|
]);
|
|
}
|
|
const epiboleLimitIdx2 = orderedItems.indexOf('系统调试') + 1;
|
|
const epiboleDays = rows6.reduce((acc, row) => {
|
|
const idx = parseInt(row[0], 10);
|
|
if (isNaN(idx) || idx > epiboleLimitIdx2) return acc;
|
|
return acc + Number(row[row.length - 1] || 0);
|
|
}, 0);
|
|
const managerHoursPerDay = Number(userText['工期核算清单']?.['项目经理每日工时'] || 8);
|
|
const projectManagerDays = Number((((epiboleDays + acceptanceTrainingDays + projectRiskDays) * 8) / (managerHoursPerDay || 8)).toFixed(6));
|
|
const durationData = { headers: headers6, rows: rows6 };
|
|
|
|
// 核算要点
|
|
let hasSolar = false
|
|
let request7Power = '';
|
|
let hasFiber = false;
|
|
for (const item of allListData) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
if (sensor.name.includes('太阳能') && !request7Power) {
|
|
request7Power = ',太阳能供电';
|
|
hasSolar = true;
|
|
}
|
|
if (sensor.name.includes('光纤')) {
|
|
request7Power = ',光纤通信';
|
|
hasFiber = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (hasFiber) break;
|
|
}
|
|
|
|
const mainPointData = getMainPointData(userText, epiboleDays, hasSolar, hasFiber);
|
|
|
|
let sheet1Data = request1Data;
|
|
let sheet1Key = request1Key;
|
|
if (userText['机箱配套']) {
|
|
sheet1Data.push(request2Data);
|
|
sheet1Key.push('机箱配套');
|
|
}
|
|
if (userText['通信系统']) {
|
|
sheet1Data.push(request3Data);
|
|
sheet1Key.push('通信系统');
|
|
}
|
|
if (request5Data) {
|
|
sheet1Data.push(request5Data);
|
|
sheet1Key.push('电力系统(太阳能)');
|
|
}
|
|
/********** 2.生成excel **********/
|
|
const tableData = generationXlsxData({
|
|
materials, sheet1Data, sheet1Key,
|
|
solarEnergy: userText['电力系统(太阳能)'] ? solarEnergy : null,
|
|
duration: {
|
|
durationData,
|
|
epiboleDays,
|
|
projectManagerDays
|
|
},
|
|
mainPointData
|
|
});
|
|
|
|
/********** 3.保存数据 **********/
|
|
await models.SchemeList.update(
|
|
{
|
|
status: 'success',
|
|
updateAt: new Date(),
|
|
tableData,
|
|
},
|
|
{ where: { id: schemeId } }
|
|
);
|
|
ctx.logger.log(`方案清单数据生成成功,schemeId:${schemeId}`);
|
|
|
|
await transaction.commit();
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
ctx.logger.log(error);
|
|
try {
|
|
await models.SchemeList.update(
|
|
{ status: 'fail' },
|
|
{ where: { id: schemeId } }
|
|
);
|
|
} catch (err) {
|
|
console.log(`修改状态失败,scheme.id=${schemeId}`);
|
|
ctx.logger.log(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 生成边坡方案清单
|
|
async function generationSlopeScheme(ctx, schemeId, userText, structureType, transmitMethod) {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
const { models } = ctx.app.fs.dc;
|
|
try {
|
|
const { apiUrl } = ctx.app.fs.config.fastGpt;
|
|
const regionAppKey = 'fastgpt-kzqnfXUusbIP6R42we1KMAST9SqBAPCtbAxTV1oQHWRuM7CEb1am2VF7z0'
|
|
|
|
/********** 1.请求数据 **********/
|
|
const allRequestKey = Object.keys(userText);
|
|
|
|
const request1Key = allRequestKey.filter(key => !['机箱配套', '通信系统', '电力系统(太阳能)'].includes(key));
|
|
async function readManifestFromFile(fileUrl) {
|
|
try {
|
|
const res = await superagent
|
|
.get(fileUrl)
|
|
.buffer(true)
|
|
.parse((response, cb) => {
|
|
const data = [];
|
|
response.on('data', (chunk) => {
|
|
data.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
});
|
|
response.on('end', () => cb(null, Buffer.concat(data)));
|
|
response.on('error', (err) => cb(err));
|
|
});
|
|
const buf = res.body;
|
|
const wb = XLSXS.read(buf, { type: 'buffer' });
|
|
const sheetNames = wb.SheetNames || [];
|
|
if (!sheetNames.length) return [];
|
|
const all = [];
|
|
for (let s = 0; s < sheetNames.length; s++) {
|
|
const ws = wb.Sheets[sheetNames[s]];
|
|
if (!ws) continue;
|
|
const rows = XLSXS.utils.sheet_to_json(ws, { header: 1, blankrows: false, defval: '' }) || [];
|
|
let nameIdx = -1, modelIdx = -1, countIdx = -1, startIdx = -1, typeIdx = -1, unitIdx = -1, codeIdx = -1, priceIdx = -1, lineLenIdx = -1;
|
|
const headerMaxScan = Math.min(rows.length, 20);
|
|
for (let i = 0; i < headerMaxScan; i++) {
|
|
const row = rows[i] || [];
|
|
for (let j = 0; j < row.length; j++) {
|
|
const cell = String(row[j] || '').replace(/\s+/g, '');
|
|
if (nameIdx < 0 && (cell === '名称' || cell.indexOf('设备名称') > -1 || cell.indexOf('物料名称') > -1 || cell.indexOf('品名') > -1)) nameIdx = j;
|
|
if (modelIdx < 0 && (cell === '规格/型号' || cell.indexOf('规格') > -1 || cell.indexOf('型号') > -1)) modelIdx = j;
|
|
if (countIdx < 0 && (cell === '数量' || cell.indexOf('数量') > -1 || cell.indexOf('总数') > -1)) countIdx = j;
|
|
if (typeIdx < 0 && (cell === '类型' || cell.indexOf('类型') > -1)) typeIdx = j;
|
|
if (unitIdx < 0 && (cell === '单位' || cell.indexOf('单位') > -1)) unitIdx = j;
|
|
if (codeIdx < 0 && (cell === '物料代码' || cell.indexOf('物料编码') > -1 || cell.indexOf('物料代码') > -1)) codeIdx = j;
|
|
if (priceIdx < 0 && (cell === '单价' || cell.indexOf('单价') > -1 || cell.indexOf('价格') > -1)) priceIdx = j;
|
|
if (lineLenIdx < 0 && (cell.indexOf('线长') > -1)) lineLenIdx = j;
|
|
}
|
|
if (nameIdx >= 0 && countIdx >= 0) { startIdx = i + 1; break; }
|
|
}
|
|
if (startIdx < 0) continue;
|
|
for (let i = startIdx; i < rows.length; i++) {
|
|
const row = rows[i] || [];
|
|
const nameCell = row[nameIdx];
|
|
const rawName = nameCell == null ? '' : String(nameCell);
|
|
const nameStr = rawName.trim();
|
|
if (!nameStr) continue;
|
|
const lower = nameStr.toLowerCase();
|
|
if (nameStr.indexOf('小计') > -1 || nameStr.indexOf('合计') > -1 || lower.indexOf('total') > -1) break;
|
|
const modelStr = modelIdx >= 0 ? String(row[modelIdx] || '').trim() : '';
|
|
const countVal = row[countIdx];
|
|
const countStr = String(countVal == null ? '' : countVal).trim();
|
|
const match = countStr.match(/-?\d+(\.\d+)?/);
|
|
const countNum = match ? Number(match[0]) : Number(countStr || 0);
|
|
if (!isNaN(countNum) && countNum > 0) {
|
|
const typeStr = typeIdx >= 0 ? String(row[typeIdx] || '').trim() : '';
|
|
const unitStr = unitIdx >= 0 ? String(row[unitIdx] || '').trim() : '';
|
|
const codeStr = codeIdx >= 0 ? String(row[codeIdx] || '').trim() : '';
|
|
const priceVal = priceIdx >= 0 ? String(row[priceIdx] || '').trim() : '';
|
|
const lineLenVal = lineLenIdx >= 0 ? String(row[lineLenIdx] || '').trim() : '';
|
|
all.push({ name: nameStr, model: modelStr, count: countNum, type: typeStr, unit: unitStr, code: codeStr, price: priceVal ? Number(priceVal) : undefined, lineLength: lineLenVal });
|
|
}
|
|
}
|
|
}
|
|
return all;
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
const request1Data = await Promise.all(request1Key.map(async key => {
|
|
try {
|
|
const expectSolarParamKeys = new Set([
|
|
'桥墩/桥塔位移监测(北斗)',
|
|
'视频监控(有线)',
|
|
'视频监控(无线1)',
|
|
'机箱配套',
|
|
'表面位移监测',
|
|
]);
|
|
|
|
const isSolarSelected = !!userText['电力系统(太阳能)'];
|
|
const params = [].concat(
|
|
slopePrompts[key].sensors.map(sensor => {
|
|
const v = userText[key]?.[sensor];
|
|
if (v === undefined && /数量|长度|桥长|车道|天数|弦数/.test(sensor)) return 0;
|
|
return v;
|
|
}),
|
|
[expectSolarParamKeys.has(key) ? isSolarSelected : transmitMethod]
|
|
);
|
|
let data = slopePrompts[key].getManifest(params) || [];
|
|
const fileUrl = slopePrompts[key]?.file;
|
|
if (fileUrl) {
|
|
const fileData = await readManifestFromFile(fileUrl);
|
|
const map = new Map();
|
|
const keyFn = (n, m) => `${String(n || '').trim().toLowerCase()}|${String(m || '').trim().toLowerCase()}`;
|
|
for (const it of data || []) map.set(keyFn(it.name, it.model), { name: String(it.name || '').trim(), model: String(it.model || '').trim(), count: Number(it.count || 0), type: it.type, unit: it.unit, code: it.code, price: it.price, lineLength: it.lineLength });
|
|
for (const it of fileData || []) {
|
|
const k = keyFn(it.name, it.model);
|
|
if (map.has(k)) {
|
|
const v = map.get(k);
|
|
if (!v.type && it.type) v.type = it.type;
|
|
if (!v.unit && it.unit) v.unit = it.unit;
|
|
if (!v.code && it.code) v.code = it.code;
|
|
if (v.price == null && it.price != null) v.price = it.price;
|
|
if (v.lineLength == null && it.lineLength != null) v.lineLength = it.lineLength;
|
|
map.set(k, v);
|
|
}
|
|
}
|
|
data = Array.from(map.values());
|
|
}
|
|
return { factor: key, data: data || [] };
|
|
} catch (error) {
|
|
console.error(`Calculation failed for ${key}`, error);
|
|
return { factor: key, data: [] };
|
|
}
|
|
}));
|
|
|
|
for (let i = 0; i < request1Data.length; i++) {
|
|
const it = request1Data[i];
|
|
if (it.factor === '表面位移监测') {
|
|
const cityPowerSet = new Set(['电源电涌保护器', '开关电源1', '导轨插座', '220V交流供电电缆']);
|
|
it.data = (it.data || []).filter(x => !cityPowerSet.has(String(x?.name || '')));
|
|
}
|
|
}
|
|
const opsSelected3 = String(userText['运维服务']?.['选择子服务'] || '').split('、').map(x => x.trim()).filter(Boolean);
|
|
for (let i = 0; i < request1Data.length; i++) {
|
|
const it = request1Data[i];
|
|
if (it.factor === '运维服务' && Array.isArray(it.data)) {
|
|
it.data = opsSelected3.length > 0 ? it.data.filter(row => opsSelected3.includes(String(row?.name || ''))) : [];
|
|
}
|
|
}
|
|
|
|
// 机箱配套
|
|
let request2Data = {
|
|
factor: "机箱配套",
|
|
data: []
|
|
};
|
|
let otherInstrumentCount = 0; // 其他模块采集仪总数
|
|
let vibratingWireSensorCount = 0; // 振弦信号传感器总数
|
|
let signal485SensorCount = 0; // 485信号传感器总数
|
|
for (const item of request1Data) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
if (sensor.name.includes("采集仪")) {
|
|
otherInstrumentCount += Number(sensor.count || 0);
|
|
}
|
|
if (["孔隙水压计", "土压力计(单膜)", "表面式应变计"].includes(sensor.name)) {
|
|
vibratingWireSensorCount += Number(sensor.count || 0);
|
|
}
|
|
if (["串联式固定测斜仪", "物位计", "投入式水位计", "雷达水位计", "雨量计", "温湿度传感器"].includes(sensor.name)) {
|
|
signal485SensorCount += Number(sensor.count || 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let multiChannelVibratingWireRecorder = 0;
|
|
let singleChannelVibratingWireAcquisitionInstrumentCount = 0;
|
|
if (userText['机箱配套']) {
|
|
try {
|
|
const isSolar = true;
|
|
const vw = Number(vibratingWireSensorCount || 0);
|
|
let fd01 = 0, f08 = 0, f16 = 0, f32 = 0;
|
|
const explicitOrFallbackCount = (factorKey, itemName, model, fallback) => {
|
|
const direct = Number(userText[factorKey]?.[itemName]);
|
|
if (!isNaN(direct) && direct > 0) return direct;
|
|
const modelKey = `${itemName}|${model || ''}`;
|
|
const byModel = Number(userText[factorKey]?.[modelKey] || userText[factorKey]?.[model]);
|
|
if (!isNaN(byModel) && byModel > 0) return byModel;
|
|
return Number(fallback || 0);
|
|
};
|
|
let items = [];
|
|
let hubs = [];
|
|
if (vw > 0) {
|
|
if (vw <= 2) {
|
|
fd01 = vw;
|
|
} else if (vw <= 32) {
|
|
f08 = Math.ceil(vw / 8);
|
|
} else if (vw <= 64) {
|
|
f16 = Math.floor(vw / 16);
|
|
const r = vw % 16;
|
|
if (r > 0) {
|
|
if (r <= 8) {
|
|
f08 += Math.ceil(r / 8);
|
|
} else {
|
|
f16 += Math.ceil(r / 16);
|
|
}
|
|
}
|
|
} else {
|
|
f32 = Math.floor(vw / 32);
|
|
const r = vw % 32;
|
|
if (r > 0) {
|
|
if (r <= 8) {
|
|
f08 += Math.ceil(r / 8);
|
|
} else {
|
|
f16 += Math.ceil(r / 16);
|
|
}
|
|
}
|
|
}
|
|
singleChannelVibratingWireAcquisitionInstrumentCount = fd01;
|
|
multiChannelVibratingWireRecorder = f08 + f16 + f32;
|
|
if (fd01 > 0) {
|
|
const c = explicitOrFallbackCount('机箱配套', '单通道振弦采集模块', 'FS-FD01', fd01);
|
|
if (c > 0) {
|
|
items.push({ name: '单通道振弦采集模块', model: 'FS-FD01', count: c });
|
|
}
|
|
}
|
|
if (f08 > 0) {
|
|
const c = explicitOrFallbackCount('机箱配套', '多通道振弦采集仪', 'FS-F08', f08);
|
|
if (c > 0) {
|
|
items.push({ name: '多通道振弦采集仪', model: 'FS-F08', count: c });
|
|
}
|
|
}
|
|
if (f16 > 0) {
|
|
const c = explicitOrFallbackCount('机箱配套', '多通道振弦采集仪', 'FS-F16', f16);
|
|
if (c > 0) {
|
|
items.push({ name: '多通道振弦采集仪', model: 'FS-F16', count: c });
|
|
}
|
|
}
|
|
if (f32 > 0) {
|
|
const c = explicitOrFallbackCount('机箱配套', '多通道振弦采集仪', 'FS-F32', f32);
|
|
if (c > 0) {
|
|
items.push({ name: '多通道振弦采集仪', model: 'FS-F32', count: c });
|
|
}
|
|
}
|
|
const s485 = Number(signal485SensorCount || 0);
|
|
if (s485 > 0) {
|
|
if (s485 <= 8) {
|
|
const c4 = Math.ceil(s485 / 4);
|
|
const hc = explicitOrFallbackCount('机箱配套', 'RS485集线器', 'FS-485JXQ-04-A', c4);
|
|
if (hc > 0) hubs.push({ name: 'RS485集线器', model: 'FS-485JXQ-04-A', count: hc });
|
|
} else {
|
|
const c8 = Math.floor(s485 / 8);
|
|
const hc8 = explicitOrFallbackCount('机箱配套', 'RS485集线器', 'FS-485JXQ-08-A', c8);
|
|
if (hc8 > 0) hubs.push({ name: 'RS485集线器', model: 'FS-485JXQ-08-A', count: hc8 });
|
|
const r = s485 % 8;
|
|
if (r > 0) {
|
|
const c4 = Math.ceil(r / 4);
|
|
const hc4 = explicitOrFallbackCount('机箱配套', 'RS485集线器', 'FS-485JXQ-04-A', c4);
|
|
if (hc4 > 0) hubs.push({ name: 'RS485集线器', model: 'FS-485JXQ-04-A', count: hc4 });
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
}
|
|
const instrumentCountForBreaker = items
|
|
.filter(x => x.name === '多通道振弦采集仪' || x.name === '单通道振弦采集模块')
|
|
.reduce((sum, x) => sum + Number(x.count || 0), 0);
|
|
const basics = [
|
|
{ name: '断路器', model: 'DZ47-60', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '12V直流电源防雷器', model: '12V', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '数据采集箱', model: 'FS-CJX03-A', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '接地电缆', model: 'BVR 1*16', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 10 },
|
|
{ name: 'GPRS无线模块', model: 'FS-DTU-4G-W-V1.00', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '物联网卡', model: '20G', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '接地桩(镀锌角铁)', model: 'FS-DXJT-2', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 3 },
|
|
{ name: '镀锌扁铁', model: 'FS-DXBL-40*40', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 10 },
|
|
{ name: '降阻剂', model: '25kg/袋', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 10 },
|
|
];
|
|
const mains = isSolar ? [] : [
|
|
{ name: '电源电涌保护器', model: 'AM2-40', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '开关电源1', model: '25-12-A', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '导轨插座', model: '10A', count: Number(otherInstrumentCount || 0) + instrumentCountForBreaker },
|
|
{ name: '220V交流供电电缆', model: 'RVV 3*2.5', count: (Number(otherInstrumentCount || 0) + instrumentCountForBreaker) * 100 },
|
|
];
|
|
const breakerCount = Number(otherInstrumentCount || 0) + instrumentCountForBreaker;
|
|
request2Data = { factor: "机箱配套", data: breakerCount > 0 ? items.concat(hubs).concat(basics).concat(mains) : [] };
|
|
} catch (error) {
|
|
request2Data = { factor: "机箱配套", data: [] };
|
|
}
|
|
}
|
|
|
|
let collectingBoxCount = 0; // 采集箱总数
|
|
let vibratingWireAcquisitionInstrumentCount = 0; // 振弦采集仪总数
|
|
let dataAcquisitionInstrumentCount = otherInstrumentCount; // 数据采集仪总数
|
|
let GNSSCount = 0; // GNSS接收机总数
|
|
let PVCNum = Number(userText['通信系统']?.['PVC管长度'] || 0); // PVC管总数
|
|
let fiberOpticsLength = Number(userText['通信系统']?.['通信光缆(光纤)长度'] || 0); // 通信光缆(光纤)长度
|
|
for (const item of request1Data.concat([request2Data])) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
if (sensor.name === "数据采集箱") {
|
|
collectingBoxCount += Number(sensor.count || 0);
|
|
}
|
|
const n2 = String(sensor.name || '');
|
|
const m2 = String(sensor.model || '');
|
|
if (n2.includes("振弦采集仪") && !(n2.includes('原位监测系统') && n2.includes('V1.0')) && !m2.includes('内置软件')) {
|
|
vibratingWireAcquisitionInstrumentCount += Number(sensor.count || 0);
|
|
}
|
|
if (sensor.name === "测地型GNSS接收机") {
|
|
GNSSCount += Number(sensor.count || 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 通信系统
|
|
let request3Data = {
|
|
factor: "通信系统",
|
|
data: []
|
|
};
|
|
if (userText['通信系统']) {
|
|
try {
|
|
let data = slopePrompts['通信系统'].getManifest([collectingBoxCount, vibratingWireAcquisitionInstrumentCount, dataAcquisitionInstrumentCount, GNSSCount, PVCNum, fiberOpticsLength]);
|
|
request3Data = { factor: "通信系统", data: data || [] };
|
|
} catch (error) {
|
|
console.error('通信系统 calculation failed', error);
|
|
request3Data = { factor: "通信系统", data: [] };
|
|
}
|
|
}
|
|
|
|
// 太阳能计算
|
|
let request5Data = null;
|
|
let solarEnergy = {
|
|
solarEnergyTitle: null,
|
|
solarEnergyData: null,
|
|
powerTitle: null,
|
|
powerData: null,
|
|
};
|
|
const materials = await models.Materials.findAll({ raw: true, where: { structureType: structureType } });
|
|
if (userText['电力系统(太阳能)']) {
|
|
const request4Params = {
|
|
'物位计': 0,
|
|
'串联式固定测斜仪': 0,
|
|
'投入式水位计': 0,
|
|
'温湿度传感器': 0,
|
|
'雨量计': 0,
|
|
'多通道振弦采集仪': 0,
|
|
'数据采集系统V1.0': 0,
|
|
'GPRS无线模块': 0,
|
|
'工业级光纤收发器': 0,
|
|
'串口服务器': 0,
|
|
'工业级交换机': 0,
|
|
'4G网络球机': 0,
|
|
'硬盘录像机': 0,
|
|
'测地型GNSS接收机': 0,
|
|
}
|
|
for (const item of request1Data.concat([request2Data, request3Data])) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
for (const key in request4Params) {
|
|
if (sensor.name === key) {
|
|
request4Params[key] += Number(sensor.count || 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const regionName = String(userText['电力系统(太阳能)']?.['地区名称'] || '');
|
|
const region = await superagent
|
|
.post(`${apiUrl}/api/v1/chat/completions`)
|
|
.send({
|
|
"stream": false,
|
|
"detail": false,
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": regionName
|
|
}
|
|
]
|
|
}
|
|
]
|
|
})
|
|
.set({
|
|
Authorization: `Bearer ${regionAppKey}`,
|
|
"Content-Type": "application/json",
|
|
})
|
|
.timeout({
|
|
response: 1000 * 60 * 30,
|
|
deadline: 1000 * 60 * 30,
|
|
})
|
|
const regionType = JSON.parse(region?.body?.choices[0]?.message?.content?.trim()).region;
|
|
const batterySafetyFactorMap = { '北方地区': 1.1, '南方平原': 1.1, '南方山区': 1.4 };
|
|
const tempCorrectionFactorMap = { '北方地区': 1.2, '南方平原': 1.0, '南方山区': 1.1 };
|
|
const batterySafetyFactor = batterySafetyFactorMap[regionType];
|
|
const tempCorrectionFactor = tempCorrectionFactorMap[regionType];
|
|
const deviceList = [
|
|
{ name: '物位计', model: 'FS-WWJ', power: 0.24, key: '物位计' },
|
|
{ name: '串联式固定测斜仪', model: 'FS-GGCL01-V1.00', power: 0.36, key: '串联式固定测斜仪' },
|
|
{ name: '投入式水位计', model: 'FS-TRSW', power: 1.0, key: '投入式水位计' },
|
|
{ name: '温湿度传感器', model: 'FS-BDS-WSD', power: 0.24, key: '温湿度传感器' },
|
|
{ name: '雨量计', model: 'FS-FDYLJ', power: 1.2, key: '雨量计' },
|
|
{ name: '多通道振弦采集仪', model: 'FS-F08', power: 2.56, key: '多通道振弦采集仪' },
|
|
{ name: '数据采集系统V1.0', model: 'FS-D04', power: 1.7, key: '数据采集系统V1.0' },
|
|
{ name: 'GPRS无线模块', model: 'FS-DTU-4G-W-V1.00', power: 0.96, key: 'GPRS无线模块' },
|
|
{ name: '工业级光纤收发器', model: 'SKMSW-02011L', power: 6, key: '工业级光纤收发器' },
|
|
{ name: '串口服务器', model: '1D(RS485/232)', power: 0.84, key: '串口服务器' },
|
|
{ name: '工业级交换机', model: '', power: 2.5, key: '工业级交换机' },
|
|
{ name: '4G网络球机', model: '3寸 400万', power: 15, key: '4G网络球机' },
|
|
{ name: '硬盘录像机', model: '4路1盘位', power: 18, key: '硬盘录像机' },
|
|
{ name: '测地型GNSS接收机', model: 'A300', power: 0, key: '测地型GNSS接收机' },
|
|
];
|
|
solarEnergy.powerTitle = '功率统计表';
|
|
const powerHeaders = ['序号', '设备名称', '默认型号', '单位', '功率(W)', '数量', '总功率(W)'];
|
|
const powerRows = [];
|
|
const unitOverride = {
|
|
'多通道振弦采集仪|FS-F08': '台(8口)',
|
|
'数据采集系统V1.0|FS-D04': '台(4口)',
|
|
};
|
|
const getUnit = (name, model) => {
|
|
const overrideKey = `${name}|${model || ''}`;
|
|
if (unitOverride[overrideKey]) return unitOverride[overrideKey];
|
|
let m = materials.find(m => m.name === name && (model ? m.model === model : true));
|
|
if (!m && model) m = materials.find(m => m.model === model);
|
|
if (!m) m = materials.find(m => m.name === name);
|
|
return m?.unit || '';
|
|
};
|
|
let totalPower = 0;
|
|
for (let i = 0; i < deviceList.length; i++) {
|
|
const d = deviceList[i];
|
|
const qty = Number(request4Params[d.key] || 0);
|
|
const rowPower = Number(d.power || 0);
|
|
const rowTotalPower = Number((rowPower * qty).toFixed(6));
|
|
totalPower += rowTotalPower;
|
|
const unit = getUnit(d.name, d.model);
|
|
powerRows.push([
|
|
String(i + 1),
|
|
d.name,
|
|
d.model,
|
|
unit,
|
|
String(rowPower),
|
|
String(qty),
|
|
String(rowTotalPower)
|
|
]);
|
|
}
|
|
solarEnergy.powerData = { headers: powerHeaders, rows: powerRows };
|
|
const days = Number(userText['电力系统(太阳能)']?.['连续阴雨天数'] || 0) || 0;
|
|
const dailyConsumption = totalPower * 24;
|
|
const averageDailyConsumption = totalPower * 2;
|
|
const batteryCapacity = batterySafetyFactor * averageDailyConsumption * days * tempCorrectionFactor / 0.7;
|
|
let batteryCount = Math.ceil(batteryCapacity / 120);
|
|
const singlePanelPower = 200;
|
|
const sunlightHours = 4;
|
|
const singlePanelDailySupply = singlePanelPower * sunlightHours * 0.63;
|
|
const deviceDailyConsumption = dailyConsumption / 0.9;
|
|
let solarPanelCount = Math.ceil(deviceDailyConsumption / singlePanelDailySupply);
|
|
const gnssCount = Number(request4Params['测地型GNSS接收机'] || 0);
|
|
if (gnssCount > 0) {
|
|
solarPanelCount += gnssCount;
|
|
batteryCount += 2 * gnssCount;
|
|
}
|
|
solarEnergy.solarEnergyTitle = regionType;
|
|
const solarHeaders = ['总功率', '日耗电量', '日平均耗电量', '连续阴雨天', '蓄电池容量安全系数', '温度修正系数', '电池容量', '电池数量', '单块太阳能功率', '日平均光照时间', '设备单日耗电量', '太阳能板单日供电量', '太阳能板数量'];
|
|
const solarRow = [
|
|
String(Number(totalPower.toFixed(6))),
|
|
String(Number(dailyConsumption.toFixed(6))),
|
|
String(Number(averageDailyConsumption.toFixed(6))),
|
|
String(days),
|
|
String(batterySafetyFactor),
|
|
String(tempCorrectionFactor),
|
|
String(Number(batteryCapacity.toFixed(6))),
|
|
String(batteryCount),
|
|
String(singlePanelPower),
|
|
String(sunlightHours),
|
|
String(Number(deviceDailyConsumption.toFixed(6))),
|
|
String(Number(singlePanelDailySupply.toFixed(6))),
|
|
String(solarPanelCount)
|
|
];
|
|
solarEnergy.solarEnergyData = { headers: solarHeaders, rows: [solarRow] };
|
|
try {
|
|
const data = slopePrompts['电力系统(太阳能)'].getManifest([solarPanelCount, batteryCount]) || [];
|
|
const controllerLoad = (200 / 18) * solarPanelCount;
|
|
const controllerModel = controllerLoad < 10 ? '12V10A' : controllerLoad < 30 ? '12V30A' : '12V50A';
|
|
const controllerCount = Math.ceil((200 * solarPanelCount) / 900);
|
|
const controllerDesc = '太阳能控制器:根据太阳能控制器选型计算公式从“12V10A、12V30A、12V50A”中选择一个合适的型号。默认太阳能板功率为200,太阳能控制器选型计算公式:(太阳能板功率/18)*太阳能板数量,根据该公式的计算结果选择对应型号:如果计算结果<10,选择太阳能控制器12V10A;如果计算结果>=10且<30选择太阳能控制器12V30A;如果计算结果>=30选择太阳能控制器12V50A。太阳能控制器数量=太阳能板功率*太阳能板数量/900(向上取整)。';
|
|
for (let k = 0; k < data.length; k++) {
|
|
const it = data[k];
|
|
if (String(it?.name) === '太阳能控制器') {
|
|
it.model = controllerModel;
|
|
it.count = controllerCount;
|
|
}
|
|
}
|
|
request5Data = { data };
|
|
} catch (error) {
|
|
console.error('电力系统(太阳能) calculation failed', error);
|
|
}
|
|
}
|
|
|
|
const allListData = request1Data.concat([request2Data, request3Data]);
|
|
if (request5Data) { allListData.push(request5Data) }
|
|
|
|
// 工期核算清单
|
|
const totalCountMap = {};
|
|
let xlNum = 0
|
|
for (const item of allListData) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
const k = sensor.name;
|
|
const v = Number(sensor.count || 0);
|
|
if (sensor.name === '接地电缆' || sensor.name === '通信线缆' || sensor.name === '接地线缆') {
|
|
xlNum += v
|
|
}
|
|
totalCountMap[k] = (totalCountMap[k] || 0) + v;
|
|
}
|
|
}
|
|
}
|
|
const cableSum = Object.entries(totalCountMap).reduce((s, [k, v]) => s + ((String(k).includes('通信电缆') || String(k).includes('接地电缆')) ? Number(v || 0) : 0), 0);
|
|
const installQtyMap = {
|
|
'表面式应变计': 8,
|
|
'内埋式应变计': 8,
|
|
'钢筋计': 8,
|
|
'土压力计': 6.4,
|
|
'孔隙水压计': 8,
|
|
'轴力计': 2,
|
|
'锚索计': 2,
|
|
'温度传感器': 8,
|
|
'温湿度传感器': 4,
|
|
'土壤温湿度传感器': 4,
|
|
'闭环磁通量标定': 8,
|
|
'开环磁通量绕线(小)': 3,
|
|
'开环磁通量绕线(中)': 1.6,
|
|
'开环磁通量绕线(大)': 0.7,
|
|
'人工磁通量温补': 5.3,
|
|
'盒式固定测斜仪': 5.3,
|
|
'串联式固定测斜仪': 6,
|
|
'裂缝计': 5.3,
|
|
'静力水准仪': 6.4,
|
|
'物位计': 6.4,
|
|
'雷达液位计': 2,
|
|
'拉线位移传感器': 3.2,
|
|
'多点位移计(振弦式)': 2.7,
|
|
'加速度计': 5.3,
|
|
'激光测距仪': 5,
|
|
'投入式水位计': 1.5,
|
|
'超声波水位计': 2,
|
|
'雨量计': 2,
|
|
'超声波风速风向仪': 2.5,
|
|
'光电挠度仪': 0.7,
|
|
'全站仪': 0.3,
|
|
'钢尺水位计': 2,
|
|
'一体化裂缝计(成品)': 2.7,
|
|
'一体化地灾(加速度计)': 4,
|
|
'一体化地灾倾角计': 4,
|
|
'GNSS接收机': 0.9,
|
|
'4G球机': 2,
|
|
'管式含水率仪': 4,
|
|
'分布式节点': 4,
|
|
'称重系统(1套2车道)': 0.3,
|
|
'拼接屏(4*6)': 0.3,
|
|
'太阳能供电': 1,
|
|
'监控中心(3天)': 3.2,
|
|
'墩座1': 4,
|
|
'墩座2': 4,
|
|
'墩座3': 2.7,
|
|
'墩座4': 2,
|
|
'基础1(具备挖机、吊车)': 0.7,
|
|
'基础2': 2.7,
|
|
'基础3': 2.7,
|
|
'基础4': 2,
|
|
'地网': 1,
|
|
'线缆(h/100米)': 8.4,
|
|
'桥架1(h/100米)': 1.3,
|
|
'桥架2(h/100米)': 1.3,
|
|
'PVC管1(h/100米)': 5.3,
|
|
'PVC管2(h/100米)': 5.3
|
|
};
|
|
const inputQtyMap = {
|
|
'表面式应变计': Number(totalCountMap['表面式应变计'] || 0),
|
|
'内埋式应变计': Number(totalCountMap['内埋式应变计'] || 0),
|
|
'钢筋计': Number(totalCountMap['钢筋计'] || 0),
|
|
'土压力计': Number(totalCountMap['土压力计(单膜)'] || 0),
|
|
'孔隙水压计': Number(totalCountMap['孔隙水压计'] || 0),
|
|
'轴力计': Number(totalCountMap['轴力计'] || 0),
|
|
'锚索计': Number(totalCountMap['锚索计'] || 0),
|
|
'温度传感器': Number(totalCountMap['温度传感器'] || 0),
|
|
'温湿度传感器': Number(totalCountMap['温湿度传感器'] || 0),
|
|
'土壤温湿度传感器': Number(totalCountMap['土壤温湿度传感器'] || 0),
|
|
'闭环磁通量标定': 0,
|
|
'开环磁通量绕线(小)': 0,
|
|
'开环磁通量绕线(中)': 0,
|
|
'开环磁通量绕线(大)': 0,
|
|
'人工磁通量温补': 0,
|
|
'盒式固定测斜仪': Number(totalCountMap['盒式固定测斜仪'] || 0),
|
|
'串联式固定测斜仪': Number(totalCountMap['串联式固定测斜仪'] || 0),
|
|
'裂缝计': Number(totalCountMap['裂缝计'] || 0),
|
|
'静力水准仪': Number(totalCountMap['静力水准仪'] || 0),
|
|
'物位计': Number(totalCountMap['物位计'] || 0),
|
|
'雷达液位计': Number(totalCountMap['雷达水位计'] || 0),
|
|
'拉线位移传感器': Number(totalCountMap['拉线位移传感器'] || 0),
|
|
'多点位移计(振弦式)': Number(totalCountMap['多点位移计(振弦式)'] || 0),
|
|
'加速度计': Number(totalCountMap['加速度计'] || 0),
|
|
'激光测距仪': Number(totalCountMap['激光测距仪'] || 0),
|
|
'投入式水位计': Number(totalCountMap['投入式水位计'] || 0),
|
|
'超声波水位计': Number(totalCountMap['超声波水位计'] || 0),
|
|
'雨量计': Number(totalCountMap['雨量计'] || 0),
|
|
'超声波风速风向仪': Number(totalCountMap['超声波风速风向仪'] || 0),
|
|
'光电挠度仪': Number(totalCountMap['光电挠度仪'] || 0),
|
|
'全站仪': Number(totalCountMap['全站仪'] || 0),
|
|
'钢尺水位计': Number(totalCountMap['钢尺水位计'] || 0),
|
|
'一体化裂缝计(成品)': Number(totalCountMap['一体化裂缝计(成品)'] || 0),
|
|
'一体化地灾(加速度计)': Number(totalCountMap['一体化地灾(加速度计)'] || 0),
|
|
'一体化地灾倾角计': Number(totalCountMap['一体化地灾倾角计'] || 0),
|
|
'GNSS接收机': Number(totalCountMap['测地型GNSS接收机'] || 0),
|
|
'4G球机': Number(totalCountMap['4G网络球机'] || 0),
|
|
'管式含水率仪': Number(totalCountMap['管式含水率仪'] || 0),
|
|
'分布式节点': Number(totalCountMap['分布式节点'] || 0),
|
|
'称重系统(1套2车道)': 0,
|
|
'拼接屏(4*6)': 0,
|
|
'太阳能供电': Number(totalCountMap['太阳能控制器'] || 0),
|
|
'监控中心(3天)': 0,
|
|
'墩座1': 0,
|
|
'墩座2': 0,
|
|
'墩座3': 0,
|
|
'墩座4': 0,
|
|
'基础1(具备挖机、吊车)': 0,
|
|
'基础2': 0,
|
|
'基础3': 0,
|
|
'基础4': 0,
|
|
'地网': Number(totalCountMap['接地桩(镀锌角铁)'] || 0),
|
|
'线缆(h/100米)': Number(xlNum || 0),
|
|
'桥架1(h/100米)': Number(totalCountMap['桥架'] || 0),
|
|
'桥架2(h/100米)': 0,
|
|
'PVC管1(h/100米)': Number(PVCNum || 0),
|
|
'PVC管2(h/100米)': 0
|
|
};
|
|
const sensorsForDebug = [
|
|
'表面式应变计',
|
|
'内埋式应变计',
|
|
'温度传感器',
|
|
'温湿度传感器',
|
|
'盒式固定测斜仪',
|
|
'裂缝计',
|
|
'静力水准仪',
|
|
'拉线位移传感器',
|
|
'超声波风速风向仪',
|
|
'光电挠度仪',
|
|
'GNSS接收机',
|
|
'4G球机'
|
|
];
|
|
const sensorTotal = sensorsForDebug.reduce((sum, k) => sum + Number(inputQtyMap[k] || 0), 0);
|
|
const systemDebugDays = sensorTotal <= 50 ? 2 : sensorTotal <= 100 ? 3 : 5;
|
|
const acceptanceTrainingDays = sensorTotal <= 100 ? 3 : 5;
|
|
const projectRiskDays = sensorTotal <= 100 ? 2 : 3;
|
|
const headers6 = ['序号', '分类', '事项', '耗时(小时/人)', '安装数量/个(2人8小时计)', '备注', '数量输入', '工期(2人天)'];
|
|
const orderedItems = [
|
|
'表面式应变计',
|
|
'内埋式应变计',
|
|
'钢筋计',
|
|
'土压力计',
|
|
'孔隙水压计',
|
|
'轴力计',
|
|
'锚索计',
|
|
'温度传感器',
|
|
'温湿度传感器',
|
|
'土壤温湿度传感器',
|
|
'闭环磁通量标定',
|
|
'开环磁通量绕线(小)',
|
|
'开环磁通量绕线(中)',
|
|
'开环磁通量绕线(大)',
|
|
'人工磁通量温补',
|
|
'盒式固定测斜仪',
|
|
'串联式固定测斜仪',
|
|
'裂缝计',
|
|
'静力水准仪',
|
|
'物位计',
|
|
'雷达液位计',
|
|
'拉线位移传感器',
|
|
'多点位移计(振弦式)',
|
|
'加速度计',
|
|
'激光测距仪',
|
|
'投入式水位计',
|
|
'超声波水位计',
|
|
'雨量计',
|
|
'超声波风速风向仪',
|
|
'光电挠度仪',
|
|
'全站仪',
|
|
'钢尺水位计',
|
|
'一体化裂缝计(成品)',
|
|
'一体化地灾(加速度计)',
|
|
'一体化地灾倾角计',
|
|
'GNSS接收机',
|
|
'4G球机',
|
|
'管式含水率仪',
|
|
'分布式节点',
|
|
'称重系统(1套2车道)',
|
|
'拼接屏(4*6)',
|
|
'太阳能供电',
|
|
'监控中心(3天)',
|
|
'墩座1',
|
|
'墩座2',
|
|
'墩座3',
|
|
'墩座4',
|
|
'基础1(具备挖机、吊车)',
|
|
'基础2',
|
|
'基础3',
|
|
'基础4',
|
|
'地网',
|
|
'线缆(h/100米)',
|
|
'桥架1(h/100米)',
|
|
'桥架2(h/100米)',
|
|
'PVC管1(h/100米)',
|
|
'PVC管2(h/100米)',
|
|
'系统调试',
|
|
'外包(天)',
|
|
'验收、培训',
|
|
'项目风险天数(设备转场、开路、技术难点等)',
|
|
'项目经理(天)'
|
|
];
|
|
function getCategory(name) {
|
|
const sensorSet = new Set([
|
|
'表面式应变计', '内埋式应变计', '钢筋计', '土压力计', '孔隙水压计', '轴力计', '锚索计',
|
|
'温度传感器', '温湿度传感器', '土壤温湿度传感器', '闭环磁通量标定', '开环磁通量绕线(小)',
|
|
'开环磁通量绕线(中)', '开环磁通量绕线(大)', '人工磁通量温补', '盒式固定测斜仪', '串联式固定测斜仪',
|
|
'裂缝计', '静力水准仪', '物位计', '雷达液位计', '拉线位移传感器', '多点位移计(振弦式)',
|
|
'加速度计', '激光测距仪', '投入式水位计', '超声波水位计', '雨量计', '超声波风速风向仪',
|
|
'光电挠度仪', '全站仪', '钢尺水位计', '一体化裂缝计(成品)', '一体化地灾(加速度计)',
|
|
'一体化地灾倾角计', '振动传感器', 'GNSS接收机', '4G球机'
|
|
]);
|
|
const deviceSet = new Set(['分布式节点', '称重系统(1套2车道)', '拼接屏(4*6)', '太阳能供电', '监控中心(3天)']);
|
|
const civilSet = new Set(['墩座1', '墩座2', '墩座3', '墩座4', '基础1(具备挖机、吊车)', '基础2', '基础3', '基础4', '地网']);
|
|
if (sensorSet.has(name)) return '传感器类';
|
|
if (deviceSet.has(name)) return '设备类';
|
|
if (civilSet.has(name)) return '土建类(地笼、地网、基座、立杆、熔纤)';
|
|
if (name.endsWith('(h/100米)') || name === '系统调试') return '集成';
|
|
if (name === '外包(天)') return '外包(天)';
|
|
if (name === '验收、培训') return '验收及培训';
|
|
if (name === '项目风险天数(设备转场、开路、技术难点等)') return '风险天数';
|
|
if (name === '项目经理(天)') return '项目经理(天)';
|
|
return '其他类';
|
|
}
|
|
const hoursPerPersonMap = {
|
|
'表面式应变计': '2',
|
|
'内埋式应变计': '2',
|
|
'钢筋计': '2',
|
|
'土压力计': '2.5',
|
|
'孔隙水压计': '2',
|
|
'轴力计': '8',
|
|
'锚索计': '8',
|
|
'温度传感器': '2',
|
|
'温湿度传感器': '4',
|
|
'土壤温湿度传感器': '4',
|
|
'闭环磁通量标定': '2',
|
|
'开环磁通量绕线(小)': '4',
|
|
'开环磁通量绕线(中)': '10',
|
|
'开环磁通量绕线(大)': '24',
|
|
'人工磁通量温补': '3',
|
|
'盒式固定测斜仪': '3',
|
|
'串联式固定测斜仪': '2',
|
|
'裂缝计': '3',
|
|
'静力水准仪': '2.5',
|
|
'拉线位移传感器': '5',
|
|
'多点位移计(振弦式)': '6',
|
|
'振动传感器': '3',
|
|
'激光测距仪': '3',
|
|
'投入式水位计': '12',
|
|
'超声波水位计': '8',
|
|
'雨量计': '8',
|
|
'超声波风速风向仪': '6',
|
|
'光电挠度仪': '24',
|
|
'全站仪': '48',
|
|
'钢尺水位计': '8',
|
|
'GNSS接收机': '18',
|
|
'4G球机': '8',
|
|
'分布式节点': '4',
|
|
'称重系统(1套2车道)': '64',
|
|
'拼接屏(4*6)': '48',
|
|
'太阳能供电': '16',
|
|
'监控中心(3天)': '5',
|
|
'墩座1': '4',
|
|
'墩座2': '4',
|
|
'墩座3': '6',
|
|
'墩座4': '8',
|
|
'基础1(具备挖机、吊车)': '24',
|
|
'基础2': '6',
|
|
'基础3': '6',
|
|
'基础4': '8',
|
|
'地网': '16',
|
|
'线缆(h/100米)': '1.9',
|
|
'桥架1(h/100米)': '12',
|
|
'桥架2(h/100米)': '16',
|
|
'PVC管1(h/100米)': '3',
|
|
'PVC管2(h/100米)': '8',
|
|
};
|
|
function getHoursPerPerson(name) {
|
|
return hoursPerPersonMap[name] ?? '';
|
|
}
|
|
const remarksMap = {
|
|
'土压力计': '根据挖槽深度、难度',
|
|
'轴力计': '含加预应力过程时间',
|
|
'锚索计': '含张拉过程时间',
|
|
'闭环磁通量标定': '需另外加上集成调试时间,固定传感器时间',
|
|
'开环磁通量绕线(小)': '内径50以下',
|
|
'开环磁通量绕线(中)': '内径50-100左右',
|
|
'开环磁通量绕线(大)': '内径100以上',
|
|
'人工磁通量温补': '距离近可转场按照1天6个计算,不需要外包',
|
|
'串联式固定测斜仪': '单位:串',
|
|
'多点位移计(振弦式)': '含打孔注浆',
|
|
'光电挠度仪': '需要参考标靶数量',
|
|
'全站仪': '全站仪安装3天计(含基础及电脑),含棱镜',
|
|
'钢尺水位计': '含混凝土立柱',
|
|
'GNSS接收机': '含基础浇筑',
|
|
'4G球机': '混凝土凝固,折返时间',
|
|
'监控中心(3天)': '监控中心整体考虑2人3天',
|
|
'墩座1': '雨量计:300*300*250mm',
|
|
'墩座2': '测斜保护墩:250*250*200mm',
|
|
'墩座3': '采集箱底座:600*300*200mm',
|
|
'墩座4': 'GNSS混凝土墩:1800*315mm(高*直径)',
|
|
'基础1(具备挖机、吊车)': '12米横臂拍立杆:1800*1800*2000mm',
|
|
'基础2': '视频立杆:300*300*800mm',
|
|
'基础3': '雷达液位计立杆基础:300*300*800mm',
|
|
'基础4': 'GNSS钢立柱基础:600*600*800mm',
|
|
'地网': '标准防雷地网(土质)',
|
|
'线缆(h/100米)': '需要机械,线缆及水管气管',
|
|
'桥架1(h/100米)': '不需机械脚手架',
|
|
'桥架2(h/100米)': '需机械脚手架',
|
|
'PVC管1(h/100米)': '不需机械脚手架',
|
|
'PVC管2(h/100米)': '需机械脚手架',
|
|
'系统调试': '传感器≤50,按2天;100≥传感器数量>50,按4天;传感器数量>100,按5天',
|
|
'验收、培训': '传感器≤100,按3天;传感器数量>100,按4天',
|
|
'项目风险天数(设备转场、开路、技术难点等)': '传感器≤100,按2天;传感器数量>100,按5天',
|
|
};
|
|
function getInstallDisplay(name, installQty, qty) {
|
|
// if (name.endsWith('(h/100米)')) {
|
|
// const v = Number(((installQty || 0) * (qty || 0) / 100).toFixed(3));
|
|
// return String(v);
|
|
// }
|
|
if (name === '地网') return '1';
|
|
if (name === '系统调试') return '2-5天';
|
|
if (name === '验收、培训') return '2-4天';
|
|
if (name === '项目风险天数(设备转场、开路、技术难点等)') return '2-5天';
|
|
if (name === '外包(天)' || name === '项目经理(天)') return '';
|
|
return String(installQty ?? '');
|
|
}
|
|
function installRuleText(name) {
|
|
if (name === '地网') return '安装数量×数量输入÷3';
|
|
if (name.endsWith('(h/100米)')) return '安装数量×数量输入÷100';
|
|
if (name === '监控中心(3天)') return '固定3天';
|
|
if (name === '系统调试') return '总数≤50→2;≤100→3;>100→5';
|
|
if (name === '验收、培训') return '总数≤100→3;>100→5';
|
|
if (name === '项目风险天数(设备转场、开路、技术难点等)') return '总数≤100→2;>100→3';
|
|
return '数量输入÷安装数量';
|
|
}
|
|
function computeDuration(name, installQty, qty) {
|
|
if (name === '地网') return Number(((qty || 0) / 3).toFixed(3));
|
|
if (name.endsWith('(h/100米)')) {
|
|
if (!qty) return 0;
|
|
const installDisplay = (installQty || 0) ? (installQty * qty / 100) : 0;
|
|
if (!installDisplay) return 0;
|
|
return Number(installDisplay).toFixed(3);
|
|
}
|
|
if (name === '监控中心(3天)') return 3;
|
|
if (name === '系统调试') return Number(systemDebugDays.toFixed(3));
|
|
if (name === '验收、培训') return Number(acceptanceTrainingDays.toFixed(3));
|
|
if (name === '项目风险天数(设备转场、开路、技术难点等)') return Number(projectRiskDays.toFixed(3));
|
|
if (name === '外包(天)') return 0;
|
|
if (!installQty) return 0;
|
|
return Number(((qty || 0) / installQty).toFixed(3));
|
|
}
|
|
const rows6 = [];
|
|
for (let i = 0; i < orderedItems.length; i++) {
|
|
const name = orderedItems[i];
|
|
const installQty = installQtyMap[name] ?? 0;
|
|
const qty = inputQtyMap[name] ?? 0;
|
|
const durationVal = computeDuration(name, installQty, qty);
|
|
const displayName = (name === '外包(天)' || name === '项目经理(天)') ? '' : name;
|
|
const installDisplay = getInstallDisplay(name, installQty, qty);
|
|
rows6.push([
|
|
String(i + 1),
|
|
getCategory(name),
|
|
displayName,
|
|
getHoursPerPerson(name),
|
|
installDisplay,
|
|
remarksMap[name] ?? '',
|
|
(name === '外包(天)' || name === '项目经理(天)') ? '' : String(Number((qty || 0).toFixed(3))),
|
|
String(durationVal)
|
|
]);
|
|
}
|
|
const epiboleLimitIdx3 = orderedItems.indexOf('系统调试') + 1;
|
|
const epiboleDays = rows6.reduce((acc, row) => {
|
|
const idx = parseInt(row[0], 10);
|
|
if (isNaN(idx) || idx > epiboleLimitIdx3) return acc;
|
|
return acc + Number(row[row.length - 1] || 0);
|
|
}, 0);
|
|
const managerHoursPerDay = Number(userText['工期核算清单']?.['项目经理每日工时'] || 8);
|
|
const projectManagerDays = Number((((epiboleDays + acceptanceTrainingDays + projectRiskDays) * 8) / (managerHoursPerDay || 8)).toFixed(6));
|
|
const durationData = { headers: headers6, rows: rows6 };
|
|
|
|
// 核算要点
|
|
let hasSolar = false
|
|
let request7Power = '';
|
|
let hasFiber = false;
|
|
for (const item of allListData) {
|
|
if (item && item.data && item.data.length > 0) {
|
|
for (const sensor of item.data) {
|
|
if (sensor.name.includes('太阳能')) {
|
|
// request7Power = ',太阳能供电';
|
|
hasSolar = true;
|
|
}
|
|
if (sensor.name.includes('光纤')) {
|
|
// request7Power = ',光纤通信';
|
|
hasFiber = true;
|
|
break;
|
|
}
|
|
if (hasSolar && hasFiber) {
|
|
|
|
}
|
|
}
|
|
}
|
|
if (hasFiber) break;
|
|
}
|
|
|
|
const mainPointData = getMainPointData(userText, epiboleDays, hasSolar, hasFiber);
|
|
|
|
let sheet1Data = request1Data;
|
|
let sheet1Key = request1Key;
|
|
if (userText['机箱配套']) {
|
|
sheet1Data.push(request2Data);
|
|
sheet1Key.push('机箱配套');
|
|
}
|
|
if (userText['通信系统']) {
|
|
sheet1Data.push(request3Data);
|
|
sheet1Key.push('通信系统');
|
|
}
|
|
if (request5Data) {
|
|
sheet1Data.push(request5Data);
|
|
sheet1Key.push('电力系统(太阳能)');
|
|
}
|
|
/********** 2.生成excel **********/
|
|
const tableData = generationXlsxData({
|
|
materials, sheet1Data, sheet1Key,
|
|
solarEnergy: userText['电力系统(太阳能)'] ? solarEnergy : null,
|
|
duration: {
|
|
durationData,
|
|
epiboleDays,
|
|
projectManagerDays
|
|
},
|
|
mainPointData
|
|
});
|
|
|
|
/********** 3.保存数据 **********/
|
|
await models.SchemeList.update(
|
|
{
|
|
status: 'success',
|
|
updateAt: new Date(),
|
|
tableData,
|
|
},
|
|
{ where: { id: schemeId } }
|
|
);
|
|
ctx.logger.log(`方案清单数据生成成功,schemeId:${schemeId}`);
|
|
|
|
await transaction.commit();
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
ctx.logger.log(error);
|
|
try {
|
|
await models.SchemeList.update(
|
|
{ status: 'fail' },
|
|
{ where: { id: schemeId } }
|
|
);
|
|
} catch (err) {
|
|
console.log(`修改状态失败,scheme.id=${schemeId}`);
|
|
ctx.logger.log(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 生成excel数据
|
|
* @returns {Array<Object>} sheets: 所有工作表数据
|
|
* [{
|
|
name: 'sheet1',
|
|
data: [],
|
|
merges: [], // 合并单元格
|
|
}]
|
|
*/
|
|
function generationXlsxData({
|
|
materials = [],
|
|
sheet1Data,
|
|
sheet1Key,
|
|
solarEnergy,
|
|
duration: {
|
|
durationData,
|
|
epiboleDays,
|
|
projectManagerDays
|
|
} = {},
|
|
mainPointData
|
|
}) {
|
|
const tableData = [];
|
|
|
|
// 通用单元格信息
|
|
const cellBorder = {
|
|
top: { style: "thin" },
|
|
right: { style: "thin" },
|
|
bottom: { style: "thin" },
|
|
left: { style: "thin" }
|
|
};
|
|
const alignmentCenter = { horizontal: "center", vertical: "center" };
|
|
const commonStyle = {
|
|
font: { name: '宋体', sz: 11 },
|
|
border: cellBorder,
|
|
alignment: alignmentCenter
|
|
};
|
|
const commonBoldStyle = {
|
|
font: { name: '宋体', sz: 11, bold: true },
|
|
border: cellBorder,
|
|
alignment: alignmentCenter
|
|
};
|
|
const emptyCell = { v: '', t: 's', s: {} };
|
|
const emptyBorderCell = { v: '', t: 's', s: { border: cellBorder } };
|
|
|
|
/********** sheet 桥梁监测设备清单 **********/
|
|
/**
|
|
* 单元格合并信息
|
|
* r: 行索引, c: 列索引, s: 起始位置, e: 结束位置
|
|
*/
|
|
let manifestMerges = [
|
|
{ s: { r: 0, c: 0 }, e: { r: 1, c: 10 } },
|
|
{ s: { r: 2, c: 0 }, e: { r: 2, c: 4 } },
|
|
{ s: { r: 2, c: 5 }, e: { r: 2, c: 10 } },
|
|
{ s: { r: 3, c: 0 }, e: { r: 3, c: 4 } },
|
|
{ s: { r: 3, c: 5 }, e: { r: 3, c: 10 } },
|
|
{ s: { r: 4, c: 0 }, e: { r: 4, c: 4 } },
|
|
{ s: { r: 4, c: 5 }, e: { r: 4, c: 10 } },
|
|
{ s: { r: 5, c: 0 }, e: { r: 5, c: 4 } },
|
|
{ s: { r: 5, c: 5 }, e: { r: 5, c: 10 } },
|
|
{ s: { r: 6, c: 0 }, e: { r: 6, c: 4 } },
|
|
{ s: { r: 6, c: 5 }, e: { r: 6, c: 10 } },
|
|
];
|
|
// 固定头部
|
|
let manifestData = [
|
|
[
|
|
{
|
|
v: "江西飞尚科技有限公司 报价单",
|
|
t: "s",
|
|
s: {
|
|
font: { name: '宋体', bold: true, sz: 20 },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" }
|
|
}
|
|
}, emptyBorderCell, emptyBorderCell, emptyBorderCell, emptyBorderCell, emptyBorderCell, emptyBorderCell, emptyBorderCell, emptyBorderCell, emptyBorderCell, emptyBorderCell
|
|
],
|
|
new Array(10).fill(emptyBorderCell),
|
|
];
|
|
for (let i = 0; i < 5; i++) {
|
|
manifestData.push([
|
|
{
|
|
v: i === 0 ? `TO:`
|
|
: i === 1 ? `收件人:`
|
|
: i === 2 ? `电话:`
|
|
: i === 3 ? `邮件:`
|
|
: i === 4 ? `项目名称:`
|
|
: '',
|
|
t: "s",
|
|
s: { font: { name: '宋体', bold: true, sz: 12 }, border: cellBorder }
|
|
}, emptyBorderCell, emptyBorderCell, emptyBorderCell, emptyBorderCell,
|
|
{
|
|
v: i === 0 ? `FROM:江西飞尚科技有限公司`
|
|
: i === 1 ? `报价人:`
|
|
: i === 2 ? `电话:`
|
|
: i === 3 ? `邮件: @free-sun.com.cn`
|
|
: i === 4 ? `报价日期:${moment().format('YYYY')}年 月 日`
|
|
: '',
|
|
t: "s",
|
|
s: { font: { name: '宋体', bold: true, sz: 12 }, border: cellBorder }
|
|
}, emptyBorderCell, emptyBorderCell, emptyBorderCell, emptyBorderCell, emptyBorderCell,
|
|
]);
|
|
}
|
|
manifestData.push(['序号', '类型', '名称', '规格/型号', '物料代码', '单位', '单价', '数量', '总价', '线长(传感器定制出厂长度)', '配置说明'].map(title => ({
|
|
v: title,
|
|
t: "s",
|
|
s: {
|
|
font: { name: '宋体', bold: true, sz: 11 },
|
|
border: cellBorder,
|
|
}
|
|
})));
|
|
// 数据
|
|
for (let i = 0; i < sheet1Data.length; i++) {
|
|
const title = sheet1Key[i];
|
|
const data = sheet1Data[i].data;
|
|
manifestData.push([{
|
|
v: `${i + 1} ${title}`,
|
|
t: "s",
|
|
s: {
|
|
font: { name: '宋体', bold: true, sz: 16, color: { rgb: "FFFFFF" } },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" },
|
|
fill: { fgColor: { rgb: "4472c4" } }
|
|
}
|
|
}].concat(new Array(9).fill(emptyBorderCell)));
|
|
manifestMerges.push({ s: { r: manifestData.length - 1, c: 0 }, e: { r: manifestData.length - 1, c: 10 } });
|
|
if (!data || data.length === 0) {
|
|
manifestData.push(new Array(11).fill(emptyCell));
|
|
continue;
|
|
}
|
|
for (let j = 0; j < data.length; j++) {
|
|
const item = data[j];
|
|
let material = materials
|
|
.filter(m => {
|
|
if (title === '索力监测(磁通量)' || title === '索力监测(加速度计)') {
|
|
return m.factor === '索力监测';
|
|
}
|
|
if (title === '环境温湿度监测' || title === '风速风向监测') {
|
|
return m.factor === '环境监测';
|
|
}
|
|
return m.factor === title
|
|
})
|
|
.find(m => {
|
|
if (m.model && item.model) {
|
|
if (m.name == item.name && m.model === item.model) return true; // name不同,model相同也算匹配
|
|
return m.name === item.name && m.model === item.model;
|
|
} else {
|
|
return m.name === item.name;
|
|
}
|
|
});
|
|
if (!material) { // 有些情况ai返回的name是type导致匹配不成功,待解决
|
|
material = {};
|
|
}
|
|
manifestData.push([
|
|
{
|
|
v: j + 1,
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
},
|
|
{
|
|
v: material.type || item.type || '',
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
},
|
|
{
|
|
v: material.name || item.name,
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
},
|
|
{
|
|
v: material.model || item.model,
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
},
|
|
{
|
|
v: material.code || item.code || '',
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
},
|
|
{
|
|
v: material.unit || item.unit || '',
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
},
|
|
{
|
|
v: (material.price ?? item.price) ?? '',
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
},
|
|
{
|
|
v: item.count ?? '',
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
},
|
|
{
|
|
v: (() => { const p = material.price ?? item.price; return p ? p * item.count : ''; })(),
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
},
|
|
{
|
|
v: (material.lineLength ?? item.lineLength) ?? '',
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
},
|
|
{
|
|
// v: material.description || '',
|
|
v: '',
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
}
|
|
]);
|
|
}
|
|
if (data.length > 0) {
|
|
manifestData.push([{
|
|
v: `${data.length + 1}`,
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
}, {
|
|
v: '小计',
|
|
t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11 },
|
|
border: cellBorder,
|
|
}
|
|
}].concat(new Array(9).fill(emptyBorderCell)));
|
|
manifestMerges.push({ s: { r: manifestData.length - 1, c: 1 }, e: { r: manifestData.length - 1, c: 2 } });
|
|
}
|
|
}
|
|
// 固定结尾
|
|
manifestData.push([{
|
|
v: `${sheet1Data.length + 1} 费用总计`,
|
|
t: "s",
|
|
s: {
|
|
font: { name: '宋体', bold: true, sz: 16, color: { rgb: "FFFFFF" } },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" },
|
|
fill: { fgColor: { rgb: "4472c4" } }
|
|
}
|
|
}].concat(new Array(9).fill(emptyBorderCell)));
|
|
manifestMerges.push({ s: { r: manifestData.length - 1, c: 0 }, e: { r: manifestData.length - 1, c: 10 } });
|
|
const costRows = ['硬件总费用', '软件总费用', '土建费', '机械费用', '系统集成费', '运维费用', '费用总计'];
|
|
for (let i = 0; i < costRows.length; i++) {
|
|
manifestData.push([{
|
|
v: i + 1,
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
}, {
|
|
v: costRows[i],
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 11 }, border: cellBorder }
|
|
}].concat(new Array(8).fill(emptyBorderCell)));
|
|
}
|
|
const remarksRows = [
|
|
'备注:',
|
|
'1、以上报价含税含运费',
|
|
' 2、报价有效期:自报价之日起1个月内有效',
|
|
' 3、产品质保期:1年',
|
|
' 4、付款方式:签订合同后预付合同总额的40%,货到现场支付合同总额的40%,安装调试完成后支付剩余20 % ',
|
|
' 5、交货期:合同签订后30天内'
|
|
];
|
|
for (let i = 0; i < remarksRows.length; i++) {
|
|
manifestData.push([{
|
|
v: remarksRows[i],
|
|
t: "s",
|
|
s: { font: { name: '宋体', sz: 14 }, border: cellBorder }
|
|
}].concat(new Array(9).fill(emptyBorderCell)));
|
|
manifestMerges.push({ s: { r: manifestData.length - 1, c: 0 }, e: { r: manifestData.length - 1, c: 10 } });
|
|
}
|
|
// 添加到表格数据
|
|
tableData.push({
|
|
name: '桥梁监测设备清单',
|
|
data: manifestData,
|
|
merges: manifestMerges,
|
|
cols: [
|
|
{ wch: 6 },
|
|
{ wch: 13 },
|
|
{ wch: 30 },
|
|
{ wch: 20 },
|
|
{ wch: 20 },
|
|
{ wch: 5 },
|
|
{ wch: 9 },
|
|
{ wch: 6 },
|
|
{ wch: 9 },
|
|
{ wch: 39 },
|
|
{ wch: 39 },
|
|
]
|
|
});
|
|
|
|
/********** sheet 太阳能计算 **********/
|
|
if (solarEnergy) {
|
|
const {
|
|
solarEnergyTitle = '',
|
|
solarEnergyData = {},
|
|
powerTitle = '',
|
|
powerData = {},
|
|
} = solarEnergy;
|
|
let solarEnergyCalcMerges = [];
|
|
let solarEnergyCalcData = [[{
|
|
v: solarEnergyTitle, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11 },
|
|
alignment: { vertical: 'center', horizontal: 'center' },
|
|
fill: { fgColor: { rgb: "ffff00" } },
|
|
}
|
|
}]];
|
|
// 太阳能数据表
|
|
if (solarEnergyData.headers?.length && solarEnergyData.rows?.length) {
|
|
solarEnergyCalcData.push(solarEnergyData.headers.map(header => ({
|
|
v: header, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11 },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" }
|
|
}
|
|
})));
|
|
solarEnergyCalcData.push(solarEnergyData.rows[0].map(row => ({
|
|
v: row, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11 },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" }
|
|
}
|
|
})));
|
|
}
|
|
// 说明
|
|
solarEnergyCalcData.push([], [{
|
|
v: '说明:太阳能板功率可选,蓄电池和太阳能板太阳能板需冗余,建议乘以1.5的系数,连续阴雨天可根据当地的实际连续阴雨天填写。', t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11 },
|
|
fill: { fgColor: { rgb: "ffff00" } },
|
|
}
|
|
}], []);
|
|
solarEnergyCalcMerges.push({ s: { r: solarEnergyCalcData.length - 2, c: 0 }, e: { r: solarEnergyCalcData.length - 2, c: 13 } });
|
|
// 功率统计表
|
|
solarEnergyCalcData.push([{
|
|
v: powerTitle, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 16, bold: true },
|
|
border: cellBorder,
|
|
alignment: { vertical: 'center', horizontal: 'center' },
|
|
}
|
|
}].concat(new Array(6).fill(emptyBorderCell)));
|
|
solarEnergyCalcMerges.push({ s: { r: solarEnergyCalcData.length - 1, c: 0 }, e: { r: solarEnergyCalcData.length - 1, c: 6 } });
|
|
if (powerData.headers?.length && powerData.rows?.length) {
|
|
solarEnergyCalcData.push(powerData.headers.map(header => ({
|
|
v: header, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11 },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" }
|
|
}
|
|
})));
|
|
for (let i = 0; i < powerData.rows.length; i++) {
|
|
const row = powerData.rows[i];
|
|
solarEnergyCalcData.push(row.map(cell => ({
|
|
v: cell, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11 },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" }
|
|
}
|
|
})));
|
|
}
|
|
}
|
|
tableData.push({
|
|
name: '太阳能计算',
|
|
data: solarEnergyCalcData,
|
|
merges: solarEnergyCalcMerges,
|
|
cols: [
|
|
{ wch: 12 },
|
|
{ wch: 20 },
|
|
{ wch: 24 },
|
|
{ wch: 12 },
|
|
{ wch: 24 },
|
|
{ wch: 15 },
|
|
{ wch: 15 },
|
|
{ wch: 20 },
|
|
{ wch: 20 },
|
|
{ wch: 15 },
|
|
{ wch: 20 },
|
|
{ wch: 15 },
|
|
{ wch: 15 },
|
|
{ wch: 15 },
|
|
]
|
|
});
|
|
}
|
|
|
|
/********** sheet 工期核算清单 **********/
|
|
let durationMerges = [];
|
|
let durationTabelData = [];
|
|
if (durationData.headers?.length && durationData.rows?.length) {
|
|
durationTabelData.push(durationData.headers.map(header => ({
|
|
v: header, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11, bold: true },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" }
|
|
}
|
|
})));
|
|
for (let i = 0; i < durationData.rows.length; i++) {
|
|
const row = durationData.rows[i];
|
|
if (row[1] === '外包(天)') {
|
|
row[row.length - 1] = epiboleDays;
|
|
durationTabelData.push(row.map(cell => ({
|
|
v: cell, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11 },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" }
|
|
}
|
|
})));
|
|
durationTabelData[durationTabelData.length - 1][2].s.alignment.horizontal = 'right';
|
|
durationMerges.push({ s: { r: durationTabelData.length - 1, c: 2 }, e: { r: durationTabelData.length - 1, c: 6 } });
|
|
} else if (row[1] === '项目经理(天)') {
|
|
row[row.length - 1] = projectManagerDays;
|
|
durationTabelData.push(row.map(cell => ({
|
|
v: cell, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11 },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" }
|
|
}
|
|
})));
|
|
durationTabelData[durationTabelData.length - 1][2].s.alignment.horizontal = 'right';
|
|
durationMerges.push({ s: { r: durationTabelData.length - 1, c: 2 }, e: { r: durationTabelData.length - 1, c: 6 } });
|
|
} else {
|
|
durationTabelData.push(row.map(cell => ({
|
|
v: cell, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11 },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" }
|
|
}
|
|
})));
|
|
}
|
|
}
|
|
}
|
|
tableData.push({
|
|
name: '工期核算清单',
|
|
data: durationTabelData,
|
|
merges: durationMerges,
|
|
cols: [
|
|
{ wch: 8 },
|
|
{ wch: 20 },
|
|
{ wch: 50 },
|
|
{ wch: 20 },
|
|
{ wch: 30 },
|
|
{ wch: 60 },
|
|
{ wch: 12 },
|
|
{ wch: 20 },
|
|
]
|
|
});
|
|
|
|
/********** sheet 核算要点 **********/
|
|
let mainPointTabelData = [];
|
|
let mainPointMerges = [];
|
|
const mpBoldCell = (v) => ({
|
|
v, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11, bold: true },
|
|
border: cellBorder,
|
|
alignment: alignmentCenter
|
|
}
|
|
});
|
|
const mpCell = (v) => ({
|
|
v, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11 },
|
|
border: cellBorder,
|
|
alignment: alignmentCenter
|
|
}
|
|
});
|
|
const topHeaderRowIndex = mainPointTabelData.push([
|
|
mpBoldCell('明细'), emptyBorderCell,
|
|
mpBoldCell('具体内容'), emptyBorderCell, emptyBorderCell,
|
|
mpBoldCell('需明确'), mpBoldCell('常见错误')
|
|
]) - 1;
|
|
mainPointMerges.push({ s: { r: topHeaderRowIndex, c: 0 }, e: { r: topHeaderRowIndex, c: 1 } });
|
|
mainPointMerges.push({ s: { r: topHeaderRowIndex, c: 2 }, e: { r: topHeaderRowIndex, c: 4 } });
|
|
const topRows = [
|
|
['清单编制人', '', '', ''],
|
|
['清单审核人(姓名)', '', '', ''],
|
|
['项目特殊环境', '常规', '', ''],
|
|
['监测项数量', '', '几个监测项', ''],
|
|
];
|
|
for (const r of topRows) {
|
|
const rowIndex = mainPointTabelData.push([
|
|
mpCell(r[0]),
|
|
emptyBorderCell,
|
|
mpCell(r[1]),
|
|
emptyBorderCell,
|
|
emptyBorderCell,
|
|
mpCell(r[2]),
|
|
mpCell(r[3])
|
|
]) - 1;
|
|
mainPointMerges.push({ s: { r: rowIndex, c: 0 }, e: { r: rowIndex, c: 1 } });
|
|
if (r[0] === '项目特殊环境' && r[1] === '常规') {
|
|
mainPointMerges.push({ s: { r: rowIndex, c: 2 }, e: { r: rowIndex, c: 5 } });
|
|
} else {
|
|
mainPointMerges.push({ s: { r: rowIndex, c: 2 }, e: { r: rowIndex, c: 4 } });
|
|
}
|
|
}
|
|
const basicInfoRows = [
|
|
['', '', '项目名称', ''],
|
|
['', '', '客户全称(主要看是否二次销售)', ''],
|
|
['', '', '竞争对手情况', ''],
|
|
['', '', '项目地点,明确到省市(必填)', ''],
|
|
];
|
|
let firstBasicInfoRowIndex = null;
|
|
for (let i = 0; i < basicInfoRows.length; i++) {
|
|
const r = basicInfoRows[i];
|
|
const rowIndex2 = mainPointTabelData.push([
|
|
i === 0 ? mpBoldCell('基本信息') : emptyBorderCell,
|
|
emptyBorderCell,
|
|
mpCell(r[1]),
|
|
emptyBorderCell,
|
|
emptyBorderCell,
|
|
mpCell(r[2]),
|
|
mpCell(r[3])
|
|
]) - 1;
|
|
if (i === 0) firstBasicInfoRowIndex = rowIndex2;
|
|
mainPointMerges.push({ s: { r: rowIndex2, c: 2 }, e: { r: rowIndex2, c: 4 } });
|
|
}
|
|
if (firstBasicInfoRowIndex != null) {
|
|
mainPointMerges.push({ s: { r: firstBasicInfoRowIndex, c: 0 }, e: { r: firstBasicInfoRowIndex + basicInfoRows.length - 1, c: 0 } });
|
|
}
|
|
for (let k = 0; k < 4; k++) {
|
|
mainPointTabelData.push(new Array(7).fill(emptyCell));
|
|
}
|
|
// 数据
|
|
if (mainPointData?.headers?.length && mainPointData?.rows?.length) {
|
|
mainPointTabelData.push(mainPointData.headers.map(header => ({
|
|
v: header, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11, bold: true },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" }
|
|
}
|
|
})));
|
|
for (let i = 0; i < mainPointData.rows.length; i++) {
|
|
const row = mainPointData.rows[i];
|
|
mainPointTabelData.push(row.map(cell => ({
|
|
v: cell, t: "s",
|
|
s: {
|
|
font: { name: '宋体', sz: 11 },
|
|
border: cellBorder,
|
|
alignment: { horizontal: "center", vertical: "center" }
|
|
}
|
|
})));
|
|
}
|
|
}
|
|
tableData.push({
|
|
name: '核算要点',
|
|
data: mainPointTabelData,
|
|
merges: mainPointMerges,
|
|
cols: [
|
|
{ wch: 50 },
|
|
{ wch: 40 },
|
|
{ wch: 15 },
|
|
{ wch: 24 },
|
|
{ wch: 12 },
|
|
{ wch: 60 },
|
|
{ wch: 15 },
|
|
]
|
|
});
|
|
|
|
return tableData;
|
|
}
|
|
|
|
module.exports.generationXlsxData = generationXlsxData;
|
|
|
|
module.exports.putScheme = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const schemeId = ctx.params.schemeId;
|
|
const body = ctx.request.body;
|
|
if (!schemeId) { throw '缺少参数' };
|
|
await models.SchemeList.update(
|
|
{ ...body },
|
|
{ where: { id: schemeId } }
|
|
);
|
|
ctx.status = 204;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '修改方案失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports.delScheme = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const schemeId = ctx.params.schemeId;
|
|
if (!schemeId) { throw '缺少参数' };
|
|
await models.SchemeList.destroy({
|
|
where: { id: schemeId },
|
|
});
|
|
ctx.status = 204;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '删除方案失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports.exportScheme = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const schemeId = ctx.params.schemeId;
|
|
if (!schemeId) { throw '缺少参数' };
|
|
const scheme = await models.SchemeList.findOne({
|
|
where: { id: schemeId },
|
|
raw: true,
|
|
});
|
|
if (!scheme) { throw '方案不存在' };
|
|
if (scheme.status !== 'success') {
|
|
throw '方案未完成';
|
|
}
|
|
|
|
const wb = XLSXS.utils.book_new(); // 创建工作簿
|
|
for (let sheetData of scheme.tableData) {
|
|
const ws = XLSXS.utils.aoa_to_sheet(sheetData.data); // 创建工作表
|
|
XLSXS.utils.book_append_sheet(wb, ws, sheetData.name); // 将工作表添加到工作簿
|
|
if (sheetData.merges && sheetData.merges.length > 0) {
|
|
ws['!merges'] = sheetData.merges;
|
|
}
|
|
if (sheetData.cols && sheetData.cols.length > 0) {
|
|
ws['!cols'] = sheetData.cols;
|
|
}
|
|
}
|
|
|
|
// 生成xlsx文件返回
|
|
const xlsxBuffer = XLSXS.write(wb, { type: 'buffer', bookType: 'xlsx' });
|
|
const xlsxFileName = `${scheme.name}.xlsx`;
|
|
ctx.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
ctx.set('Content-Disposition', `attachment; filename="${encodeURIComponent(xlsxFileName)}"`);
|
|
ctx.set('Content-Length', xlsxBuffer.length);
|
|
ctx.body = xlsxBuffer;
|
|
ctx.status = 200;
|
|
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '导出方案失败'
|
|
};
|
|
}
|
|
}
|
|
|
|
function safeParse(jsonString) {
|
|
// 将所有的 \* 替换成 *
|
|
const fixed = jsonString.replace(/\\(?=\*)/g, '');
|
|
return JSON.parse(fixed);
|
|
}
|
|
function getMainPointData(userText, epiboleDays, hasSolar, hasFiber) {
|
|
// const hasSolar = (() => {
|
|
// if (userText && userText['电力系统(太阳能)']) return true;
|
|
// if (!userText || typeof userText !== 'object') return false;
|
|
// for (const k of Object.keys(userText)) {
|
|
// if (String(k).includes('太阳能')) return true;
|
|
// const v = userText[k];
|
|
// if (typeof v === 'string' && v.includes('太阳能')) return true;
|
|
// if (v && typeof v === 'object') {
|
|
// for (const kk of Object.keys(v)) {
|
|
// const vv = v[kk];
|
|
// if (typeof vv === 'string' && vv.includes('太阳能')) return true;
|
|
// }
|
|
// }
|
|
// }
|
|
// return false;
|
|
// })();
|
|
let wbNum = epiboleDays + 2
|
|
const electricSource = hasSolar ? '太阳能' : '市电';
|
|
const electricLine = hasFiber ? '有线' : '无线';
|
|
const headers = ['类别', '明细', '具体内容', '人数/类型', '天数/米数/个数/趟数', '承担方', '需明确'];
|
|
const rows = [
|
|
['施工成本', '项目经理', '项目经理工资费用', '1', wbNum, '我司', '几个项目经理多长时间'],
|
|
['施工成本', '项目经理', '天窗点系数', '1', wbNum, '我司', '考虑天窗时间需要备注天窗点时长;\n结合封路、天窗时长考虑'],
|
|
['施工成本', '项目经理', '项目经理住宿及补贴', '1', wbNum, '我司', '食宿由谁承担'],
|
|
['施工成本', '项目经理', '差旅', '1', '1', '我司', '来回差旅费用几趟(往返算一趟)'],
|
|
['施工成本', '项目经理', '交通(主要指现场用车打车等)', '1', '1', '我司', '现场是否需要用车'],
|
|
['施工成本', '机械使用(主要指桥检车及臂长、登高车、脚手架、船只等)', '桁架桥检车 桁架长16m,跨宽2.5m,跨高3.7', '/', '', '我司', '工作量,由谁承担'],
|
|
['施工成本', '机械使用(主要指桥检车及臂长、登高车、脚手架、船只等)', '曲臂式登高车 臂长13.5m', '/', '', '我司', '结合封路、天窗时长考虑'],
|
|
['施工成本', '机械使用(主要指桥检车及臂长、登高车、脚手架、船只等)', '移动脚手架', '/', '', '我司', '移动脚手架一副尺寸长宽高为2*0.8*1.2,前面写幅数,此处备注写使用天数,且叠加高度不得超过6米'],
|
|
['施工成本', '机械使用(主要指桥检车及臂长、登高车、脚手架、船只等)', '小船(渔船类)', '/', '', '我司', ''],
|
|
['施工成本', '机械使用(主要指桥检车及臂长、登高车、脚手架、船只等)', '钢管脚手架(立方米)', '/', '', '我司', '写明需要搭设的长宽高的尺寸,计算出立方数后填入\n此项若有,请写明规格等信息,若无写0'],
|
|
['施工成本', '土建等', '熔纤', '/', '', '我司', '熔纤次数'],
|
|
['施工成本', '土建等', '基坑沙石', '/', '', '我司', '打孔,岩质/土质/工作量,由谁承担,注明是否工勘过且工勘后需尽可能详细的提供地质报告、现场环境情况、是否可以直接机械运上去、取水取电点是否有'],
|
|
['施工成本', '土建等', 'GNSS混凝土墩', '/', '', '我司', 'GNSS钢立柱基础:600*600*800mm'],
|
|
['施工成本', '土建等', '立杆基础浇筑(2米)', '/', '', '我司', '测斜保护墩:400*400*400mm'],
|
|
['施工成本', '土建等', '立杆基础浇筑(4米)', '/', '', '我司', '3米/4米视频立杆:600*600*800mm'],
|
|
['施工成本', '土建等', '立杆基础浇筑(6米)', '/', '', '我司', '12米抓拍立杆:按照1800*1800*2000mm计算'],
|
|
['施工成本', '土建等', '落地机箱底座', '/', '', '我司', '1号采集箱底座基础:700*450*200mm'],
|
|
['施工成本', '外包', '工资', '2', wbNum, '我司', '若有静力水准仪,需间隔3个月进行补液,每次核算1人/次外包差旅'],
|
|
['施工成本', '外包', '住宿补贴', '2', wbNum, '我司', ''],
|
|
['施工成本', '外包', '差旅', '2', wbNum, '我司', ''],
|
|
['施工成本', '外包', '用车', '2', '2', '我司', '来回差旅费用几趟(往返算一趟)'],
|
|
['施工成本', '外包', '临时用电', '2', '0', '我司', '几台发电机,使用几天'],
|
|
['施工成本', '外包', '其他:机械', '挖掘机 标准斗容量0.1L', '0', '我司', '填写台班数'],
|
|
['施工成本', '外包', '交通管制(封路)', '', '', '我司', '封几个车道,封几天'],
|
|
['施工成本', '外包高空作业(人员)', '工资', '', '', '我司', '投入几个外包人员,多长工期'],
|
|
['施工成本', '外包高空作业(人员)', '住宿补贴', '', '', '我司', ''],
|
|
['施工成本', '外包高空作业(人员)', '差旅', '', '', '我司', ''],
|
|
['施工成本', '外包高空作业(人员)', '用车', '', '', '我司', '来回差旅费用几趟(往返算一趟)'],
|
|
['施工成本', '特种作业(人员)', '工资', '', '', '我司', '明缺什么工种,投入几个多长工期'],
|
|
['施工成本', '特种作业(人员)', '住宿补贴', '', '', '我司', ''],
|
|
['施工成本', '特种作业(人员)', '差旅', '', '', '我司', ''],
|
|
['施工成本', '特种作业(人员)', '用车', '', '', '我司', '来回差旅费用几趟(往返算一趟)'],
|
|
['施工成本', '用电', '用电', '/', electricSource, '客户', '用电由谁解决'],
|
|
['施工成本', '通讯方式', '通讯方式', '/', electricLine, '客户', '是否涉及光纤铺线'],
|
|
['施工成本', '其他', '其他', '/', '', '/', '例如投标等'],
|
|
['施工成本', '资金占用', '资金占用', '/', '', '/', '回款方式'],
|
|
['软件', '软件', '软件', '我司软件开发', '否', '/', '是否需要软件开发,若是走评估流程,时长软件会传递商务'],
|
|
['运维', '项目经理', '项目经理工资费用', '0', '0', '我司', '几个项目经理多长时间'],
|
|
['运维', '项目经理', '天窗点系数', '0', '0', '我司', '考虑天窗时间需要备注天窗点时长;\n结合封路、天窗时长考虑'],
|
|
['运维', '项目经理', '项目经理住宿及补贴', '0', '0', '我司', '食宿由谁承担'],
|
|
['运维', '项目经理', '差旅', '0', '0', '我司', '来回差旅费用几趟(往返算一趟)'],
|
|
['运维', '项目经理', '交通(主要指现场用车打车等)', '0', '0', '我司', '现场是否需要用车'],
|
|
['运维', '外包', '工资', '0', '0', '我司', ''],
|
|
['运维', '外包', '住宿补贴', '0', '0', '我司', ''],
|
|
['运维', '外包', '差旅', '0', '0', '我司', ''],
|
|
['运维', '外包', '用车', '0', '0', '我司', '来回差旅费用几趟(往返算一趟)'],
|
|
['运维机械', '运维机械', '运维机械', '桁架桥检车 桁架长16m,跨宽2.5m,跨高3.7', '', '客户', ''],
|
|
['运维交通管制(封路)', '运维交通管制(封路)', '运维交通管制(封路)', '0', '', '客户', ''],
|
|
['硬件运维(含质保)', '硬件运维(含质保)', '硬件运维(含质保)', '/', '', '我司', '几年\n1、静力水准仪运维,如客户自行加液,防冻液数量中就不体现(在运维中备注客户自行加液)\n2、如需我们加液,防冻液的数量统计在清单。在运维中备注,防冻液加液去几趟,第一年不算。'],
|
|
['软件运维', '软件运维', '软件运维', '/', '', '我司', '几年'],
|
|
['风险告知', '风险告知', '风险告知(示例,具体看项目有无进行增减):\n1、取电位置无法明确,涉及后期增加线缆辅材及人工;\n2、工勘过程无法到达,传感器安装位置及走线无法确认;\n3、交通不便,无法确定机械能否到达现场;\n4、青苗费,当地居民索要费用的;\n5、某项工作界限没有明确费用承担方,但又是实施必备项;\n6、施工机械需要客户配合提供,后期无法提供存在风险。\n7、其他涉及项目实施、影响实施成本内容;', '/', '', '/', '']
|
|
];
|
|
return { headers, rows };
|
|
}
|
|
|
|
module.exports.getMainPointData = getMainPointData;
|
|
|