|
|
|
|
@ -311,7 +311,7 @@
|
|
|
|
|
<template #export>
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
@click="exportExcel(new Date().getTime().toString())"
|
|
|
|
|
@click="handleExport"
|
|
|
|
|
>导出</Button
|
|
|
|
|
>
|
|
|
|
|
</template>
|
|
|
|
|
@ -373,7 +373,7 @@ import { op } from "@/const/op";
|
|
|
|
|
import { download } from "@/utils/downloadRequest";
|
|
|
|
|
import { getparameter } from "@/api/system/dictionary";
|
|
|
|
|
import { show } from "@/api/system/customForm";
|
|
|
|
|
import * as XLSX from "xlsx";
|
|
|
|
|
import * as XLSX from "xlsx-js-style";
|
|
|
|
|
import { saveAs } from "file-saver";
|
|
|
|
|
import { listdept } from "@/api/system/department"
|
|
|
|
|
import { deepCopy } from '@/utils'
|
|
|
|
|
@ -435,51 +435,271 @@ export default {
|
|
|
|
|
this.dateRangeEnd0 = null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 判断字段是否为数字类型
|
|
|
|
|
isNumericField(field, fieldName) {
|
|
|
|
|
if (!field) return false;
|
|
|
|
|
|
|
|
|
|
// 检查配置中的类型
|
|
|
|
|
if (field.edit_input === 'number' ||
|
|
|
|
|
field.type === 'number' ||
|
|
|
|
|
field.type === 'integer' ||
|
|
|
|
|
field.type === 'decimal' ||
|
|
|
|
|
field.type === 'float') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查字段名是否包含数字相关关键词
|
|
|
|
|
const numericKeywords = ['面积', '值', '金额', '价格', '费用', '成本', '数量', '元', '万元', '平方米', 'm²', '㎡', '单价', '缴税', '租金', '免租期', '天', '底价', '评估'];
|
|
|
|
|
if (fieldName) {
|
|
|
|
|
for (let keyword of numericKeywords) {
|
|
|
|
|
if (fieldName.includes(keyword)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
handleExport() {
|
|
|
|
|
const fileName = '招租管理' + this.$moment().format('YYYY-MM-DD');
|
|
|
|
|
this.exportExcel(fileName);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async exportExcel(sheetName) {
|
|
|
|
|
let filterTableColumns = this.$refs['xyTable']?.tableFormat || []
|
|
|
|
|
const res = await index(
|
|
|
|
|
Object.assign(this.select, { page: 1, page_size: 9999 })
|
|
|
|
|
);
|
|
|
|
|
if (res.data) {
|
|
|
|
|
let headers = filterTableColumns.filter(i => !!i.prop).map((i) => {
|
|
|
|
|
// 基础列(排除标的物列)
|
|
|
|
|
let baseHeaders = filterTableColumns.filter(i => !!i.prop && i.prop !== 'assets').map((i) => {
|
|
|
|
|
// 从 form 中查找字段配置
|
|
|
|
|
const fieldConfig = this.form.find(f => f.field === i.prop);
|
|
|
|
|
return {
|
|
|
|
|
key: i.prop,
|
|
|
|
|
title: i.label,
|
|
|
|
|
isNumber: this.isNumericField(fieldConfig, i.label)
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 添加提交人、提交单位和创建时间到 headers
|
|
|
|
|
headers.push({
|
|
|
|
|
baseHeaders.push({
|
|
|
|
|
key: 'admin.name',
|
|
|
|
|
title: '提交人'
|
|
|
|
|
title: '提交人',
|
|
|
|
|
isNumber: false
|
|
|
|
|
});
|
|
|
|
|
headers.push({
|
|
|
|
|
baseHeaders.push({
|
|
|
|
|
key: 'department.name',
|
|
|
|
|
title: '提交单位'
|
|
|
|
|
title: '提交单位',
|
|
|
|
|
isNumber: false
|
|
|
|
|
});
|
|
|
|
|
headers.push({
|
|
|
|
|
baseHeaders.push({
|
|
|
|
|
key: 'created_at',
|
|
|
|
|
title: '创建时间'
|
|
|
|
|
title: '创建时间',
|
|
|
|
|
isNumber: false
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const data = res.data.map((row) =>
|
|
|
|
|
headers.map((header) => {
|
|
|
|
|
// 标的物相关列
|
|
|
|
|
const assetHeaders = [
|
|
|
|
|
{ key: 'asset_name', title: '资产名称', isNumber: false },
|
|
|
|
|
{ key: 'asset_type', title: '分类', isNumber: false },
|
|
|
|
|
{ key: 'asset_zuoluo', title: '坐落', isNumber: false },
|
|
|
|
|
{ key: 'asset_xiangxiweizhi', title: '详细位置', isNumber: false },
|
|
|
|
|
{ key: 'asset_shijimianji', title: '实际面积(m²)', isNumber: true },
|
|
|
|
|
{ key: 'asset_chuzumianji', title: '出租面积(m²)', isNumber: true }
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// 完整的 headers(基础列 + 标的物列)
|
|
|
|
|
const headers = [...baseHeaders, ...assetHeaders];
|
|
|
|
|
|
|
|
|
|
// 构建数据行
|
|
|
|
|
const data = [];
|
|
|
|
|
const merges = []; // 用于存储合并单元格信息
|
|
|
|
|
|
|
|
|
|
res.data.forEach((row, rowIndex) => {
|
|
|
|
|
const assets = row.id_lets_to_assets_let_id_relation || [];
|
|
|
|
|
const assetCount = Math.max(1, assets.length); // 至少1行(主数据行或标的物行)
|
|
|
|
|
|
|
|
|
|
// 记录当前记录在主数据中的起始行(不包括标题行,从0开始)
|
|
|
|
|
const startRowIndex = data.length;
|
|
|
|
|
|
|
|
|
|
// 构建主数据行
|
|
|
|
|
const mainRow = baseHeaders.map((header) => {
|
|
|
|
|
let value;
|
|
|
|
|
// 处理提交人、提交单位和创建时间字段
|
|
|
|
|
if (header.key === 'admin.name') {
|
|
|
|
|
return row['admin']?.name || '';
|
|
|
|
|
value = row['admin']?.name || '';
|
|
|
|
|
} else if (header.key === 'department.name') {
|
|
|
|
|
value = row['department']?.name || '';
|
|
|
|
|
} else if (header.key === 'created_at') {
|
|
|
|
|
value = row['created_at'] ? this.$moment(row['created_at']).format('YYYY-MM-DD HH:mm:ss') : '';
|
|
|
|
|
} else {
|
|
|
|
|
value = row[header.key];
|
|
|
|
|
}
|
|
|
|
|
if (header.key === 'department.name') {
|
|
|
|
|
return row['department']?.name || '';
|
|
|
|
|
|
|
|
|
|
// 如果是数字类型字段,确保值是数字类型
|
|
|
|
|
if (header.isNumber && value !== null && value !== undefined && value !== '') {
|
|
|
|
|
let numValue;
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
|
const cleanedValue = value.toString().replace(/[元万元m²㎡\s,,/天]/g, '').trim();
|
|
|
|
|
numValue = Number(cleanedValue);
|
|
|
|
|
} else {
|
|
|
|
|
numValue = Number(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isNaN(numValue) && numValue !== '') {
|
|
|
|
|
return numValue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (header.key === 'created_at') {
|
|
|
|
|
return row['created_at'] ? this.$moment(row['created_at']).format('YYYY-MM-DD HH:mm:ss') : '';
|
|
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 辅助函数:获取标的物数据
|
|
|
|
|
const getAssetData = (asset) => {
|
|
|
|
|
// 判断资产类型
|
|
|
|
|
let assetType = '资产';
|
|
|
|
|
if (asset.house_id) {
|
|
|
|
|
assetType = '房产';
|
|
|
|
|
} else if (asset.land_id) {
|
|
|
|
|
assetType = '土地';
|
|
|
|
|
} else if (asset.biaodileixing) {
|
|
|
|
|
assetType = asset.biaodileixing;
|
|
|
|
|
}
|
|
|
|
|
return row[header.key];
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 标的物数据
|
|
|
|
|
return [
|
|
|
|
|
asset.name || '',
|
|
|
|
|
assetType,
|
|
|
|
|
asset.zuoluo || '',
|
|
|
|
|
asset.xiangxiweizhi || '',
|
|
|
|
|
asset.shijimianji || '0',
|
|
|
|
|
asset.chuzumianji || '0'
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 主数据行的标的物列:如果有标的物,显示第一个标的物;否则为空
|
|
|
|
|
let firstAssetData = assets.length > 0 ? getAssetData(assets[0]) : assetHeaders.map(() => '');
|
|
|
|
|
const mainRowWithAssets = [...mainRow, ...firstAssetData];
|
|
|
|
|
data.push(mainRowWithAssets);
|
|
|
|
|
|
|
|
|
|
// 添加剩余的标的物行(从第二个开始)
|
|
|
|
|
if (assets.length > 1) {
|
|
|
|
|
for (let i = 1; i < assets.length; i++) {
|
|
|
|
|
const assetRow = baseHeaders.map(() => ''); // 基础列为空
|
|
|
|
|
const assetData = getAssetData(assets[i]);
|
|
|
|
|
data.push([...assetRow, ...assetData]);
|
|
|
|
|
}
|
|
|
|
|
} else if (assets.length === 0) {
|
|
|
|
|
// 如果没有标的物,添加一行空的标的物行
|
|
|
|
|
const emptyAssetRow = baseHeaders.map(() => '');
|
|
|
|
|
const emptyAssetData = assetHeaders.map(() => '');
|
|
|
|
|
data.push([...emptyAssetRow, ...emptyAssetData]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果有多行(主数据行 + 标的物行),需要合并主数据列
|
|
|
|
|
if (assetCount > 1) {
|
|
|
|
|
const endRowIndex = data.length - 1; // 结束行索引(不包括标题行)
|
|
|
|
|
baseHeaders.forEach((header, colIndex) => {
|
|
|
|
|
merges.push({
|
|
|
|
|
s: { r: startRowIndex + 1, c: colIndex }, // +1 因为标题行
|
|
|
|
|
e: { r: endRowIndex + 1, c: colIndex } // +1 因为标题行
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 添加标题行
|
|
|
|
|
data.unshift(headers.map((header) => header.title));
|
|
|
|
|
|
|
|
|
|
const wb = XLSX.utils.book_new();
|
|
|
|
|
const ws = XLSX.utils.aoa_to_sheet(data);
|
|
|
|
|
|
|
|
|
|
// 应用合并单元格
|
|
|
|
|
if (merges.length > 0) {
|
|
|
|
|
ws['!merges'] = merges;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 定义样式
|
|
|
|
|
const headerStyle = {
|
|
|
|
|
font: { bold: true, color: { rgb: "FFFFFF" } },
|
|
|
|
|
fill: { fgColor: { rgb: "4472C4" } }, // 蓝色背景
|
|
|
|
|
alignment: { horizontal: "center", vertical: "center" },
|
|
|
|
|
border: {
|
|
|
|
|
top: { style: "thin", color: { rgb: "000000" } },
|
|
|
|
|
bottom: { style: "thin", color: { rgb: "000000" } },
|
|
|
|
|
left: { style: "thin", color: { rgb: "000000" } },
|
|
|
|
|
right: { style: "thin", color: { rgb: "000000" } }
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const cellStyle = {
|
|
|
|
|
alignment: { horizontal: "center", vertical: "center" },
|
|
|
|
|
border: {
|
|
|
|
|
top: { style: "thin", color: { rgb: "000000" } },
|
|
|
|
|
bottom: { style: "thin", color: { rgb: "000000" } },
|
|
|
|
|
left: { style: "thin", color: { rgb: "000000" } },
|
|
|
|
|
right: { style: "thin", color: { rgb: "000000" } }
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 设置所有单元格的样式和数字格式
|
|
|
|
|
const range = XLSX.utils.decode_range(ws['!ref']);
|
|
|
|
|
for (let R = 0; R <= range.e.r; R++) {
|
|
|
|
|
for (let C = 0; C <= range.e.c; C++) {
|
|
|
|
|
const cellAddress = XLSX.utils.encode_cell({ r: R, c: C });
|
|
|
|
|
if (!ws[cellAddress]) {
|
|
|
|
|
// 如果单元格不存在,创建一个空单元格
|
|
|
|
|
ws[cellAddress] = { v: '', t: 's' };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cell = ws[cellAddress];
|
|
|
|
|
const header = headers[C];
|
|
|
|
|
|
|
|
|
|
// 设置样式:标题行使用 headerStyle,其他行使用 cellStyle
|
|
|
|
|
if (R === 0) {
|
|
|
|
|
// 标题行
|
|
|
|
|
cell.s = JSON.parse(JSON.stringify(headerStyle));
|
|
|
|
|
} else {
|
|
|
|
|
// 数据行
|
|
|
|
|
cell.s = JSON.parse(JSON.stringify(cellStyle));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理数字类型字段
|
|
|
|
|
if (header && header.isNumber && R > 0) {
|
|
|
|
|
let numValue;
|
|
|
|
|
|
|
|
|
|
// 处理单元格值,转换为数字
|
|
|
|
|
if (typeof cell.v === 'string') {
|
|
|
|
|
const cleanedValue = cell.v.toString().replace(/[元万元m²㎡\s,,/天]/g, '').trim();
|
|
|
|
|
numValue = Number(cleanedValue);
|
|
|
|
|
} else {
|
|
|
|
|
numValue = Number(cell.v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isNaN(numValue) && cell.v !== null && cell.v !== undefined && cell.v !== '') {
|
|
|
|
|
cell.t = 'n'; // 设置为数字类型
|
|
|
|
|
cell.v = numValue; // 确保值是数字
|
|
|
|
|
// 根据字段名设置合适的格式
|
|
|
|
|
if (header.title && (header.title.includes('面积') || header.title.includes('m²') || header.title.includes('㎡'))) {
|
|
|
|
|
cell.z = '#,##0.00'; // 面积保留2位小数
|
|
|
|
|
} else if (header.title && (header.title.includes('值') || header.title.includes('金额') || header.title.includes('元') || header.title.includes('单价') || header.title.includes('租金') || header.title.includes('底价') || header.title.includes('评估'))) {
|
|
|
|
|
cell.z = '#,##0.00'; // 金额保留2位小数
|
|
|
|
|
} else if (header.title && (header.title.includes('天') || header.title.includes('期'))) {
|
|
|
|
|
cell.z = '#,##0'; // 天数整数格式
|
|
|
|
|
} else {
|
|
|
|
|
cell.z = '#,##0'; // 整数格式
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置列宽(可选,让表格更美观)
|
|
|
|
|
const colWidths = headers.map(() => ({ wch: 15 })); // 默认列宽15
|
|
|
|
|
ws['!cols'] = colWidths;
|
|
|
|
|
|
|
|
|
|
XLSX.utils.book_append_sheet(wb, ws, sheetName);
|
|
|
|
|
const wbout = XLSX.write(wb, {
|
|
|
|
|
bookType: "xlsx",
|
|
|
|
|
@ -732,6 +952,119 @@ export default {
|
|
|
|
|
label: "序号",
|
|
|
|
|
formatter: (row, column, cellValue, index) => (this.$refs['xyTable'].selectOpt.page - 1) * this.$refs['xyTable'].selectOpt.page_size + index + 1
|
|
|
|
|
})
|
|
|
|
|
this.table.unshift({
|
|
|
|
|
label: "标的物",
|
|
|
|
|
prop: 'assets',
|
|
|
|
|
width: 320,
|
|
|
|
|
align: 'left',
|
|
|
|
|
customFn: row => {
|
|
|
|
|
if (!row.id_lets_to_assets_let_id_relation || row.id_lets_to_assets_let_id_relation.length === 0) {
|
|
|
|
|
return <span>-</span>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 判断资产类型:有 house_id 是房产,有 land_id 是土地,都没有则根据 biaodileixing 判断
|
|
|
|
|
const getAssetType = (item) => {
|
|
|
|
|
if (item.house_id) return '房产'
|
|
|
|
|
if (item.land_id) return '土地'
|
|
|
|
|
if (item.biaodileixing) return item.biaodileixing
|
|
|
|
|
return '资产'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let tags = row.id_lets_to_assets_let_id_relation.slice(0, 1).map(i => {
|
|
|
|
|
const assetType = getAssetType(i)
|
|
|
|
|
const assetName = i.name || " "
|
|
|
|
|
const detailLocation = i.xiangxiweizhi || ""
|
|
|
|
|
return (
|
|
|
|
|
<Tag color="success" style="background: rgb(65, 100, 227) !important;">
|
|
|
|
|
【{assetType}】{assetName}{detailLocation ? '-' + detailLocation : ''}
|
|
|
|
|
</Tag>
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
{
|
|
|
|
|
tags
|
|
|
|
|
}
|
|
|
|
|
{row.id_lets_to_assets_let_id_relation.length > 1 && [
|
|
|
|
|
<br key="br"></br>,
|
|
|
|
|
<el-popover key="popover" title="标的物" width={1000}>
|
|
|
|
|
<el-link slot="reference"
|
|
|
|
|
type="primary"
|
|
|
|
|
style={{
|
|
|
|
|
"font-size": "13px",
|
|
|
|
|
"word-break": "keep-all",
|
|
|
|
|
display: "inline",
|
|
|
|
|
}}>更多</el-link>
|
|
|
|
|
<div slot="default">
|
|
|
|
|
<Table size="small"
|
|
|
|
|
data={row.id_lets_to_assets_let_id_relation}
|
|
|
|
|
columns={[
|
|
|
|
|
{
|
|
|
|
|
title: '资产名称',
|
|
|
|
|
key: 'name',
|
|
|
|
|
width: 180,
|
|
|
|
|
render: (h, { row: myRow }) => {
|
|
|
|
|
return h('span', myRow.name || " ")
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '分类',
|
|
|
|
|
key: 'type',
|
|
|
|
|
width: 100,
|
|
|
|
|
render: (h, { row: myRow }) => {
|
|
|
|
|
let type = '资产'
|
|
|
|
|
if (myRow.house_id) {
|
|
|
|
|
type = '房产'
|
|
|
|
|
} else if (myRow.land_id) {
|
|
|
|
|
type = '土地'
|
|
|
|
|
} else if (myRow.biaodileixing) {
|
|
|
|
|
type = myRow.biaodileixing
|
|
|
|
|
}
|
|
|
|
|
return h('span', type)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '坐落',
|
|
|
|
|
key: 'zuoluo',
|
|
|
|
|
minWidth: 200,
|
|
|
|
|
render: (h, { row: myRow }) => {
|
|
|
|
|
return h('span', myRow.zuoluo || " ")
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '详细位置',
|
|
|
|
|
key: 'xiangxiweizhi',
|
|
|
|
|
minWidth: 200,
|
|
|
|
|
render: (h, { row: myRow }) => {
|
|
|
|
|
return h('span', myRow.xiangxiweizhi || " ")
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '实际面积(m²)',
|
|
|
|
|
key: 'shijimianji',
|
|
|
|
|
width: 140,
|
|
|
|
|
render: (h, { row: myRow }) => {
|
|
|
|
|
return h('span', myRow.shijimianji || "0")
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '出租面积(m²)',
|
|
|
|
|
key: 'chuzumianji',
|
|
|
|
|
width: 140,
|
|
|
|
|
render: (h, { row: myRow }) => {
|
|
|
|
|
return h('span', myRow.chuzumianji || "0")
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
]}>
|
|
|
|
|
</Table>
|
|
|
|
|
</div>
|
|
|
|
|
</el-popover>
|
|
|
|
|
]}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
this.table.push({
|
|
|
|
|
prop: 'admin.name',
|
|
|
|
|
width: 120,
|
|
|
|
|
|