<template> <div class="app-container"> <el-card class="box-card"> <div slot="header" class="clearfix"> <span>资产清单</span> <el-button-group style="float: right;"> <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd">新增资产</el-button> <el-button type="primary" icon="el-icon-upload2" size="mini" @click="handleImport">批量导入</el-button> <el-button type="primary" icon="el-icon-download" size="mini" @click="handleExport">导出</el-button> <el-button type="primary" icon="el-icon-printer" size="mini" @click="handlePrint" :disabled="multiple">打印标签</el-button> </el-button-group> </div> <!-- 搜索条件 --> <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="100px"> <el-form-item label="资产状态" prop="assetStatus"> <el-select size="small" v-model="queryParams.assetStatus" placeholder="请选择资产状态" clearable style="width: 180px"> <el-option v-for="dict in statusOptions" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </el-form-item> <el-form-item label="资产分类" prop="classificationId"> <el-cascader size="small" v-model="queryParams.classificationId" :options="classificationOptions" :props="{ checkStrictly: true, value: 'id', label: 'name', emitPath: false }" clearable style="width: 180px" /> </el-form-item> <el-form-item label="资产名称" prop="assetName"> <el-input size="small" v-model="queryParams.assetName" placeholder="请输入资产名称" clearable style="width: 180px" /> </el-form-item> <el-form-item label="所在位置"> <el-cascader v-model="queryParams.locationId" :options="locationOptions" size="small" :props="{ checkStrictly: true, emitPath: false, expandTrigger: 'hover', value: 'id', label: 'name' }" clearable filterable placeholder="请选择所在位置" style="width: 180px" /> </el-form-item> <el-form-item label="资产编码" prop="assetCode"> <el-input size="small" v-model="queryParams.assetCode" placeholder="请输入资产编码" clearable style="width: 180px" /> </el-form-item> <el-form-item label="设备序列号" prop="serialNumber"> <el-input size="small" v-model="queryParams.serialNumber" placeholder="请输入设备序列号" clearable style="width: 180px" /> </el-form-item> <el-form-item label="管理员" prop="adminUserId"> <el-select v-model="queryParams.adminUserId" placeholder="请选择管理员" clearable size="small" filterable remote :remote-method="remoteSearchAdmin" :loading="adminLoading" style="width: 180px" multiple > <el-option v-for="item in adminOptions" :key="item.id" :label="item.name" :value="item.id" /> </el-select> </el-form-item> <el-form-item label="保养到期" prop="maintenanceExpired"> <el-select v-model="queryParams.maintenanceExpired" size="small" placeholder="请选择" clearable style="width: 180px"> <el-option label="全部" value="" /> <el-option label="是" value="1" /> <el-option label="否" value="0" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> <el-button icon="el-icon-refresh" style="margin-right: 10px;" size="mini" @click="resetQuery">重置</el-button> <el-popover placement="bottom" width="200" trigger="click" v-model="columnFilterVisible"> <el-checkbox-group v-model="selectedColumns"> <el-checkbox label="all" @change="handleCheckAllChange">全部</el-checkbox> <el-divider></el-divider> <el-checkbox v-for="(item, index) in columns" :key="index" :label="item.prop" :disabled="item.disabled">{{ item.label }}</el-checkbox> </el-checkbox-group> <el-button slot="reference" type="primary" size="mini" icon="el-icon-s-operation">列显示</el-button> </el-popover> </el-form-item> </el-form> <!-- 列表展示区域 --> <el-table v-loading="loading" :data="assetList" border @selection-change="handleSelectionChange" style="width: 100%; margin-top: 15px;" > <el-table-column type="selection" width="55" align="center" /> <el-table-column label="资产编码" prop="assetCode" width="120" :show-overflow-tooltip="true"> <template slot-scope="scope"> <el-link type="primary" @click="handleDetail(scope.row)">{{ scope.row.assetCode }}</el-link> </template> </el-table-column> <el-table-column label="资产分类" width="120" v-if="isColumnVisible('classification')" :show-overflow-tooltip="true"> <template slot-scope="scope"> {{ getClassificationName(scope.row.classificationId) }} </template> </el-table-column> <el-table-column label="资产名称" prop="assetName" width="120" v-if="isColumnVisible('assetName')" :show-overflow-tooltip="true" /> <el-table-column label="资产状态" prop="assetStatus" width="100" v-if="isColumnVisible('assetStatus')"> <template slot-scope="scope"> <el-tag :type="getStatusType(scope.row.assetStatus)">{{ scope.row.assetStatus }}</el-tag> </template> </el-table-column> <el-table-column label="品牌" prop="brand" width="100" v-if="isColumnVisible('brand')" :show-overflow-tooltip="true" /> <el-table-column label="型号" prop="model" width="100" v-if="isColumnVisible('model')" :show-overflow-tooltip="true" /> <el-table-column label="设备序列号" prop="serialNumber" width="140" v-if="isColumnVisible('serialNumber')" :show-overflow-tooltip="true" /> <el-table-column label="管理员" prop="administratorName" width="100" v-if="isColumnVisible('administratorName')" :show-overflow-tooltip="true" /> <el-table-column label="所属公司" prop="companyName" width="140" v-if="isColumnVisible('companyName')" :show-overflow-tooltip="true" /> <el-table-column label="所在位置" width="140" v-if="isColumnVisible('location')" :show-overflow-tooltip="true"> <template slot-scope="scope"> {{ getLocationName(scope.row.locationId) }} </template> </el-table-column> <el-table-column label="购置时间" prop="purchaseDate" width="120" v-if="isColumnVisible('purchaseDate')" /> <el-table-column label="购置方式" prop="purchaseType" width="100" v-if="isColumnVisible('purchaseType')"> <template slot-scope="scope"> {{ scope.row.purchaseType === '1' ? '采购' : '租赁' }} </template> </el-table-column> <el-table-column label="购置金额(含税)" prop="purchaseAmount" width="130" v-if="isColumnVisible('purchaseAmount')" /> <el-table-column label="入库时间" prop="storageDate" width="120" v-if="isColumnVisible('storageDate')" /> <el-table-column label="预计使用期限(月)" prop="expectedDepreciationPeriod" width="150" v-if="isColumnVisible('expectedUsePeriod')" /> <el-table-column label="备注" prop="remark" width="150" v-if="isColumnVisible('remark')" :show-overflow-tooltip="true" /> <el-table-column label="资产照片" width="100" v-if="isColumnVisible('image')" align="center"> <template slot-scope="scope"> <el-image v-if="scope.row.imageUrl" :src="scope.row.imageUrl" style="width: 50px; height: 50px" :preview-src-list="[scope.row.imageUrl]"> </el-image> <span v-else>无</span> </template> </el-table-column> <el-table-column label="保养到期时间" prop="maintenanceDueDate" width="130" v-if="isColumnVisible('maintenanceDueDate')" /> <el-table-column label="保养说明" prop="maintenanceDescription" width="150" v-if="isColumnVisible('maintenanceDescription')" :show-overflow-tooltip="true" /> <el-table-column label="预计折旧期限(月)" prop="expectedDepreciationPeriod" width="150" v-if="isColumnVisible('expectedDepreciationPeriod')" /> <el-table-column label="操作" width="150" align="center" fixed="right"> <template slot-scope="scope"> <el-button type="text" size="mini" @click="handleUpdate(scope.row)">修改</el-button> <el-button type="text" size="mini" @click="handleDelete(scope.row)" v-if="scope.row.assetStatus === '0'" style="color: #F56C6C;">删除</el-button> </template> </el-table-column> </el-table> <!-- 分页 --> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryParams.pageNum" :page-sizes="[10, 20, 50, 100]" :page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total" style="margin-top: 15px; text-align: right;"> </el-pagination> </el-card> <!-- 导入对话框 --> <el-dialog :title="importTitle" :visible.sync="upload.open" width="400px" append-to-body> <el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag> <i class="el-icon-upload"></i> <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> <div class="el-upload__tip" slot="tip"> <el-button type="text" @click="downloadImportTemplate">下载模板</el-button> <div>只允许导入xls、xlsx格式文件。</div> </div> </el-upload> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="submitFileForm">确 定</el-button> <el-button @click="upload.open = false">取 消</el-button> </div> </el-dialog> <!-- 查看详情对话框 --> <el-dialog :title="detailTitle" :visible.sync="detailOpen" width="900px" append-to-body> <asset-detail v-if="detailOpen" :asset-id="currentAssetId" /> </el-dialog> <!-- 添加/修改资产对话框 --> <el-dialog :title="title" :visible.sync="open" width="900px" append-to-body> <asset-form v-if="open" :asset-id="currentAssetId" :is-edit="isEdit" @refresh="getList" @close="open = false" /> </el-dialog> <!-- 打印标签对话框 --> <el-dialog title="打印资产标签" :visible.sync="printOpen" width="800px" append-to-body> <asset-label-print v-if="printOpen" :ids="ids" :selected-assets="assetsToPrint" @close="printOpen = false" /> </el-dialog> <!-- 隐藏的打印组件,用于生成打印内容 --> <asset-label-print ref="assetLabelPrint" :ids="ids" :selected-assets="assetsToPrint" style="display: none;" /> </div> </template> <script> import { listAssets, deleteAsset, deleteAssets, listCompanies, listUsers, exportAsset, downloadTemplate, printAssetLabels, getLabelConfig } from '@/api/asset/inventory' import { getClassificationTree } from '@/api/asset/classification' import { getLocationTree } from '@/api/asset/location' import AssetDetail from './components/AssetDetail' import AssetForm from './components/AssetForm' import AssetLabelPrint from './components/AssetLabelPrint' import { API_SUCCESS_CODE } from '@/utils/constants' export default { name: 'AssetInventory', components: { AssetDetail, AssetForm, AssetLabelPrint }, data() { return { printOpen: false, // 遮罩层 loading: false, // 资产清单列表 assetList: [], // 选中数组 ids: [], // 非单个禁用 single: true, // 非多个禁用 multiple: true, // 总条数 total: 0, // 要打印的资产数据 assetsToPrint: [], // 弹出层标题 title: '', // 是否显示弹出层 open: false, // 是否显示详情弹出层 detailOpen: false, // 是否显示导入弹出层 importOpen: false, // 导入标题 importTitle: '导入资产数据', // 详情标题 detailTitle: '资产详情', // 当前处理的资产ID currentAssetId: null, // 是否编辑模式 isEdit: false, // 资产状态选项 statusOptions: [], // 管理员选项 adminOptions: [], // 管理员搜索加载中 adminLoading: false, // 资产分类选项 classificationOptions: [], // 资产位置选项 locationOptions: [], // 公司选项 companyOptions: [], // 查询参数 queryParams: { pageNum: 1, pageSize: 10, assetStatus: '', classificationId: undefined, assetName: '', locationId: undefined, assetCode: '', serialNumber: '', adminUserId: undefined, maintenanceExpired: '' }, // 上传参数 upload: { // 是否显示弹出层 open: false, // 是否禁用上传 isUploading: false, // 设置上传的请求头部 headers: {}, // 上传的地址 url: process.env.VUE_APP_BASE_API + '/asset/import' }, // 列显示设置 columns: [ { prop: 'assetCode', label: '资产编码', visible: true, disabled: true }, { prop: 'tagLink', label: '标签链接', visible: true, disabled: false }, { prop: 'classification', label: '资产分类', visible: true, disabled: false }, { prop: 'assetName', label: '资产名称', visible: true, disabled: false }, { prop: 'assetStatus', label: '资产状态', visible: true, disabled: false }, { prop: 'brand', label: '品牌', visible: true, disabled: false }, { prop: 'model', label: '型号', visible: true, disabled: false }, { prop: 'serialNumber', label: '设备序列号', visible: true, disabled: false }, { prop: 'administratorName', label: '管理员', visible: true, disabled: false }, { prop: 'companyName', label: '所属公司', visible: true, disabled: false }, { prop: 'location', label: '所在位置', visible: true, disabled: false }, { prop: 'purchaseDate', label: '购置时间', visible: true, disabled: false }, { prop: 'purchaseType', label: '购置方式', visible: true, disabled: false }, { prop: 'purchaseAmount', label: '购置金额(含税)', visible: true, disabled: false }, { prop: 'storageDate', label: '入库时间', visible: true, disabled: false }, { prop: 'expectedUsePeriod', label: '预计使用期限(月)', visible: true, disabled: false }, { prop: 'remark', label: '备注', visible: true, disabled: false }, { prop: 'image', label: '资产照片', visible: true, disabled: false }, { prop: 'maintenanceDueDate', label: '保养到期时间', visible: true, disabled: false }, { prop: 'maintenanceDescription', label: '保养说明', visible: true, disabled: false }, { prop: 'expectedDepreciationPeriod', label: '预计折旧期限(月)', visible: true, disabled: false } ], columnFilterVisible: false, selectedColumns: [] } }, created() { this.getList() this.getStatusOptions() this.getClassificationOptions() this.getLocationOptions() // this.getCompanyOptions() // 初始化选中的列 this.selectedColumns = this.columns .filter(col => col.visible) .map(col => col.prop) // 检查是否所有列都被选中,如果是,添加 'all' 选项 const allSelected = this.columns .filter(col => !col.disabled) .every(col => this.selectedColumns.includes(col.prop)) if (allSelected) { this.selectedColumns.push('all') } }, computed: { // 判断列是否可见 isColumnVisible() { return (prop) => this.selectedColumns.includes(prop) }, // 判断是否所有可选列都被选中 isAllSelected() { const selectableColumns = this.columns .filter(col => !col.disabled) .map(col => col.prop) return selectableColumns.every(prop => this.selectedColumns.includes(prop)) } }, watch: { selectedColumns: { handler(newVal) { // 确保必选列始终存在 if (!newVal.includes('assetCode')) { this.selectedColumns.push('assetCode') } // 获取所有可选列(除了必选列) const selectableColumns = this.columns .filter(col => !col.disabled) .map(col => col.prop) // 检查所有可选列是否都被选中 const allSelected = selectableColumns.every(prop => newVal.includes(prop)) // 更新"全部"选项的状态 if (allSelected && !newVal.includes('all')) { this.selectedColumns.push('all') } else if (!allSelected && newVal.includes('all')) { // 从选择中移除"全部"选项 const index = this.selectedColumns.indexOf('all') if (index > -1) { this.selectedColumns.splice(index, 1) } } // 更新 columns 中的 visible 属性 this.columns.forEach(col => { col.visible = this.selectedColumns.includes(col.prop) }) }, deep: true } }, methods: { /** 查询资产清单列表 */ getList() { this.loading = true listAssets(this.queryParams).then(response => { if (response.code === API_SUCCESS_CODE) { this.assetList = response.data.data || [] this.total = response.data.total } this.loading = false }) }, /** 获取资产状态选项 */ getStatusOptions() { this.statusOptions = [ { value: '空闲', label: '空闲' }, { value: '在用', label: '在用' }, { value: '借用', label: '借用' }, { value: '维修中', label: '维修中' }, { value: '报废', label: '报废' } ] }, /** 获取资产分类树形选项 */ getClassificationOptions() { getClassificationTree({ status: '1' }).then(response => { if (response.code === API_SUCCESS_CODE) { this.classificationOptions = this.processClassificationTree(response.data || []) } }) }, /** 获取资产位置树形选项 */ getLocationOptions() { getLocationTree().then(response => { if (response.code === API_SUCCESS_CODE) { this.locationOptions = this.processLocationTree(response.data || []) } }) }, processLocationTree(data) { if (!data || !Array.isArray(data)) return [] const processNode = (node) => { const processedNode = { id: node.id, name: `${node.code || ''}-${node.locationName || node.label || ''}`, children: [] } if (node.children && Array.isArray(node.children) && node.children.length > 0) { processedNode.children = node.children.map(child => processNode(child)) } return processedNode } return data.map(item => processNode(item)) }, processClassificationTree(data) { if (!data || !Array.isArray(data)) return [] const processNode = (node) => { const processedNode = { id: node.id, name: `${node.classificationCode || node.code || ''}-${node.classificationName || node.label || ''}`, children: [] } if (node.children && Array.isArray(node.children) && node.children.length > 0) { processedNode.children = node.children.map(child => processNode(child)) } return processedNode } return data.map(item => processNode(item)) }, /** 获取公司选项 */ getCompanyOptions() { listCompanies().then(response => { if (response.code === API_SUCCESS_CODE) { this.companyOptions = response.data || [] } }) }, /** 远程搜索管理员 */ remoteSearchAdmin(query) { if (query) { this.adminLoading = true listUsers({ name: query }).then(response => { this.adminLoading = false if (response.code === API_SUCCESS_CODE) { this.adminOptions = response.data || [] } }).catch(() => { this.adminLoading = false }) } }, /** 获取资产状态显示文本 */ getStatusText(assetStatus) { const statusMap = { '0': '空闲', '1': '在用', '2': '借用', '3': '维修中', '4': '报废' } return statusMap[assetStatus] || '未知' }, /** 获取资产状态显示类型 */ getStatusType(assetStatus) { const typeMap = { '空闲': 'success', '在用': 'primary', '借用': 'info', '维修中': 'warning', '报废': 'warning', '5': 'warning', '6': 'warning', '7': 'danger' } return typeMap[assetStatus] || 'info' }, /** 搜索按钮操作 */ handleQuery() { this.queryParams.pageNum = 1 this.getList() }, /** 重置按钮操作 */ resetQuery() { // 使用Element UI表单的resetFields方法重置表单 if (this.$refs.queryForm) { this.$refs.queryForm.resetFields() } this.queryParams = { pageNum: 1, pageSize: 10, assetStatus: '', classificationId: undefined, assetName: '', locationId: undefined, assetCode: '', serialNumber: '', adminUserId: undefined, maintenanceExpired: '' } this.handleQuery() }, /** 全选/取消全选处理 */ handleCheckAllChange(checked) { // 获取所有可选列的 prop 值(除了必选列) const selectableColumns = this.columns .filter(col => !col.disabled) .map(col => col.prop) if (checked) { // 如果选中"全部",则选择所有列 this.selectedColumns = ['all', 'assetCode', ...selectableColumns] } else { // 如果取消选中"全部",则只保留必选列 this.selectedColumns = ['assetCode'] } }, /** 多选框选中数据 */ handleSelectionChange(selection) { this.ids = selection.map(item => item.id) this.multiple = !selection.length }, /** 新增按钮操作 */ handleAdd() { this.open = true this.title = '新增资产' this.isEdit = false this.currentAssetId = null }, /** 修改按钮操作 */ handleUpdate(row) { this.open = true this.title = '修改资产' this.isEdit = true this.currentAssetId = row.assetCode }, /** 详情按钮操作 */ handleDetail(row) { this.detailOpen = true this.detailTitle = '资产详情' this.currentAssetId = row.assetCode }, /** 删除按钮操作 */ handleDelete(row) { this.$confirm('是否确认删除资产编码为"' + row.assetCode + '"的数据项?', '警告', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { return deleteAsset(row.assetCode) }).then(response => { if (response.code === API_SUCCESS_CODE) { this.$message.success('删除成功') this.getList() } }).catch(() => {}) }, /** 批量删除按钮操作 */ handleBatchDelete() { const assetCodes = this.selectedRows.map(item => item.assetCode) if (assetCodes.length === 0) { this.$message.warning('请至少选择一条记录') return } this.$confirm('是否确认批量删除选中的' + assetCodes.length + '条数据?', '警告', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { return deleteAssets(assetCodes) }).then(response => { if (response.code === API_SUCCESS_CODE) { this.$message.success('批量删除成功') this.getList() } }).catch(() => {}) }, /** 导出按钮操作 */ handleExport() { const queryParams = { ...this.queryParams } queryParams.pageNum = undefined queryParams.pageSize = undefined this.$confirm('是否确认导出所有资产数据项?', '警告', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.exportLoading = true return exportAsset(queryParams) }).then(response => { this.download(response, '资产数据.xlsx') this.exportLoading = false }).catch(() => { this.exportLoading = false }) }, /** 下载模板操作 */ downloadImportTemplate() { downloadTemplate().then(response => { this.download(response, '资产导入模板.xlsx') }) }, /** 导入按钮操作 */ handleImport() { this.upload.open = true this.importTitle = '导入资产数据' }, /** 处理文件上传中 */ handleFileUploadProgress() { this.upload.isUploading = true }, /** 文件上传成功处理 */ handleFileSuccess(response) { this.upload.isUploading = false this.$refs.upload.clearFiles() this.upload.open = false if (response.code === API_SUCCESS_CODE) { this.$alert(`成功导入${response.data && response.data.successCount ? response.data.successCount : 0}条数据`, { type: 'success' }) this.getList() } else { // 如果是文件流,则下载错误文件 this.$alert(response.msg || '导入失败,正在下载错误文件', '导入结果', { type: 'error' }) this.download(response, `资产导入错误文件_${new Date().getTime()}.xlsx`) } }, /** 提交上传文件 */ submitFileForm() { this.$refs.upload.submit() }, /** 打印标签 */ handlePrint() { if (this.ids.length === 0) { this.$message.warning('请选择要打印的资产') return } // 从选中的列表项中获取相关数据 this.assetsToPrint = this.assetList.filter(item => this.ids.includes(item.id || item.assetId)) // 直接调用浏览器打印功能 this.$nextTick(() => { // const printWindow = window.open('', '_blank') // if (!printWindow) { // this.$message.error('打印窗口被阻止,请允许弹出窗口') // return // } // 获取打印内容 const printContent = this.$refs.assetLabelPrint.getPrintContent(this.assetsToPrint) // 写入打印窗口 printWindow.document.open() printWindow.document.write(printContent) printWindow.document.close() // 等待资源加载完成后打印 printWindow.onload = function() { printWindow.print() // 打印完成后关闭窗口 printWindow.close() } }) }, /** 打印单个资产标签 */ handlePrintSingle(row) { this.ids = [row.id || row.assetId] this.assetsToPrint = [row] this.handlePrint() }, /** 分页大小改变 */ handleSizeChange(val) { this.queryParams.pageSize = val this.getList() }, /** 分页页码改变 */ handleCurrentChange(val) { this.queryParams.pageNum = val this.getList() }, /** 获取分类名称 */ getClassificationName(classificationId) { if (!classificationId) return '未知分类' // 递归查找分类 const findClassification = (items) => { for (const item of items) { if (item.id === classificationId) { return item.name } if (item.children && item.children.length > 0) { const found = findClassification(item.children) if (found) return found } } return null } const name = findClassification(this.classificationOptions) return name || '未知分类' }, /** 获取位置名称 */ getLocationName(locationId) { if (!locationId) return '未知位置' // 递归查找位置 const findLocation = (items) => { for (const item of items) { if (item.id === locationId) { return item.name } if (item.children && item.children.length > 0) { const found = findLocation(item.children) if (found) return found } } return null } const name = findLocation(this.locationOptions) return name || '未知位置' } } } </script> <style lang="scss" scoped> .app-container { .el-table { margin-top: 15px; } .el-form { display: flex; flex-wrap: wrap; align-items: center; margin-bottom: 10px; } .el-checkbox + .el-checkbox { margin-left: 0; } } </style>