|
|
@@ -1607,9 +1607,10 @@ module.exports = app => {
|
|
|
return;
|
|
|
}
|
|
|
if (query.module === 'attachment') {
|
|
|
- if (!['current', 'named'].includes(tender.scope) || period.type !== 'none' || period.scope !== 'none' ||
|
|
|
- query.aggregation !== 'current' || filters.status || filters.contract_type || filters.chapter || filters.bill_code) {
|
|
|
- throw '附件查询一次只支持一个标段,请进入标段页面或指定标段名称';
|
|
|
+ if (!['current', 'named', 'none'].includes(tender.scope) || query.operation !== 'list' || query.metrics.length ||
|
|
|
+ period.type !== 'none' || period.scope !== 'none' || query.aggregation !== 'current' ||
|
|
|
+ filters.status || filters.contract_type || filters.chapter || filters.bill_code) {
|
|
|
+ throw '附件查询范围不正确,请进入项目或标段页面后重新查询';
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
@@ -1643,6 +1644,25 @@ module.exports = app => {
|
|
|
|
|
|
async _loadParameterScope(query, context) {
|
|
|
const scope = await this._loadScope('', {});
|
|
|
+ if (query.module === 'attachment' && query.tender.scope === 'none') {
|
|
|
+ const path = String(context.path || '').split(/[?#]/)[0];
|
|
|
+ const subProjectMatch = path.match(/^\/sp\/([^/]+)(?:\/|$)/);
|
|
|
+ const tenderMatch = path.match(/^\/(?:wap\/)?tender\/([1-9]\d*)(?:\/|$)/);
|
|
|
+ let subProjectId = subProjectMatch && subProjectMatch[1];
|
|
|
+ if (!subProjectId && tenderMatch) {
|
|
|
+ const tender = scope.tenders.find(item => String(item.id) === tenderMatch[1]);
|
|
|
+ subProjectId = tender && String(tender.subProjectId || tender.spid);
|
|
|
+ }
|
|
|
+ if (!subProjectId) throw '无法确定当前项目,请进入项目或标段页面后查询资料管理文件';
|
|
|
+ const subProjects = scope.subProjects.filter(item => String(item.id) === String(subProjectId));
|
|
|
+ if (subProjects.length !== 1) throw '无法确定当前项目,或您无权查看该项目';
|
|
|
+ return {
|
|
|
+ subProjects,
|
|
|
+ tenders: scope.tenders.filter(item => String(item.subProjectId || item.spid) === String(subProjectId)),
|
|
|
+ tenderOnly: false,
|
|
|
+ projectAttachment: true,
|
|
|
+ };
|
|
|
+ }
|
|
|
const subProjectMatch = query.filters.status === 'pending_me' && ['none', 'all'].includes(query.tender.scope)
|
|
|
? String(context.path || '').match(/^\/sp\/([^/?#]+)(?:\/|$)/) : null;
|
|
|
if (subProjectMatch) {
|
|
|
@@ -1839,21 +1859,27 @@ module.exports = app => {
|
|
|
|
|
|
/** 汇总单个标段分散在各业务模块中的附件。 */
|
|
|
async _queryAttachments(scope, filters) {
|
|
|
+ if (scope.projectAttachment) return await this._queryDataManagementAttachments(scope, filters);
|
|
|
if (scope.tenders.length !== 1) throw '附件查询一次只支持一个标段,请补充完整标段名称';
|
|
|
const tenderId = Number(scope.tenders[0].id);
|
|
|
- const keyword = filters.keyword.trim().toLowerCase();
|
|
|
+ const keyword = this._normalizeAttachmentFilenameKeyword(filters.keyword);
|
|
|
const result = [];
|
|
|
const contractIds = new Set((await this._loadContracts(scope)).map(item => String(item.id)));
|
|
|
+ const stages = await this.ctx.service.stage.getAllDataByCondition({ where: { tid: tenderId } });
|
|
|
+ const stageOrderMap = new Map(stages.map(item => [String(item.id), item.order]));
|
|
|
+ const stageOrderSet = new Set(stages.map(item => String(item.order)));
|
|
|
for (const source of this._attachmentSources()) {
|
|
|
+ if (source.projectScoped) continue;
|
|
|
let rows = [];
|
|
|
if (source.parent) {
|
|
|
const parents = source.parent.permission === 'financialPay'
|
|
|
? await this.ctx.service.financialPay.getListByStatus(scope.subProjects[0].id, null, null, 0, [tenderId])
|
|
|
: await this.ctx.service[source.parent.service].getAllDataByCondition({ where: { tid: tenderId } });
|
|
|
for (const parent of parents) {
|
|
|
- rows.push(...await this.ctx.service[source.service].getAllDataByCondition({
|
|
|
+ const childRows = await this.ctx.service[source.service].getAllDataByCondition({
|
|
|
where: { [source.parent.foreignKey]: parent.id },
|
|
|
- }));
|
|
|
+ });
|
|
|
+ rows.push(...childRows.map(row => Object.assign({}, row, { assistantParent: parent })));
|
|
|
}
|
|
|
} else {
|
|
|
const where = { [source.tenderField]: tenderId };
|
|
|
@@ -1862,6 +1888,8 @@ module.exports = app => {
|
|
|
}
|
|
|
for (const row of rows) {
|
|
|
if (source.contractScoped && !contractIds.has(String(row.cid))) continue;
|
|
|
+ const attachmentStageOrder = this._getAttachmentStageOrder(source.key, row, stageOrderMap, stageOrderSet);
|
|
|
+ if (attachmentStageOrder === null) continue;
|
|
|
let files = [row];
|
|
|
if (source.embedded) {
|
|
|
try {
|
|
|
@@ -1889,6 +1917,7 @@ module.exports = app => {
|
|
|
module: source.label,
|
|
|
uploadTime,
|
|
|
filesize: file.filesize || file.file_size || '',
|
|
|
+ pageUrl: this._getAttachmentPageUrl(source.key, scope.subProjects[0].id, tenderId, file, row, attachmentStageOrder),
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
@@ -1904,9 +1933,10 @@ module.exports = app => {
|
|
|
],
|
|
|
items: result.slice(0, this.maxItems).map(item => ({
|
|
|
title: item.filename,
|
|
|
- description: [item.module, item.uploadTime ? this.ctx.moment(item.uploadTime).format('YYYY-MM-DD HH:mm') : ''].filter(Boolean).join(' · '),
|
|
|
+ description: [item.module, this._formatAttachmentTime(item.uploadTime)].filter(Boolean).join(' · '),
|
|
|
value: this._formatAttachmentSize(item.filesize),
|
|
|
url: `/assistant/attachment/${encodeURIComponent(item.source)}/${encodeURIComponent(item.id)}`,
|
|
|
+ pageUrl: item.pageUrl,
|
|
|
})),
|
|
|
truncated: result.length > this.maxItems,
|
|
|
suggestions: [],
|
|
|
@@ -1919,7 +1949,30 @@ module.exports = app => {
|
|
|
if (!source || !id) throw '附件参数不正确';
|
|
|
let file;
|
|
|
let tenderId;
|
|
|
- if (source.embedded) {
|
|
|
+ if (source.projectScoped) {
|
|
|
+ file = await this.ctx.service[source.service].getDataById(id);
|
|
|
+ if (!file || Number(file.is_deleted) !== 0 || !file.filepath) throw '附件不存在或已删除';
|
|
|
+ const scope = await this._loadScope('', {});
|
|
|
+ const subProject = scope.subProjects.find(item => String(item.id) === String(file.spid));
|
|
|
+ if (!subProject) throw '您无权查看该附件';
|
|
|
+ this.ctx.subProject = subProject;
|
|
|
+ const filing = await this.ctx.service.filing.getDataById(file.filing_id);
|
|
|
+ if (!filing || Number(filing.is_deleted) !== 0 || String(filing.spid) !== String(subProject.id)) {
|
|
|
+ throw '附件所在资料目录不存在';
|
|
|
+ }
|
|
|
+ const isAdmin = Number(this.ctx.session.sessionUser.is_admin) === 1;
|
|
|
+ const permission = await this.ctx.service.subProjectFilingPermission.getResolvedPermission(
|
|
|
+ subProject.id,
|
|
|
+ this.ctx.session.sessionUser.accountId,
|
|
|
+ filing.id,
|
|
|
+ filing.filing_type,
|
|
|
+ subProject.permission.file_permission,
|
|
|
+ subProject.permission.filing_type,
|
|
|
+ isAdmin
|
|
|
+ );
|
|
|
+ if (this._hasDataManagementConfigPermission(subProject)) permission.can_view = 1;
|
|
|
+ if (!permission.can_view) throw '您无权查看该附件';
|
|
|
+ } else if (source.embedded) {
|
|
|
const separator = id.indexOf(':');
|
|
|
if (separator <= 0) throw '附件参数不正确';
|
|
|
const row = await this.ctx.service[source.service].getDataById(id.slice(0, separator));
|
|
|
@@ -1945,18 +1998,20 @@ module.exports = app => {
|
|
|
}
|
|
|
}
|
|
|
if (!file || (source.notDeleted && Number(file.is_deleted) !== 0) || !file.filepath) throw '附件不存在或已删除';
|
|
|
- const scope = await this._loadScope('', {});
|
|
|
- const tender = scope.tenders.find(item => String(item.id) === tenderId);
|
|
|
- if (!tenderId || !tender) throw '您无权查看该附件';
|
|
|
- if (source.contractScoped) {
|
|
|
- const subProjectId = String(tender.subProjectId || tender.spid);
|
|
|
- const attachmentScope = {
|
|
|
- subProjects: scope.subProjects.filter(item => String(item.id) === subProjectId),
|
|
|
- tenders: [tender],
|
|
|
- tenderOnly: true,
|
|
|
- };
|
|
|
- const contractIds = new Set((await this._loadContracts(attachmentScope)).map(item => String(item.id)));
|
|
|
- if (!contractIds.has(String(file.cid))) throw '您无权查看该附件';
|
|
|
+ if (!source.projectScoped) {
|
|
|
+ const scope = await this._loadScope('', {});
|
|
|
+ const tender = scope.tenders.find(item => String(item.id) === tenderId);
|
|
|
+ if (!tenderId || !tender) throw '您无权查看该附件';
|
|
|
+ if (source.contractScoped) {
|
|
|
+ const subProjectId = String(tender.subProjectId || tender.spid);
|
|
|
+ const attachmentScope = {
|
|
|
+ subProjects: scope.subProjects.filter(item => String(item.id) === subProjectId),
|
|
|
+ tenders: [tender],
|
|
|
+ tenderOnly: true,
|
|
|
+ };
|
|
|
+ const contractIds = new Set((await this._loadContracts(attachmentScope)).map(item => String(item.id)));
|
|
|
+ if (!contractIds.has(String(file.cid))) throw '您无权查看该附件';
|
|
|
+ }
|
|
|
}
|
|
|
const filename = String(file.file_name || file.filename || '附件').trim();
|
|
|
const fileext = String(file.fileext || '').trim();
|
|
|
@@ -1967,9 +2022,10 @@ module.exports = app => {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- /** 统一附件查询白名单;仅包含能直接归属到标段且具有独立文件记录的数据源。 */
|
|
|
+ /** 统一附件查询白名单;标段附件按业务来源查询,资料管理文件按项目及目录权限查询。 */
|
|
|
_attachmentSources() {
|
|
|
return [
|
|
|
+ { key: 'data_management', service: 'file', label: '资料管理', projectScoped: true, notDeleted: true },
|
|
|
{ key: 'ledger', service: 'ledgerAtt', tenderField: 'tid', label: '台账' },
|
|
|
{ key: 'stage', service: 'stageAtt', tenderField: 'tid', label: '计量' },
|
|
|
{ key: 'stage_detail', service: 'stageDetailAtt', tenderField: 'tid', label: '计量清单附件', embedded: true },
|
|
|
@@ -1993,7 +2049,6 @@ module.exports = app => {
|
|
|
{ key: 'quality_inspection', service: 'qualityInspectionAtt', tenderField: 'tid', label: '质量巡检' },
|
|
|
{ key: 'safe_inspection', service: 'safeInspectionAtt', tenderField: 'tid', label: '安全巡检' },
|
|
|
{ key: 'safe_stage', service: 'safeStageFile', tenderField: 'tid', label: '安全计量', notDeleted: true },
|
|
|
- { key: 'temp', service: 'tempFile', tenderField: 'tid', label: '临时附件' },
|
|
|
];
|
|
|
}
|
|
|
|
|
|
@@ -2007,6 +2062,166 @@ module.exports = app => {
|
|
|
if (bytes < 1024 * 1024 * 1024) return (bytes / 1024 / 1024).toFixed(1) + ' MB';
|
|
|
return (bytes / 1024 / 1024 / 1024).toFixed(1) + ' GB';
|
|
|
}
|
|
|
+
|
|
|
+ /** 查询当前项目资料管理中有权查看的文件。 */
|
|
|
+ async _queryDataManagementAttachments(scope, filters) {
|
|
|
+ if (scope.subProjects.length !== 1) throw '资料管理文件查询一次只支持一个项目';
|
|
|
+ const subProject = scope.subProjects[0];
|
|
|
+ this.ctx.subProject = subProject;
|
|
|
+ const filings = await this.ctx.service.filing.getValidFiling(subProject.id, 'all');
|
|
|
+ const sessionUser = this.ctx.session.sessionUser;
|
|
|
+ const permissionMap = await this.ctx.service.subProjectFilingPermission.getPermissionMap(
|
|
|
+ subProject.id,
|
|
|
+ sessionUser.accountId,
|
|
|
+ filings,
|
|
|
+ subProject.permission.filing_type,
|
|
|
+ subProject.permission.file_permission,
|
|
|
+ Number(sessionUser.is_admin) === 1
|
|
|
+ );
|
|
|
+ if (this._hasDataManagementConfigPermission(subProject)) {
|
|
|
+ Object.keys(permissionMap).forEach(filingId => { permissionMap[filingId].can_view = 1; });
|
|
|
+ }
|
|
|
+ const visibleFilings = filings.filter(filing => permissionMap[filing.id] && permissionMap[filing.id].can_view);
|
|
|
+ const visibleFilingIds = visibleFilings.map(filing => filing.id);
|
|
|
+ const filingMap = new Map(filings.map(filing => [String(filing.id), filing]));
|
|
|
+ const pathCache = new Map();
|
|
|
+ const getFilingPath = filing => {
|
|
|
+ const cacheKey = String(filing.id);
|
|
|
+ if (pathCache.has(cacheKey)) return pathCache.get(cacheKey);
|
|
|
+ const names = [];
|
|
|
+ const visited = new Set();
|
|
|
+ let current = filing;
|
|
|
+ while (current && !visited.has(String(current.id))) {
|
|
|
+ visited.add(String(current.id));
|
|
|
+ if (current.name) names.unshift(current.name);
|
|
|
+ current = filingMap.get(String(current.tree_pid));
|
|
|
+ }
|
|
|
+ const filingPath = names.join(' / ');
|
|
|
+ pathCache.set(cacheKey, filingPath);
|
|
|
+ return filingPath;
|
|
|
+ };
|
|
|
+ const rows = visibleFilingIds.length ? await this.ctx.service.file.getAllDataByCondition({
|
|
|
+ where: { spid: subProject.id, filing_id: visibleFilingIds, is_deleted: 0 },
|
|
|
+ orders: [['update_time', 'desc']],
|
|
|
+ }) : [];
|
|
|
+ const keyword = filters.keyword.trim().toLowerCase();
|
|
|
+ const filenameKeyword = this._normalizeAttachmentFilenameKeyword(filters.keyword);
|
|
|
+ const result = rows.map(file => {
|
|
|
+ const filing = filingMap.get(String(file.filing_id));
|
|
|
+ const directory = filing ? getFilingPath(filing) : '';
|
|
|
+ const filename = String(file.filename || '').trim();
|
|
|
+ const fileext = String(file.fileext || '').trim();
|
|
|
+ const fullName = filename && fileext && !filename.toLowerCase().endsWith(fileext.toLowerCase())
|
|
|
+ ? filename + fileext : filename;
|
|
|
+ return {
|
|
|
+ source: 'data_management',
|
|
|
+ id: String(file.id),
|
|
|
+ filename: fullName,
|
|
|
+ directory,
|
|
|
+ filingId: String(file.filing_id),
|
|
|
+ uploadTime: file.create_time || file.update_time || '',
|
|
|
+ filesize: file.filesize || '',
|
|
|
+ };
|
|
|
+ }).filter(file => {
|
|
|
+ if (!file.filename || !keyword) return !!file.filename;
|
|
|
+ const filenameMatched = filenameKeyword && file.filename.toLowerCase().indexOf(filenameKeyword) !== -1;
|
|
|
+ const directoryMatched = file.directory.toLowerCase().indexOf(keyword) !== -1;
|
|
|
+ return filenameMatched || directoryMatched;
|
|
|
+ });
|
|
|
+ result.sort((a, b) => new Date(b.uploadTime || 0).getTime() - new Date(a.uploadTime || 0).getTime());
|
|
|
+ const directories = new Set(result.map(item => item.directory).filter(Boolean));
|
|
|
+ return {
|
|
|
+ answer: `资料管理中找到${result.length}个文件或者目录。`,
|
|
|
+ summary: [
|
|
|
+ { label: '文件数量', value: result.length + '个' },
|
|
|
+ { label: '文件所属目录', value: directories.size + '个' },
|
|
|
+ ],
|
|
|
+ items: result.slice(0, this.maxItems).map(item => ({
|
|
|
+ title: item.filename,
|
|
|
+ description: ['资料管理', item.directory, this._formatAttachmentTime(item.uploadTime)]
|
|
|
+ .filter(Boolean).join(' · '),
|
|
|
+ value: this._formatAttachmentSize(item.filesize),
|
|
|
+ url: `/assistant/attachment/${encodeURIComponent(item.source)}/${encodeURIComponent(item.id)}`,
|
|
|
+ pageUrl: `/sp/${encodeURIComponent(subProject.id)}/filesjs?filing_id=${encodeURIComponent(item.filingId)}`,
|
|
|
+ })),
|
|
|
+ truncated: result.length > this.maxItems,
|
|
|
+ suggestions: [],
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 资料管理目录配置人员与管理员和页面保持一致,可以查看全部目录。 */
|
|
|
+ _hasDataManagementConfigPermission(subProject) {
|
|
|
+ if (Number(this.ctx.session.sessionUser.is_admin) === 1) return true;
|
|
|
+ const permissions = subProject && subProject.permission && subProject.permission.file_permission || [];
|
|
|
+ const constants = this.ctx.service.subProjPermission.PermissionConst.file;
|
|
|
+ return [constants.manage_dir.value, constants.auth_user.value].some(value => permissions.indexOf(value) >= 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 查文件名时去掉句式两端的“文件/附件”,目录匹配仍使用原关键词。 */
|
|
|
+ _normalizeAttachmentFilenameKeyword(value) {
|
|
|
+ return String(value || '').trim().toLowerCase()
|
|
|
+ .replace(/^(?:文件|附件)\s*|\s*(?:文件|附件)$/g, '').trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 附件结果跳转到所属业务页;无法稳定定位单条记录时返回模块入口。 */
|
|
|
+ _getAttachmentPageUrl(sourceKey, subProjectId, tenderId, file, row, attachmentStageOrder) {
|
|
|
+ const spid = encodeURIComponent(subProjectId);
|
|
|
+ const tid = encodeURIComponent(tenderId);
|
|
|
+ const data = Object.assign({}, row || {}, file || {});
|
|
|
+ const value = key => data[key] === undefined || data[key] === null ? '' : encodeURIComponent(data[key]);
|
|
|
+ const encodedStageOrder = attachmentStageOrder === undefined || attachmentStageOrder === null
|
|
|
+ ? '' : encodeURIComponent(attachmentStageOrder);
|
|
|
+ const contractType = contractConst.typeMap[data.contract_type];
|
|
|
+ const urls = {
|
|
|
+ ledger: `/tender/${tid}/ledger`,
|
|
|
+ stage: encodedStageOrder ? `/tender/${tid}/measure/stage/${encodedStageOrder}` : `/tender/${tid}/measure/stage`,
|
|
|
+ stage_detail: encodedStageOrder ? `/tender/${tid}/measure/stage/${encodedStageOrder}/detail` : `/tender/${tid}/measure/stage`,
|
|
|
+ stage_shoufang: encodedStageOrder ? `/tender/${tid}/measure/stage/${encodedStageOrder}` : `/tender/${tid}/measure/stage`,
|
|
|
+ material: data.s_order ? `/tender/${tid}/measure/material/${value('s_order')}` : `/tender/${tid}/measure/material`,
|
|
|
+ advance: data.vid ? `/tender/${tid}/advance/${value('vid')}/detail` : `/tender/${tid}/advance/start`,
|
|
|
+ payment: data.tr_id && data.td_id ? `/sp/${spid}/payment/${value('tr_id')}/detail/${value('td_id')}` : `/sp/${spid}/payment`,
|
|
|
+ stage_pay: encodedStageOrder ? `/tender/${tid}/measure/stage/${encodedStageOrder}/pay` : `/tender/${tid}/measure/stage`,
|
|
|
+ phase_pay: `/tender/${tid}/pay`,
|
|
|
+ change: data.cid ? `/tender/${tid}/change/${value('cid')}/information` : `/tender/${tid}/change`,
|
|
|
+ change_project: data.cpid ? `/tender/${tid}/change/project/${value('cpid')}/information` : `/tender/${tid}/change/project`,
|
|
|
+ change_apply: data.caid ? `/tender/${tid}/change/apply/${value('caid')}/information` : `/tender/${tid}/change/apply`,
|
|
|
+ change_plan: data.cpid ? `/tender/${tid}/change/plan/${value('cpid')}/information` : `/tender/${tid}/change/plan`,
|
|
|
+ contract: `/sp/${spid}/contract/tender/${tid}/detail${contractType ? '/' + contractType : ''}`,
|
|
|
+ contract_supplement: `/sp/${spid}/contract/tender/${tid}/detail${contractType ? '/' + contractType : ''}`,
|
|
|
+ contract_pay: `/sp/${spid}/contract/tender/${tid}/detail${contractType ? '/' + contractType : ''}`,
|
|
|
+ construction: data.log_id ? `/sp/${spid}/construction/${tid}/log/${value('log_id')}` : `/sp/${spid}/construction/${tid}/list`,
|
|
|
+ financial_pay: data.assistantParent && data.assistantParent.id
|
|
|
+ ? `/sp/${spid}/financial/pay/${encodeURIComponent(data.assistantParent.id)}/detail` : `/sp/${spid}/financial/pay/tender`,
|
|
|
+ cost: `/sp/${spid}/cost/tender/${tid}/ledger`,
|
|
|
+ quality: `/sp/${spid}/quality/tender/${tid}/info`,
|
|
|
+ quality_inspection: data.qiid ? `/sp/${spid}/quality/tender/${tid}/inspection/${value('qiid')}/information` : `/sp/${spid}/quality/tender/${tid}/inspection`,
|
|
|
+ safe_inspection: data.qiid ? `/sp/${spid}/safe/tender/${tid}/inspection/${value('qiid')}/information` : `/sp/${spid}/safe/tender/${tid}/inspection`,
|
|
|
+ safe_stage: `/sp/${spid}/safe/tender/${tid}/stage`,
|
|
|
+ };
|
|
|
+ return urls[sourceKey] || `/tender/${tid}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 解析计量类附件所属期;返回null表示关联计量期已不存在,应过滤该附件。 */
|
|
|
+ _getAttachmentStageOrder(sourceKey, row, stageOrderMap, stageOrderSet) {
|
|
|
+ if (sourceKey === 'stage') {
|
|
|
+ return stageOrderSet.has(String(row.sid)) ? row.sid : null;
|
|
|
+ }
|
|
|
+ if (sourceKey === 'stage_detail') {
|
|
|
+ return stageOrderSet.has(String(row.sorder)) ? row.sorder : null;
|
|
|
+ }
|
|
|
+ if (sourceKey === 'stage_shoufang' || sourceKey === 'stage_pay') {
|
|
|
+ return stageOrderMap.has(String(row.sid)) ? stageOrderMap.get(String(row.sid)) : null;
|
|
|
+ }
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 格式化附件时间;计量附件等旧表的in_time使用秒级时间戳。 */
|
|
|
+ _formatAttachmentTime(value) {
|
|
|
+ if (value === undefined || value === null || value === '') return '';
|
|
|
+ const text = String(value).trim();
|
|
|
+ const time = /^\d{9,10}$/.test(text) ? this.ctx.moment.unix(Number(text)) : this.ctx.moment(value);
|
|
|
+ return time.isValid() ? time.format('YYYY-MM-DD HH:mm') : '';
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
return Assistant;
|