diff --git a/dist.zip b/dist.zip index 972d43b..2650e08 100644 Binary files a/dist.zip and b/dist.zip differ diff --git a/src/api/system/baseForm.js b/src/api/system/baseForm.js index 58a26fa..51fa810 100644 --- a/src/api/system/baseForm.js +++ b/src/api/system/baseForm.js @@ -11,11 +11,11 @@ function customParamsSerializer(params) { }); } else { // 其他数组(如 filter),假设是对象数组 - params[key].forEach((item,index) => { + params[key].forEach((item,index) => { if (item && typeof item === 'object' && item.key !== undefined) { - result += `${key}[${index}][key]=${item.key}&${key}[${index}][op]=${item.op}&${key}[${index}][value]=${item.value}&`; + result += `${key}[${index}][key]=${item.key}&${key}[${index}][op]=${item.op}&${key}[${index}][value]=${item.value}&`; } - }); + }); } } else { result += `${key}=${params[key]}&`; diff --git a/src/utils/createDialog.js b/src/utils/createDialog.js index 6bdd4c2..b99aa7b 100644 --- a/src/utils/createDialog.js +++ b/src/utils/createDialog.js @@ -98,20 +98,20 @@ export class CreateDialog { if (info.edit_input === "checkbox" || info.edit_input === "radio") { return info._params && info._params instanceof Array ? info._params.map((i) => - h("el-option", { - props: { - label: - i.name || - i.mingcheng || - i.label || - i.key || - i.value || - i.id || - i.no, - value: i.id || i.value, - }, - }) - ) + h("el-option", { + props: { + label: + i.name || + i.mingcheng || + i.label || + i.key || + i.value || + i.id || + i.no, + value: i.id || i.value, + }, + }) + ) : []; } if (info.edit_input === "file" || info.edit_input === "files") { @@ -227,14 +227,28 @@ export class CreateDialog { e ) => { if (i.field) { + // 如果是金额字段,先进行数字过滤 + let finalValue = e; + const amountKeywords = ['元']; + const isAmountField = amountKeywords.some(keyword => i.name && i.name.includes(keyword)); + if (isAmountField && (i.edit_input === "text" || i.edit_input === "textarea")) { + // 只允许数字和小数点 + const numericValue = String(e).replace(/[^\d.]/g, ''); + // 确保只有一个小数点 + const parts = numericValue.split('.'); + finalValue = parts.length > 2 + ? parts[0] + '.' + parts.slice(1).join('') + : numericValue; + } + if (this.options.fromData) { - that[this.options.fromData][i.field] = e; + that[this.options.fromData][i.field] = finalValue; that[this.options.fromData] = Object.assign( {}, that[this.options.fromData] ); } else { - that.form[i.field] = e; + that.form[i.field] = finalValue; that.form = Object.assign( {}, that.form @@ -245,7 +259,7 @@ export class CreateDialog { }, scopedSlots: i.edit_input === "file" || - i.edit_input === "files" + i.edit_input === "files" ? { file: (scope) => { let { file } = scope; @@ -255,95 +269,95 @@ export class CreateDialog { display: 'flex', 'align-items': 'center' } - },[h("div", { + }, [h("div", { style: { 'margin-right': "auto" } }, [ - h("i", { + h("i", { + class: { + "el-icon-circle-check": + file.status === + "success", + "el-icon-loading": + file.status === + "uploading", + }, + style: { + color: + file.status === + "success" + ? "green" + : "", + }, + }), + h( + "a", + { + attrs: { + href: file.url, + download: file.name, + target: "_blank", + }, class: { - "el-icon-circle-check": + "uploaded-a": file.status === "success", - "el-icon-loading": - file.status === - "uploading", }, style: { - color: - file.status === - "success" - ? "green" - : "", - }, - }), - h( - "a", - { - attrs: { - href: file.url, - download: file.name, - target: "_blank", - }, - class: { - "uploaded-a": - file.status === - "success", - }, - style: { - padding: "0 4px", - color: file.status === + padding: "0 4px", + color: file.status === "success" - ? "green" - : "", - }, + ? "green" + : "", }, - file.original_name || file.name - ), - ]), - h("div",{ + }, + file.original_name || file.name + ), + ]), + h("div", { + style: { + "margin-right": "32px", + 'display': file.status === 'success' ? 'block' : 'none' + } + }, [ + h("i", { + class: "el-icon-view", style: { - "margin-right": "32px", - 'display': file.status === 'success' ? 'block' : 'none' - } - },[ - h("i",{ - class: "el-icon-view", - style: { - 'cursor': 'pointer', - 'margin-right': '12px' - }, - on: { - ['click']: _ => { - that.$bus.$emit('online-file', file.url) - } - } - }), - h("i",{ - class: "el-icon-download", - style: { - 'cursor': 'pointer' - }, - on: { - ['click']: _ => { - download(file.url) - } + 'cursor': 'pointer', + 'margin-right': '12px' + }, + on: { + ['click']: _ => { + that.$bus.$emit('online-file', file.url) } - }) - ]), + } + }), h("i", { - class: "el-icon-close", + class: "el-icon-download", style: { - "display": this.options.disabled ? "none" : "inline" + 'cursor': 'pointer' }, on: { - ["click"]: () => - this.fileRemoveHandler( - file, - i.field - ), - }, - })]) + ['click']: _ => { + download(file.url) + } + } + }) + ]), + h("i", { + class: "el-icon-close", + style: { + "display": this.options.disabled ? "none" : "inline" + }, + on: { + ["click"]: () => + this.fileRemoveHandler( + file, + i.field + ), + }, + })]) ]; }, } @@ -361,7 +375,7 @@ export class CreateDialog { let info = that.formInfo.find((i) => i.field === replace.key); if (!info) { if (replace.label) { - dom.splice(replace.sort??dom.length,0, + dom.splice(replace.sort ?? dom.length, 0, h( "el-form-item", { @@ -379,7 +393,7 @@ export class CreateDialog { ) ) } else { - dom.splice(replace.sort??dom.length,0,replace.render) + dom.splice(replace.sort ?? dom.length, 0, replace.render) } } }); @@ -396,8 +410,8 @@ export class CreateDialog { title: that.title ? that.title : that.type === "add" - ? "新增" - : "编辑", + ? "新增" + : "编辑", visible: that.dialogVisible, width: this.options?.width ? this.options.width : "800px", "close-on-click-modal": false, @@ -466,9 +480,9 @@ export class CreateDialog { ), ]), ] - ) : h('div',[ + ) : h('div', [ formRender, - h('div', { style: "display: flex;justify-content: center;" },[ + h('div', { style: "display: flex;justify-content: center;" }, [ h( "el-button", { diff --git a/src/views/assets/handle.vue b/src/views/assets/handle.vue index a23976a..ac0deda 100644 --- a/src/views/assets/handle.vue +++ b/src/views/assets/handle.vue @@ -288,7 +288,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" @@ -344,118 +344,306 @@ export default { }; }, + // 判断字段是否为数字类型 + 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; + }, + 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 headers = 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({ key: 'admin.name', - title: '提交人' + title: '提交人', + isNumber: false }); headers.push({ key: 'department.name', - title: '提交单位' + title: '提交单位', + isNumber: false }); headers.push({ key: 'created_at', - title: '创建时间' + title: '创建时间', + isNumber: false }); + // 资产标的相关列 + const assetHeaders = [ + { title: '资产名称', isNumber: false }, + { title: '分类', isNumber: false }, + { title: '坐落', isNumber: false }, + { title: '详细位置', isNumber: false }, + { title: '实际面积(m²)', isNumber: true }, + { title: '本次调整面积(m²)', isNumber: true }, + { title: '调整后面积(m²)', isNumber: true } + ]; + let data = [] let merges = [] let rowIndex = 0 res.data.forEach((row, rindex) => { let myRow = headers.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 { + const i = this.form.find(i => i.field === header.key) + if (i && i._relations) { + let { link_relation, foreign_key, link_with_name } = i._relations; + if (link_relation === "newHasOne" || link_relation === "hasOne") { + value = row[link_with_name]?.original_name || row[link_with_name]?.name || + row[link_with_name]?.no || + row[link_with_name]?.value + } else if (link_relation === "hasMany" || link_relation === "newHasMany") { + value = row[link_with_name]?.map((o) => ( + o?.name || + o?.no || + o?.value || + o?.biaoti || + o?.mingcheng + )).toString() + } else { + value = row[header.key] + } + } else { + if (this.table.find(i => i.prop === header.key)?.formatter) { + value = this.table.find(i => i.prop === header.key).formatter(row, {}, row[header.key], rindex) + } 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 = (myAssetLink) => { + let myAsset = {} + if (myAssetLink.land_id) { + myAsset = myAssetLink['lands'] || {} + } else if (myAssetLink.house_id) { + myAsset = myAssetLink['house'] || {} } - const i = this.form.find(i => i.field === header.key) - if (i && i._relations) { - let { link_relation, foreign_key, link_with_name } = i._relations; - if (link_relation === "newHasOne" || link_relation === "hasOne") { - return row[link_with_name]?.original_name || row[link_with_name]?.name || - row[link_with_name]?.no || - row[link_with_name]?.value + // 判断资产类型 + let assetType = '资产'; + if (myAssetLink.house_id) { + assetType = '房产'; + } else if (myAssetLink.land_id) { + assetType = '土地'; + } else if (myAssetLink.biaodileixing) { + assetType = myAssetLink.biaodileixing; + } + + // 处理面积字段,确保是数字类型 + let shijimianji = 0; + if (myAsset.shijimianji !== null && myAsset.shijimianji !== undefined && myAsset.shijimianji !== '') { + if (typeof myAsset.shijimianji === 'string') { + const cleaned = myAsset.shijimianji.toString().replace(/[元万元m²㎡\s,,/天]/g, '').trim(); + shijimianji = Number(cleaned) || 0; + } else { + shijimianji = Number(myAsset.shijimianji) || 0; } - - if (link_relation === "hasMany" || link_relation === "newHasMany") { - return row[link_with_name]?.map((o) => ( - o?.name || - o?.no || - o?.value || - o?.biaoti || - o?.mingcheng - )).toString() + } + + let bencizhengshoumianji = 0; + if (myAssetLink.bencizhengshoumianji !== null && myAssetLink.bencizhengshoumianji !== undefined && myAssetLink.bencizhengshoumianji !== '') { + if (typeof myAssetLink.bencizhengshoumianji === 'string') { + const cleaned = myAssetLink.bencizhengshoumianji.toString().replace(/[元万元m²㎡\s,,/天]/g, '').trim(); + bencizhengshoumianji = Number(cleaned) || 0; + } else { + bencizhengshoumianji = Number(myAssetLink.bencizhengshoumianji) || 0; } } - - if (this.table.find(i => i.prop === header.key)?.formatter) { - return this.table.find(i => i.prop === header.key).formatter(row, {}, row[header.key], rindex) + + let zichanshengyumianji = 0; + if (myAssetLink.zichanshengyumianji !== null && myAssetLink.zichanshengyumianji !== undefined && myAssetLink.zichanshengyumianji !== '') { + if (typeof myAssetLink.zichanshengyumianji === 'string') { + const cleaned = myAssetLink.zichanshengyumianji.toString().replace(/[元万元m²㎡\s,,/天]/g, '').trim(); + zichanshengyumianji = Number(cleaned) || 0; + } else { + zichanshengyumianji = Number(myAssetLink.zichanshengyumianji) || 0; + } } - return row[header.key] - }) - if (row['asset_handles_to_assets'].length > 0) { - for (let j = 0;j 0) { + // 记录当前记录在主数据中的起始行(不包括标题行,从0开始) + const startRowIndex = data.length; + + // 主数据行的资产列:如果有资产,显示第一个资产;否则为空 + let firstAssetData = getAssetData(row['asset_handles_to_assets'][0]); + const mainRowWithAssets = [...myRow, ...firstAssetData]; + data.push(mainRowWithAssets); + + // 添加剩余的资产行(从第二个开始) + if (row['asset_handles_to_assets'].length > 1) { + for (let j = 1; j < row['asset_handles_to_assets'].length; j++) { + const assetRow = headers.map(() => ''); // 基础列为空 + const assetData = getAssetData(row['asset_handles_to_assets'][j]); + data.push([...assetRow, ...assetData]); } - data.push([...myRow,myAsset.name,myAssetLink.bencizhengshoumianji,myAssetLink.zichanshengyumianji]) } - if (headers.length > 0) { - for (let q = 0;q < headers.length;q ++) { + + // 如果有多行(主数据行 + 资产行),需要合并主数据列 + if (row['asset_handles_to_assets'].length > 1) { + const endRowIndex = data.length - 1; // 结束行索引(不包括标题行) + headers.forEach((header, colIndex) => { merges.push({ - s: { - r: rowIndex + 1, - c: q - }, - e: { - r: rowIndex + 1 + row['asset_handles_to_assets'].length - 1, - c: q - } - }) - } + s: { r: startRowIndex + 1, c: colIndex }, // +1 因为标题行 + e: { r: endRowIndex + 1, c: colIndex } // +1 因为标题行 + }); + }); } + rowIndex += row['asset_handles_to_assets'].length } else { + // 如果没有资产,添加一行空的资产行 + const emptyAssetData = assetHeaders.map(() => ''); + data.push([...myRow, ...emptyAssetData]); rowIndex++ - data.push(myRow) } }); - headers.push({ - title: '资产名称' - }) - headers.push({ - title: '本次征收面积' - }) - headers.push({ - title: '资产剩余面积' - }) + + headers.push(...assetHeaders) data.unshift(headers.map((header) => header.title)); const wb = XLSX.utils.book_new(); const ws = XLSX.utils.aoa_to_sheet(data); ws['!merges'] = merges + + // 设置数字类型单元格的格式 + const range = XLSX.utils.decode_range(ws['!ref']); + for (let R = 1; R <= range.e.r; R++) { // 跳过标题行(R=0) + for (let C = 0; C <= range.e.c; C++) { + const cellAddress = XLSX.utils.encode_cell({ r: R, c: C }); + if (!ws[cellAddress]) continue; + const header = headers[C]; + + // 设置样式:标题行使用 headerStyle,其他行使用 cellStyle + if (R === 0) { + // 标题行 + ws[cellAddress].s = { + fill: { fgColor: { rgb: "E6E6FA" } }, + font: { bold: true, sz: 12 }, + alignment: { horizontal: "center", vertical: "center" }, + border: { + top: { style: "thin" }, + bottom: { style: "thin" }, + left: { style: "thin" }, + right: { style: "thin" } + } + }; + } else { + // 数据行 + ws[cellAddress].s = { + alignment: { horizontal: "center", vertical: "center" }, + border: { + top: { style: "thin" }, + bottom: { style: "thin" }, + left: { style: "thin" }, + right: { style: "thin" } + } + }; + } + + // 处理数字类型字段 + if (header && header.isNumber && R > 0) { + const cell = ws[cellAddress]; + 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('评估'))) { + cell.z = '#,##0.00'; // 金额保留2位小数 + } else { + cell.z = '#,##0'; // 整数格式 + } + } + } + } + } + + // 设置列宽 + ws['!cols'] = headers.map(() => ({ wch: 15 })); + XLSX.utils.book_append_sheet(wb, ws, sheetName); const wbout = XLSX.write(wb, { bookType: "xlsx", @@ -667,30 +855,136 @@ export default { this.table.unshift( { label: "资产", prop: 'assets', - width: 300, + width: 320, align: 'left', customFn: row => { - let tags = row.asset_handles_to_assets?.map(i => ( - {(i.land_id ? '【土地】' : '【房产】')+(i.lands?.name || i.house?.name)} - )) + if (!row.asset_handles_to_assets || row.asset_handles_to_assets.length === 0) { + return - + } + + // 判断资产类型:有 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 '资产' + } + + // 获取资产详细信息 + const getAssetInfo = (item) => { + let asset = {} + if (item.land_id) { + asset = item.lands || {} + } else if (item.house_id) { + asset = item.house || {} + } + return asset + } + + let tags = row.asset_handles_to_assets.slice(0, 1).map(i => { + const assetType = getAssetType(i) + const asset = getAssetInfo(i) + const assetName = asset.name || i.name || " " + const detailLocation = asset.xiangxiweizhi || i.xiangxiweizhi || "" + return ( + + 【{assetType}】{assetName}{detailLocation ? '-' + detailLocation : ''} + + ) + }) + return (
{ - tags.slice(0, 1) + tags } - - 1 ? "inline" : "none", - }}>更多 -
- { - tags - } -
-
+ {row.asset_handles_to_assets.length > 1 && [ +

, + + 更多 +
+ { + const asset = getAssetInfo(myRow) + return h('span', asset.name || 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 }) => { + const asset = getAssetInfo(myRow) + return h('span', asset.zuoluo || myRow.zuoluo || " ") + } + }, + { + title: '详细位置', + key: 'xiangxiweizhi', + minWidth: 200, + render: (h, { row: myRow }) => { + const asset = getAssetInfo(myRow) + return h('span', asset.xiangxiweizhi || myRow.xiangxiweizhi || " ") + } + }, + { + title: '实际面积(m²)', + key: 'shijimianji', + width: 140, + render: (h, { row: myRow }) => { + const asset = getAssetInfo(myRow) + return h('span', asset.shijimianji || myRow.shijimianji || "0") + } + }, + { + title: '本次调整面积(m²)', + key: 'bencizhengshoumianji', + width: 140, + render: (h, { row: myRow }) => { + return h('span', myRow.bencizhengshoumianji || "0") + } + }, + { + title: '调整后面积(m²)', + key: 'zichanshengyumianji', + width: 140, + render: (h, { row: myRow }) => { + return h('span', myRow.zichanshengyumianji || "0") + } + }, + ]}> +
+
+
+ ]}
) } diff --git a/src/views/assets/house.vue b/src/views/assets/house.vue index 50f562f..f4ace0f 100644 --- a/src/views/assets/house.vue +++ b/src/views/assets/house.vue @@ -553,7 +553,7 @@ export default { return false; }, - + async exportExcel(sheetName) { let filterTableColumns = this.$refs['xyTable']?.tableFormat || [] const res = await index( @@ -596,39 +596,39 @@ export default { } else if (header.key === 'created_at') { value = row['created_at'] ? this.$moment(row['created_at']).format('YYYY-MM-DD HH:mm:ss') : ''; } else { - const i = this.form.find(i => i.field === header.key) - //if (i.edit_input === 'file' || i.edit_input === 'files') return '' - // if ( - // i.select_item && - // typeof i.select_item === "object" && - // !(i.select_item instanceof Array) - // ) { - // let keys = Object.keys(i.select_item); - // let paramMap = new Map(); - // keys.forEach((key) => { - // paramMap.set(i.select_item[key], key); - // }); - // return paramMap.get(row[i.field]?.toString()); - // } + const i = this.form.find(i => i.field === header.key) + //if (i.edit_input === 'file' || i.edit_input === 'files') return '' + // if ( + // i.select_item && + // typeof i.select_item === "object" && + // !(i.select_item instanceof Array) + // ) { + // let keys = Object.keys(i.select_item); + // let paramMap = new Map(); + // keys.forEach((key) => { + // paramMap.set(i.select_item[key], key); + // }); + // return paramMap.get(row[i.field]?.toString()); + // } if (i && i._relations) { - let { link_relation, foreign_key, link_with_name } = i._relations; - if (link_relation === "newHasOne" || link_relation === "hasOne") { + let { link_relation, foreign_key, link_with_name } = i._relations; + if (link_relation === "newHasOne" || link_relation === "hasOne") { value = row[link_with_name]?.original_name || row[link_with_name]?.name || - row[link_with_name]?.no || - row[link_with_name]?.value + row[link_with_name]?.no || + row[link_with_name]?.value } else if (link_relation === "hasMany" || link_relation === "newHasMany") { value = row[link_with_name]?.map((o) => ( - o?.name || - o?.no || - o?.value || - o?.biaoti || - o?.mingcheng - )).toString() + o?.name || + o?.no || + o?.value || + o?.biaoti || + o?.mingcheng + )).toString() } else { value = row[header.key] - } + } } else { - if (this.table.find(i => i.prop === header.key)?.formatter) { + if (this.table.find(i => i.prop === header.key)?.formatter) { value = this.table.find(i => i.prop === header.key).formatter(row, {}, row[header.key]) } else { value = row[header.key] diff --git a/src/views/assets/land.vue b/src/views/assets/land.vue index db7c3ef..2d8d96a 100644 --- a/src/views/assets/land.vue +++ b/src/views/assets/land.vue @@ -532,7 +532,7 @@ export default { return false; }, - + async exportExcel(sheetName) { let filterTableColumns = this.$refs['xyTable']?.tableFormat || [] const res = await index( @@ -575,39 +575,39 @@ export default { } else if (header.key === 'created_at') { value = row['created_at'] ? this.$moment(row['created_at']).format('YYYY-MM-DD HH:mm:ss') : ''; } else { - const i = this.form.find(i => i.field === header.key) - //if (i.edit_input === 'file' || i.edit_input === 'files') return '' - // if ( - // i.select_item && - // typeof i.select_item === "object" && - // !(i.select_item instanceof Array) - // ) { - // let keys = Object.keys(i.select_item); - // let paramMap = new Map(); - // keys.forEach((key) => { - // paramMap.set(i.select_item[key], key); - // }); - // return paramMap.get(row[i.field]?.toString()); - // } + const i = this.form.find(i => i.field === header.key) + //if (i.edit_input === 'file' || i.edit_input === 'files') return '' + // if ( + // i.select_item && + // typeof i.select_item === "object" && + // !(i.select_item instanceof Array) + // ) { + // let keys = Object.keys(i.select_item); + // let paramMap = new Map(); + // keys.forEach((key) => { + // paramMap.set(i.select_item[key], key); + // }); + // return paramMap.get(row[i.field]?.toString()); + // } if (i && i._relations) { - let { link_relation, foreign_key, link_with_name } = i._relations; - if (link_relation === "newHasOne" || link_relation === "hasOne") { + let { link_relation, foreign_key, link_with_name } = i._relations; + if (link_relation === "newHasOne" || link_relation === "hasOne") { value = row[link_with_name]?.original_name || row[link_with_name]?.name || - row[link_with_name]?.no || - row[link_with_name]?.value + row[link_with_name]?.no || + row[link_with_name]?.value } else if (link_relation === "hasMany" || link_relation === "newHasMany") { value = row[link_with_name]?.map((o) => ( - o?.name || - o?.no || - o?.value || - o?.biaoti || - o?.mingcheng - )).toString() + o?.name || + o?.no || + o?.value || + o?.biaoti || + o?.mingcheng + )).toString() } else { value = row[header.key] - } + } } else { - if (this.table.find(i => i.prop === header.key)?.formatter) { + if (this.table.find(i => i.prop === header.key)?.formatter) { value = this.table.find(i => i.prop === header.key).formatter(row, {}, row[header.key]) } else { value = row[header.key] diff --git a/src/views/lease/index.vue b/src/views/lease/index.vue index 8bea287..7da28b3 100644 --- a/src/views/lease/index.vue +++ b/src/views/lease/index.vue @@ -972,7 +972,7 @@ export default { tags.slice(0, 1) }

- + 0 ? "inline" : "none", }}>更多 -
- { - let asset = myRow.house_id ? row.houses.find(j => j.id === myRow.house_id) : row.land.find(j => j.id === myRow.land_id) - return h('span', (asset?.name || myRow.name || " ")) - } - }, - { - title: '坐落', - key: 'zuoluo', - minWidth: 200, - render: (h, { row: myRow }) => { - let asset = myRow.house_id ? row.houses.find(j => j.id === myRow.house_id) : row.land.find(j => j.id === myRow.land_id) - return h('span', (asset?.zuoluo || myRow.zuoluo || " ")) - } - }, - { - title: '详细位置', - key: 'xiangxiweizhi', - minWidth: 200 - }, - { - title: '出租面积(m²)', - key: 'chuzumianji', - width: 180, - render: (h, { row: myRow }) => { - let asset = myRow.house_id ? row.houses.find(j => j.id === myRow.house_id) : row.land.find(j => j.id === myRow.land_id) - return h('span', (asset?.chuzumianji || myRow.chuzumianji || " ")) - } - }, - ]}> -
-
+
+ { + let asset = myRow.house_id ? row.houses.find(j => j.id === myRow.house_id) : row.land.find(j => j.id === myRow.land_id) + return h('span', (asset?.name || 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 }) => { + let asset = myRow.house_id ? row.houses.find(j => j.id === myRow.house_id) : row.land.find(j => j.id === myRow.land_id) + return h('span', (asset?.zuoluo || 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 }) => { + let asset = myRow.house_id ? row.houses.find(j => j.id === myRow.house_id) : row.land.find(j => j.id === myRow.land_id) + return h('span', (asset?.shijimianji || myRow.shijimianji || "0")) + } + }, + { + title: '出租面积(m²)', + key: 'chuzumianji', + width: 140, + render: (h, { row: myRow }) => { + let asset = myRow.house_id ? row.houses.find(j => j.id === myRow.house_id) : row.land.find(j => j.id === myRow.land_id) + return h('span', (asset?.chuzumianji || myRow.chuzumianji || "0")) + } + }, + ]}> +
+
) diff --git a/src/views/lease/let.vue b/src/views/lease/let.vue index 71278e1..2e942af 100644 --- a/src/views/lease/let.vue +++ b/src/views/lease/let.vue @@ -465,7 +465,7 @@ export default { const fileName = '招租管理' + this.$moment().format('YYYY-MM-DD'); this.exportExcel(fileName); }, - + async exportExcel(sheetName) { let filterTableColumns = this.$refs['xyTable']?.tableFormat || [] const res = await index( diff --git a/src/views/lease/plan.vue b/src/views/lease/plan.vue index b9f0952..2e1f063 100644 --- a/src/views/lease/plan.vue +++ b/src/views/lease/plan.vue @@ -311,7 +311,7 @@ @@ -380,7 +380,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" @@ -448,23 +448,304 @@ export default { this.dateRangeEnd0 = null; }, + handleExport() { + const fileName = '租赁计划' + this.$moment().format('YYYY-MM-DD'); + this.exportExcel(fileName); + }, + + // 判断字段是否为数字类型 + 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; + }, + 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 = this.form.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.field, - title: i.name, + key: i.prop, + title: i.label, + isNumber: this.isNumericField(fieldConfig, i.label) }; }); - const data = res.data.map((row) => - headers.map((header) => row[header.key]) - ); + + // 添加提交人、提交单位和创建时间到 headers + baseHeaders.push({ + key: 'admin.name', + title: '提交人', + isNumber: false + }); + baseHeaders.push({ + key: 'department.name', + title: '提交单位', + isNumber: false + }); + baseHeaders.push({ + key: 'created_at', + title: '创建时间', + isNumber: false + }); + + // 资产标的相关列 + 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) => { + // 合并 land 和 houses 数组 + const allAssets = [ + ...(row?.land || []).map(i => ({ ...i, assetType: '土地' })), + ...(row?.houses || []).map(i => ({ ...i, assetType: '房产' })) + ]; + const assetCount = Math.max(1, allAssets.length); // 至少1行(主数据行或资产标的行) + + // 记录当前记录在主数据中的起始行(不包括标题行,从0开始) + const startRowIndex = data.length; + + // 构建主数据行 + const mainRow = baseHeaders.map((header) => { + let value; + // 处理提交人、提交单位和创建时间字段 + if (header.key === '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 if (header.key === 'chengzufang') { + // 处理承租方字段(来自关联数据) + value = row.lease_id_leases_id_relation?.chengzufang || ''; + } else if (header.key === 'chuzufang') { + // 处理出租方字段(来自关联数据) + value = row.lease_id_leases_id_relation?.chuzufang || ''; + } else { + value = row[header.key]; + } + + // 如果是数字类型字段,确保值是数字类型 + 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; + } + } + + return value; + }); + + // 辅助函数:获取资产标的数据 + const getAssetData = (asset) => { + // 判断资产类型 + let assetType = '资产'; + if (asset.assetType) { + assetType = asset.assetType; + } else if (asset.house_id) { + assetType = '房产'; + } else if (asset.land_id) { + assetType = '土地'; + } else if (asset.biaodileixing) { + assetType = asset.biaodileixing; + } + + // 处理面积字段,确保是数字类型 + let shijimianji = 0; + if (asset.shijimianji !== null && asset.shijimianji !== undefined && asset.shijimianji !== '') { + if (typeof asset.shijimianji === 'string') { + const cleaned = asset.shijimianji.toString().replace(/[元万元m²㎡\s,,/天]/g, '').trim(); + shijimianji = Number(cleaned) || 0; + } else { + shijimianji = Number(asset.shijimianji) || 0; + } + } + + let chuzumianji = 0; + if (asset.chuzumianji !== null && asset.chuzumianji !== undefined && asset.chuzumianji !== '') { + if (typeof asset.chuzumianji === 'string') { + const cleaned = asset.chuzumianji.toString().replace(/[元万元m²㎡\s,,/天]/g, '').trim(); + chuzumianji = Number(cleaned) || 0; + } else { + chuzumianji = Number(asset.chuzumianji) || 0; + } + } + + // 资产标的数据 + return [ + asset.name || '', + assetType, + asset.zuoluo || '', + asset.xiangxiweizhi || '', + shijimianji, + chuzumianji + ]; + }; + + // 主数据行的资产标的列:如果有资产标的,显示第一个资产标的;否则为空 + let firstAssetData = allAssets.length > 0 ? getAssetData(allAssets[0]) : assetHeaders.map(() => ''); + const mainRowWithAssets = [...mainRow, ...firstAssetData]; + data.push(mainRowWithAssets); + + // 添加剩余的资产标的行(从第二个开始) + if (allAssets.length > 1) { + for (let i = 1; i < allAssets.length; i++) { + const assetRow = baseHeaders.map(() => ''); // 基础列为空 + const assetData = getAssetData(allAssets[i]); + data.push([...assetRow, ...assetData]); + } + } else if (allAssets.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('评估') || 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", @@ -722,42 +1003,122 @@ export default { label: "承租方", formatter: (row, column, cellValue) => row.lease_id_leases_id_relation?.chengzufang }, + { + prop: "chuzufang", + width: 120, + label: "出租方", + formatter: (row, column, cellValue) => row.lease_id_leases_id_relation?.chuzufang + }, { label: "资产标的", prop: 'assets', width: 340, align: 'left', customFn: row => { + // 合并 land 和 houses 数组 + const allAssets = [ + ...(row?.land || []).map(i => ({ ...i, assetType: '土地' })), + ...(row?.houses || []).map(i => ({ ...i, assetType: '房产' })) + ]; + + if (!allAssets || allAssets.length === 0) { + return - + } + + // 判断资产类型 + const getAssetType = (item) => { + if (item.assetType) return item.assetType; + if (item.house_id) return '房产'; + if (item.land_id) return '土地'; + if (item.biaodileixing) return item.biaodileixing; + return '资产'; + } + + // 显示第一个资产 + const firstAsset = allAssets[0]; + const assetType = getAssetType(firstAsset); + const assetName = firstAsset.name || " "; + const detailLocation = firstAsset.xiangxiweizhi || ""; + let tags = [ - ...row?.land?.map((i) => - ( - {i.name} - ) - ), - ...row?.houses?.map((i) => - ( - {i.name} - ) - ), - ] + + 【{assetType}】{assetName}{detailLocation ? '-' + detailLocation : ''} + + ]; + return (
{ - tags.slice(0, 1) + tags } - - 1 ? "inline" : "none", - }}>更多 -
- { - tags - } -
-
+ {allAssets.length > 1 && [ +

, + + 更多 +
+ { + return h('span', myRow.name || " ") + } + }, + { + title: '分类', + key: 'type', + width: 100, + render: (h, { row: myRow }) => { + let type = getAssetType(myRow); + 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") + } + }, + ]}> +
+
+
+ ]}
) }