资产清单对接,费用类型对接

This commit is contained in:
zengqiyang 2025-04-09 18:33:57 +08:00
parent 7e8237190a
commit 50a376b14b
15 changed files with 2023 additions and 630 deletions

5
package.json Normal file
View File

@ -0,0 +1,5 @@
{
"dependencies": {
"qrcode.vue": "^3.6.0"
}
}

View File

@ -1,4 +1,4 @@
NODE_ENV = 'development'
# 开发环境API地址
VUE_APP_BASE_API = http://192.168.137.3:8080/api
VUE_APP_BASE_API = http://192.168.137.3:8080/api

View File

@ -1,26 +1,26 @@
import request from '@/utils/request'
// 查询资产清单列表
export function listAsset(query) {
// 获取资产列表
export function listAssets(query) {
return request({
url: '/asset/inventory/list',
url: '/asset/list',
method: 'get',
params: query
})
}
// 查询资产详细
export function getAsset(id) {
// 获取资产详情
export function getAsset(assetCode) {
return request({
url: '/asset/inventory/' + id,
method: 'get'
url: `/asset/${assetCode}`,
method: 'get',
})
}
// 新增资产
export function addAsset(data) {
return request({
url: '/asset/inventory',
url: '/asset/create',
method: 'post',
data: data
})
@ -29,71 +29,50 @@ export function addAsset(data) {
// 修改资产
export function updateAsset(data) {
return request({
url: '/asset/inventory',
url: `/asset/${data.assetCode}`,
method: 'put',
data: data
})
}
// 删除资产
export function delAsset(id) {
export function deleteAsset(assetCode) {
return request({
url: '/asset/inventory/' + id,
method: 'delete'
url: '/asset/delete',
method: 'delete',
params: { assetCode }
})
}
// 批量删除资产
export function delAssetBatch(ids) {
export function deleteAssets(assetCodes) {
return request({
url: '/asset/inventory/batch',
url: '/asset/batch/delete',
method: 'delete',
data: { ids }
data: { assetCodes }
})
}
// 导出资产清单
// 导出资产
export function exportAsset(query) {
return request({
url: '/asset/inventory/export',
url: '/asset/export',
method: 'get',
params: query,
responseType: 'blob'
})
}
// 下载导入模板
// 下载资产导入模板
export function downloadTemplate() {
return request({
url: '/asset/inventory/template',
url: '/asset/template/download',
method: 'get',
responseType: 'blob'
})
}
// 导入资产数据
export function importAsset(file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/asset/inventory/import',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
// 获取资产分类树形列表
export function listAssetClassTree() {
return request({
url: '/asset/class/tree',
method: 'get'
})
}
// 获取资产位置树形列表
// 获取资产位置树
export function listAssetLocationTree() {
return request({
url: '/asset/location/tree',
@ -109,7 +88,7 @@ export function listCompanies() {
})
}
// 获取用户列表(用于选择管理员)
// 获取用户列表
export function listUsers(query) {
return request({
url: '/system/user/list',
@ -118,33 +97,41 @@ export function listUsers(query) {
})
}
// 打印资产标签
export function printAssetLabel(ids) {
// 上传资产图片
export function uploadAssetImage(data) {
return request({
url: '/asset/inventory/print',
url: '/asset/upload/image',
method: 'post',
data: { ids }
data: data
})
}
// 获取资产状态列表
export function getAssetStatusOptions() {
/**
* 批量打印资产标签
* @param {Array} assetCodes 资产编码数组
* @returns {Object} 请求结果
*/
export function printAssetLabels(assetCodes) {
return request({
url: '/asset/inventory/status/options',
url: '/asset/print',
method: 'post',
data: assetCodes
})
}
// 检查资产编码是否存在
export function checkAssetCode(code) {
return request({
url: '/asset/check/code',
method: 'get',
params: { code }
})
}
// 获取标签配置
export function getLabelConfig() {
return request({
url: '/asset/label',
method: 'get'
})
}
// 上传资产照片
export function uploadAssetImage(file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/common/upload',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}

93
pc/src/api/finance.js Normal file
View File

@ -0,0 +1,93 @@
import request from '@/utils/request'
// 费用分类管理接口
export function getFeeCategories(query) {
return request({
url: '/finance/category/page',
method: 'get',
params: query
})
}
export function getAllFeeCategories() {
return request({
url: '/finance/category/list',
method: 'get'
})
}
export function getFeeCategory(id) {
return request({
url: `/finance/category/${id}`,
method: 'get'
})
}
export function addFeeCategory(data) {
return request({
url: '/finance/category',
method: 'post',
data
})
}
export function updateFeeCategory(id, data) {
return request({
url: `/finance/category/${id}`,
method: 'put',
data
})
}
export function deleteFeeCategory(id) {
return request({
url: `/finance/category/${id}`,
method: 'delete'
})
}
// 费用类型管理接口
export function getFeeTypes(query) {
return request({
url: '/finance/type/page',
method: 'get',
params: query
})
}
export function getFeeTypesByCategory(categoryId) {
return request({
url: `/finance/type/list/${categoryId}`,
method: 'get'
})
}
export function getFeeType(id) {
return request({
url: `/finance/type/${id}`,
method: 'get'
})
}
export function addFeeType(data) {
return request({
url: '/finance/type',
method: 'post',
data
})
}
export function updateFeeType(id, data) {
return request({
url: `/finance/type/${id}`,
method: 'put',
data
})
}
export function deleteFeeType(id) {
return request({
url: `/finance/type/${id}`,
method: 'delete'
})
}

29
pc/src/api/inventory.js Normal file
View File

@ -0,0 +1,29 @@
import request from '@/utils/request'
/**
* 导入库存
* @param {Object} data 文件数据
* @returns {Object} 请求结果
*/
export function importInventory(data) {
return request({
url: '/inventory/import',
method: 'post',
data: data,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
/**
* 下载库存导入模板
* @returns {Object} 请求结果
*/
export function downloadInventoryTemplate() {
return request({
url: '/inventory/template/download',
method: 'get',
responseType: 'blob'
})
}

View File

@ -11,8 +11,8 @@
>
<template v-for="route in routes">
<el-submenu
v-if="route.children && route.children.length > 1"
:key="route.path"
v-if="route.children && route.children.length > 0"
:key="route.path + '-submenu'"
:index="route.path"
>
<template slot="title">
@ -30,7 +30,7 @@
</el-submenu>
<el-menu-item
v-else-if="!route.hidden"
:key="route.path"
:key="route.path + '-item'"
:index="route.path"
>
<i :class="route.meta && route.meta.icon"></i>

View File

@ -3,6 +3,7 @@ import VueRouter from 'vue-router'
import Layout from '@/layout/index'
import systemRoutes from './modules/system'
import projectRoutes from './modules/project'
import financeRoutes from './modules/finance'
Vue.use(VueRouter)
@ -28,6 +29,7 @@ const routes = [
},
systemRoutes,
projectRoutes,
financeRoutes,
{
path: '/asset',
component: Layout,

View File

@ -0,0 +1,17 @@
import Layout from '@/layout/index'
export default {
path: '/finance',
component: Layout,
name: 'Finance',
meta: { title: '业务财务', icon: 'el-icon-money' },
redirect: '/finance/feeType',
children: [
{
path: 'feeType',
component: () => import('@/views/finance/feeType/index.vue'),
name: 'FeeType',
meta: { title: '费用类型', icon: 'el-icon-tickets' }
}
]
}

View File

@ -33,11 +33,11 @@ service.interceptors.response.use(
// 如果返回的状态码不是000000则判断为错误
if (res.code !== '000000') {
Message({
message: res.msg || '系统错误',
message: res.msg || res.message || '系统错误',
type: 'error',
duration: 5 * 1000
})
return Promise.reject(new Error(res.msg || '系统错误'))
return Promise.reject(new Error(res.msg || res.message || '系统错误'))
} else {
return res
}

View File

@ -3,27 +3,27 @@
<el-tabs v-model="activeTab">
<el-tab-pane label="基础信息" name="basic">
<el-descriptions class="margin-top" title="资产基础信息" :column="3" border>
<el-descriptions-item label="资产编码">{{ assetDetail.code }}</el-descriptions-item>
<el-descriptions-item label="资产分类">{{ assetDetail.classificationName }}</el-descriptions-item>
<el-descriptions-item label="资产名称">{{ assetDetail.name }}</el-descriptions-item>
<el-descriptions-item label="资产编码">{{ assetDetail.assetCode || assetDetail.code }}</el-descriptions-item>
<el-descriptions-item label="资产分类">{{ getClassificationName(assetDetail.classificationId) }}</el-descriptions-item>
<el-descriptions-item label="资产名称">{{ assetDetail.assetName || assetDetail.name }}</el-descriptions-item>
<el-descriptions-item label="资产状态">
<el-tag :type="getStatusType(assetDetail.status)">{{ getStatusText(assetDetail.status) }}</el-tag>
<el-tag :type="getStatusType(assetDetail.assetStatus || assetDetail.status)">{{ getStatusText(assetDetail.assetStatus || assetDetail.status) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="品牌">{{ assetDetail.brand }}</el-descriptions-item>
<el-descriptions-item label="型号">{{ assetDetail.model }}</el-descriptions-item>
<el-descriptions-item label="设备序列号">{{ assetDetail.serialNumber }}</el-descriptions-item>
<el-descriptions-item label="管理员">{{ assetDetail.administratorName }}</el-descriptions-item>
<el-descriptions-item label="所属公司">{{ assetDetail.companyName }}</el-descriptions-item>
<el-descriptions-item label="所在位置">{{ assetDetail.locationName }}</el-descriptions-item>
<el-descriptions-item label="所在位置">{{ getLocationName(assetDetail.locationId) }}</el-descriptions-item>
<el-descriptions-item label="购置方式">
{{ assetDetail.purchaseType === '1' ? '采购' : assetDetail.purchaseType === '2' ? '租赁' : '-' }}
</el-descriptions-item>
<el-descriptions-item label="购置金额(含税)">{{ assetDetail.purchaseAmount }}</el-descriptions-item>
<el-descriptions-item label="购置时间">{{ assetDetail.purchaseTime }}</el-descriptions-item>
<el-descriptions-item label="入库时间">{{ assetDetail.storageTime }}</el-descriptions-item>
<el-descriptions-item label="预计使用期限(月)">{{ assetDetail.expectedUsePeriod }}</el-descriptions-item>
<el-descriptions-item label="购置时间">{{ assetDetail.purchaseDate || assetDetail.purchaseTime }}</el-descriptions-item>
<el-descriptions-item label="入库时间">{{ assetDetail.storageDate || assetDetail.storageTime }}</el-descriptions-item>
<el-descriptions-item label="预计使用期限(月)">{{ assetDetail.expectedUsePeriod || assetDetail.expectedDepreciationPeriod }}</el-descriptions-item>
<el-descriptions-item label="预计折旧期限(月)">{{ assetDetail.expectedDepreciationPeriod }}</el-descriptions-item>
<el-descriptions-item label="保养到期时间">{{ assetDetail.maintenanceTime }}</el-descriptions-item>
<el-descriptions-item label="保养到期时间">{{ assetDetail.maintenanceDueDate || assetDetail.maintenanceTime }}</el-descriptions-item>
<el-descriptions-item label="保养说明" :span="3">{{ assetDetail.maintenanceDescription }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="3">{{ assetDetail.remark }}</el-descriptions-item>
</el-descriptions>
@ -46,6 +46,8 @@
<script>
import { getAsset } from '@/api/asset/inventory'
import { getClassificationTree } from '@/api/asset/classification'
import { getLocationTree } from '@/api/asset/location'
export default {
name: 'AssetDetail',
@ -59,7 +61,11 @@ export default {
return {
loading: false,
activeTab: 'basic',
assetDetail: {}
assetDetail: {},
//
classificationOptions: [],
//
locationOptions: []
}
},
watch: {
@ -72,6 +78,10 @@ export default {
immediate: true
}
},
created() {
this.getClassificationOptions()
this.getLocationOptions()
},
methods: {
/** 获取资产详情 */
getAssetDetail() {
@ -81,6 +91,7 @@ export default {
getAsset(this.assetId).then(response => {
if (response.code === '000000') {
this.assetDetail = response.data || {}
console.log('资产详情数据:', this.assetDetail)
} else {
this.$message.error(response.msg || '获取资产详情失败')
}
@ -93,14 +104,14 @@ export default {
/** 获取资产状态显示文本 */
getStatusText(status) {
const statusMap = {
'0': '空闲',
'1': '在用',
'2': '借用',
'3': '派发中',
'4': '退库中',
'5': '借出中',
'6': '归还中',
'7': '维修中'
'空闲': '空闲',
'在用': '在用',
'借用': '借用',
'派发中': '派发中',
'退库中': '退库中',
'借出中': '借出中',
'归还中': '归还中',
'维修中': '维修中'
}
return statusMap[status] || '未知'
},
@ -108,16 +119,125 @@ export default {
/** 获取资产状态显示类型 */
getStatusType(status) {
const typeMap = {
'0': 'success',
'1': 'primary',
'2': 'info',
'3': 'warning',
'4': 'warning',
'5': 'warning',
'6': 'warning',
'7': 'danger'
'空闲': 'success',
'在用': 'primary',
'借用': 'info',
'派发中': 'warning',
'退库中': 'warning',
'借出中': 'warning',
'归还中': 'warning',
'维修中': 'danger'
}
return typeMap[status] || 'info'
},
/** 获取资产分类树形选项 */
getClassificationOptions() {
getClassificationTree({ status: '1' }).then(response => {
if (response.code === '000000') {
this.classificationOptions = this.processClassificationTree(response.data || [])
}
})
},
/** 处理分类树形数据 */
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))
},
/** 获取位置树选项 */
getLocationOptions() {
getLocationTree().then(response => {
if (response.code === '000000') {
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))
},
/** 获取分类名称 */
getClassificationName(classificationId) {
if (!classificationId) return '未知分类'
//
if (this.assetDetail.classificationName) return this.assetDetail.classificationName
//
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 '未知位置'
//
if (this.assetDetail.locationName) return this.assetDetail.locationName
//
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 || '未知位置'
}
}
}

View File

@ -10,17 +10,25 @@
<el-cascader
v-model="form.classificationId"
:options="classificationOptions"
:props="{ checkStrictly: true, value: 'id', label: 'name', emitPath: false }"
:props="{
checkStrictly: true,
emitPath: false,
expandTrigger: 'hover',
value: 'id',
label: 'name',
leaf: 'leaf'
}"
placeholder="请选择资产分类"
clearable
filterable
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="管理员" prop="administratorId">
<el-form-item label="管理员" prop="adminUserId">
<el-select
v-model="form.administratorId"
v-model="form.adminUserId"
placeholder="请选择管理员"
filterable
remote
@ -57,9 +65,17 @@
<el-cascader
v-model="form.locationId"
:options="locationOptions"
:props="{ checkStrictly: true, value: 'id', label: 'name', emitPath: false }"
:props="{
checkStrictly: true,
emitPath: false,
expandTrigger: 'hover',
value: 'id',
label: 'name',
leaf: 'leaf'
}"
placeholder="请选择所在位置"
clearable
filterable
style="width: 100%"
/>
</el-form-item>
@ -73,16 +89,16 @@
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="资产名称" prop="name">
<el-input v-model="form.name" placeholder="请输入资产名称" />
<el-form-item label="资产名称" prop="assetName">
<el-input v-model="form.assetName" placeholder="请输入资产名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="资产编码" prop="code">
<el-input v-model="form.code" placeholder="请输入资产编码" :disabled="isEdit" />
<el-form-item label="资产编码" prop="assetCode">
<el-input v-model="form.assetCode" placeholder="请输入资产编码" :disabled="isEdit" />
</el-form-item>
</el-col>
<el-col :span="8">
@ -109,17 +125,17 @@
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="预计使用期限(月)" prop="expectedUsePeriod">
<el-input-number v-model="form.expectedUsePeriod" :min="0" :step="1" style="width: 100%" />
<el-form-item label="预计使用期限(月)" prop="expectedDepreciationPeriod">
<el-input-number v-model="form.expectedDepreciationPeriod" :min="0" :step="1" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="购置时间" prop="purchaseTime">
<el-form-item label="购置时间" prop="purchaseDate">
<el-date-picker
v-model="form.purchaseTime"
v-model="form.purchaseDate"
type="date"
placeholder="请选择购置时间"
style="width: 100%"
@ -128,9 +144,9 @@
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="入库时间" prop="storageTime">
<el-form-item label="入库时间" prop="storageDate">
<el-date-picker
v-model="form.storageTime"
v-model="form.storageDate"
type="date"
placeholder="请选择入库时间"
style="width: 100%"
@ -139,9 +155,9 @@
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="保养到期时间" prop="maintenanceTime">
<el-form-item label="保养到期时间" prop="maintenanceDueDate">
<el-date-picker
v-model="form.maintenanceTime"
v-model="form.maintenanceDueDate"
type="date"
placeholder="请选择保养到期时间"
style="width: 100%"
@ -207,7 +223,10 @@
</template>
<script>
import { getAsset, addAsset, updateAsset, listAssetClassTree, listAssetLocationTree, listCompanies, listUsers, uploadAssetImage } from '@/api/asset/inventory'
import { getAsset, addAsset, updateAsset, listCompanies, listUsers, uploadAssetImage, checkAssetCode } from '@/api/asset/inventory'
import { getClassificationTree } from '@/api/asset/classification'
import { getLocationTree } from '@/api/asset/location'
export default {
name: 'AssetForm',
@ -243,22 +262,22 @@ export default {
form: {
id: undefined,
classificationId: undefined,
administratorId: undefined,
adminUserId: undefined,
companyId: undefined,
locationId: undefined,
purchaseType: '1',
name: '',
code: '',
assetName: '',
assetCode: '',
brand: '',
model: '',
serialNumber: '',
purchaseAmount: undefined,
expectedUsePeriod: undefined,
expectedDepreciationPeriod: undefined,
remark: '',
purchaseTime: '',
storageTime: '',
purchaseDate: '',
storageDate: '',
imageUrl: '',
maintenanceTime: '',
maintenanceDueDate: '',
maintenanceDescription: '',
expectedDepreciationPeriod: undefined
},
@ -267,7 +286,7 @@ export default {
classificationId: [
{ required: true, message: '请选择资产分类', trigger: 'change' }
],
administratorId: [
adminUserId: [
{ required: true, message: '请选择管理员', trigger: 'change' }
],
companyId: [
@ -279,7 +298,7 @@ export default {
purchaseType: [
{ required: true, message: '请选择购置方式', trigger: 'change' }
],
code: [
assetCode: [
{ required: true, message: '请输入资产编码', trigger: 'blur' }
],
brand: [
@ -294,11 +313,39 @@ export default {
this.getCompanyOptions()
this.remoteSearchAdmin('')
//
this.addMockData()
if (this.isEdit && this.assetId) {
this.getAssetDetail()
}
},
methods: {
/** 添加模拟数据 */
addMockData() {
//
if (this.adminOptions.length === 0) {
this.adminOptions = [
{ id: '1', name: '张三' },
{ id: '2', name: '李四' },
{ id: '3', name: '王五' },
{ id: '4', name: '赵六' },
{ id: '5', name: '钱七' }
]
}
//
if (this.companyOptions.length === 0) {
this.companyOptions = [
{ id: '1', name: '总公司' },
{ id: '2', name: '北京分公司' },
{ id: '3', name: '上海分公司' },
{ id: '4', name: '广州分公司' },
{ id: '5', name: '深圳分公司' }
]
}
},
/** 获取资产详情 */
getAssetDetail() {
this.loading = true
@ -307,9 +354,9 @@ export default {
this.form = response.data || {}
//
if (this.form.administratorId && this.form.administratorName) {
if (this.form.adminUserId && this.form.administratorName) {
this.adminOptions = [
{ id: this.form.administratorId, name: this.form.administratorName }
{ id: this.form.adminUserId, name: this.form.administratorName }
]
}
} else {
@ -323,28 +370,78 @@ export default {
/** 获取资产分类树形选项 */
getClassificationOptions() {
listAssetClassTree().then(response => {
getClassificationTree({ status: '1' }).then(response => {
if (response.code === '000000') {
this.classificationOptions = response.data || []
this.classificationOptions = this.processClassificationTree(response.data || [])
}
})
},
/** 获取资产位置树形选项 */
/** 处理分类树形数据 */
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))
processedNode.leaf = false
} else {
processedNode.leaf = true
}
return processedNode
}
return data.map(item => processNode(item))
},
/** 获取位置树选项 */
getLocationOptions() {
listAssetLocationTree().then(response => {
getLocationTree().then(response => {
if (response.code === '000000') {
this.locationOptions = response.data || []
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))
processedNode.leaf = false
} else {
processedNode.leaf = true
}
return processedNode
}
return data.map(item => processNode(item))
},
/** 获取公司选项 */
getCompanyOptions() {
listCompanies().then(response => {
if (response.code === '000000') {
this.companyOptions = response.data || []
}
}).catch(() => {
// API使
this.addMockData()
})
},
@ -358,6 +455,8 @@ export default {
}
}).catch(() => {
this.adminLoading = false
// API使
this.addMockData()
})
},
@ -369,23 +468,47 @@ export default {
const formData = { ...this.form }
const method = this.isEdit ? updateAsset : addAsset
method(formData).then(response => {
if (response.code === '000000') {
this.$message.success(this.isEdit ? '修改成功' : '新增成功')
this.$emit('refresh')
this.$emit('close')
} else {
this.$message.error(response.msg || (this.isEdit ? '修改失败' : '新增失败'))
}
this.loading = false
}).catch(() => {
this.loading = false
})
//
if (!this.isEdit) {
checkAssetCode(this.form.code).then(response => {
if (response.code === '000000') {
if (!response.data) {
this.$message.error('资产编码已存在,请更换其他编码')
this.loading = false
return
}
this.saveAsset(formData)
} else {
this.$message.error(response.msg || '检查资产编码失败')
this.loading = false
}
}).catch(() => {
this.loading = false
})
} else {
this.saveAsset(formData)
}
}
})
},
/** 保存资产信息 */
saveAsset(formData) {
const method = this.isEdit ? updateAsset : addAsset
method(formData).then(response => {
if (response.code === '000000') {
this.$message.success(this.isEdit ? '修改成功' : '新增成功')
this.$emit('refresh')
this.$emit('close')
} else {
this.$message.error(response.msg || (this.isEdit ? '修改失败' : '新增失败'))
}
this.loading = false
}).catch(() => {
this.loading = false
})
},
/** 取消按钮 */
cancel() {
this.$emit('close')
@ -410,7 +533,7 @@ export default {
if (response.code === '000000') {
this.form.imageUrl = response.data.url
} else {
this.$message.error('上传图片失败')
this.$message.error(response.msg || '上传图片失败')
}
},

View File

@ -1,57 +1,26 @@
<template>
<div class="asset-print-container" v-loading="loading">
<div class="print-options">
<div class="option-item">
<span class="option-label">标签模板:</span>
<el-select v-model="printParams.templateId" placeholder="请选择标签模板" style="width: 200px">
<el-option
v-for="item in templateOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</div>
<div class="option-item">
<span class="option-label">纸张类型:</span>
<el-select v-model="printParams.paperType" placeholder="请选择纸张类型" style="width: 200px">
<el-option label="A4" value="A4" />
<el-option label="A5" value="A5" />
<el-option label="标签纸" value="LABEL" />
</el-select>
</div>
<div class="option-item">
<span class="option-label">显示内容:</span>
<el-checkbox-group v-model="printParams.showFields">
<el-checkbox label="code">资产编码</el-checkbox>
<el-checkbox label="name">资产名称</el-checkbox>
<el-checkbox label="qrCode">二维码</el-checkbox>
<el-checkbox label="barCode">条形码</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="preview-container">
<div class="preview-title">打印预览</div>
<div class="preview-content" ref="previewContent">
<div v-for="(asset, index) in assetsToPrint" :key="index" class="label-item">
<div class="label-header">资产标签</div>
<div class="label-content">
<div v-if="printParams.showFields.includes('code')" class="label-field">
<span class="field-label">资产编码:</span>
<span class="field-value">{{ asset.code }}</span>
</div>
<div v-if="printParams.showFields.includes('name')" class="label-field">
<span class="field-label">资产名称:</span>
<span class="field-value">{{ asset.name }}</span>
</div>
<!-- 模拟二维码 -->
<div v-if="printParams.showFields.includes('qrCode')" class="label-qrcode">
<div class="mock-qrcode"></div>
</div>
<!-- 模拟条形码 -->
<div v-if="printParams.showFields.includes('barCode')" class="label-barcode">
<div class="mock-barcode"></div>
<div ref="printSection" class="print-page">
<div class="print-content">
<div class="print-header">{{ currentDate }} {{ title }}</div>
<div class="label-grid">
<div v-for="(asset, index) in selectedAssets" :key="index" class="label-item">
<div class="label-content">
<div class="qrcode-section">
<div class="asset-icon">
<!-- <i class="el-icon-s-goods"></i> 资产 -->
</div>
<div class="qrcode">
<qrcode-vue :value="asset.id || asset.assetId" :size="80" level="H"></qrcode-vue>
</div>
<!-- <div class="asset-code">资产编码: {{ asset.assetCode || asset.code }}</div> -->
</div>
<div class="info-section">
<div v-for="(field, fieldIndex) in displayFields" :key="fieldIndex" class="info-item">
<span v-if="asset[field.key]">{{ field.label }}: {{ asset[field.key] }}</span>
</div>
</div>
</div>
</div>
</div>
@ -66,49 +35,84 @@
</template>
<script>
import { printAssetLabel } from '@/api/asset/inventory'
import { getLabelConfig } from '@/api/asset/inventory'
import QrcodeVue from 'qrcode.vue'
export default {
name: 'AssetLabelPrint',
components: {
QrcodeVue
},
props: {
ids: {
type: Array,
required: true
},
selectedAssets: {
type: Array,
default: () => []
}
},
data() {
return {
loading: false,
assetsToPrint: [],
templateOptions: [
{ id: '1', name: '默认标签模板' },
{ id: '2', name: '简洁标签模板' },
{ id: '3', name: '详细标签模板' }
],
printParams: {
templateId: '1',
paperType: 'A4',
showFields: ['code', 'name', 'qrCode']
}
title: '资产标签',
currentDate: '',
labelConfig: null,
displayFields: [
{ key: 'assetName', label: '资产名称' },
{ key: 'classificationName', label: '资产分类' },
{ key: 'locationName', label: '所在位置' },
{ key: 'brand', label: '品牌' },
{ key: 'model', label: '型号' },
{ key: 'serialNumber', label: '设备序列号' },
{ key: 'administratorName', label: '管理员' }
]
}
},
created() {
this.getPrintData()
//
this.setCurrentDate()
// 便
console.log('Selected assets:', JSON.stringify(this.selectedAssets))
//
this.getLabelConfig()
},
methods: {
/** 获取打印数据 */
getPrintData() {
if (!this.ids || this.ids.length === 0) {
this.$message.warning('未选择要打印的资产')
return
}
/** 设置当前日期 */
setCurrentDate() {
const now = new Date()
this.currentDate = now.getFullYear() + '/' +
String(now.getMonth() + 1).padStart(2, '0') + '/' +
String(now.getDate()).padStart(2, '0')
},
/** 获取标签配置 */
getLabelConfig() {
this.loading = true
printAssetLabel(this.ids).then(response => {
getLabelConfig().then(response => {
if (response.code === '000000') {
this.assetsToPrint = response.data || []
} else {
this.$message.error(response.msg || '获取打印数据失败')
this.labelConfig = response.data
// API使API
if (this.labelConfig && this.labelConfig.labelItems) {
// labelItems
// IDID
let labelItemArray = []
if (typeof this.labelConfig.labelItems === 'string') {
const itemIds = this.labelConfig.labelItems.split(',').filter(item => item.trim() !== '')
labelItemArray = itemIds.map(id => ({ id: parseInt(id.trim()) }))
} else if (Array.isArray(this.labelConfig.labelItems)) {
labelItemArray = this.labelConfig.labelItems
}
//
if (labelItemArray.length > 0) {
this.displayFields = this.mapLabelItemsToFields(labelItemArray)
}
}
}
this.loading = false
}).catch(() => {
@ -116,134 +120,264 @@ export default {
})
},
/** 将标签项映射到对应的字段 */
mapLabelItemsToFields(labelItems) {
// ID
const fieldMappings = {
1: { key: 'assetName', label: '资产名称', possibleKeys: ['assetName', 'name'] },
2: { key: 'classificationName', label: '资产分类', possibleKeys: ['classificationName', 'className'] },
3: { key: 'assetCode', label: '资产编码', possibleKeys: ['assetCode', 'code'] },
4: { key: 'locationName', label: '资产位置', possibleKeys: ['locationName', 'location'] },
5: { key: 'brand', label: '品牌', possibleKeys: ['brand'] },
6: { key: 'model', label: '型号', possibleKeys: ['model'] },
7: { key: 'serialNumber', label: '设备序列号', possibleKeys: ['serialNumber', 'sn'] },
8: { key: 'administratorName', label: '管理员', possibleKeys: ['administratorName', 'admin', 'adminName'] },
9: { key: 'maintenanceDueDate', label: '保养到期时间', possibleKeys: ['maintenanceDueDate', 'maintenanceTime', 'maintDueDate', 'maintainEndDate'] },
10: { key: 'maintenanceDescription', label: '保养说明', possibleKeys: ['maintenanceDescription', 'maintInstructions', 'maintenanceDesc', 'maintDesc', 'maintenanceInfo'] },
11: { key: 'departmentName', label: '使用部门', possibleKeys: ['departmentName', 'deptName', 'department'] }
}
//
console.log('labelItems:', labelItems)
// labelItems
return labelItems.map(item => {
// ID
const id = item.id || item.fieldId
const mapping = fieldMappings[id]
console.log('Processing item:', item, 'mapping:', mapping)
if (mapping) {
return {
key: mapping.key,
label: item.name || item.fieldName || mapping.label,
possibleKeys: mapping.possibleKeys
}
}
return null
}).filter(item => item !== null)
},
/** 获取打印内容 */
getPrintContent() {
this.handlePrint()
return true
},
/** 执行打印 */
handlePrint() {
//
//
const printWindow = window.open('', '_blank')
if (!printWindow) {
this.$message.error('打印窗口被阻止,请允许弹出窗口后重试')
this.$message.error('打印窗口被浏览器阻止,请允许弹出窗口')
return
}
//
let printContent = `
<html>
<head>
<title>资产标签打印</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
.print-page {
page-break-after: always;
}
.label-item {
border: 1px solid #ddd;
border-radius: 5px;
padding: 10px;
margin-bottom: 15px;
width: 300px;
}
.label-header {
font-size: 16px;
font-weight: bold;
text-align: center;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #eee;
}
.label-content {
padding: 5px;
}
.label-field {
margin-bottom: 5px;
}
.field-label {
font-weight: bold;
display: inline-block;
width: 80px;
}
.field-value {
display: inline-block;
}
.label-qrcode, .label-barcode {
text-align: center;
margin-top: 10px;
}
.mock-qrcode {
width: 80px;
height: 80px;
background: repeating-conic-gradient(#000 0% 25%, #fff 0% 50%) 50% / 10px 10px;
margin: 0 auto;
}
.mock-barcode {
width: 150px;
height: 40px;
background: repeating-linear-gradient(to right, #000, #000 2px, #fff 2px, #fff 4px);
margin: 0 auto;
}
@media print {
body {
padding: 0;
margin: 0;
//
const assetsToUse = this.selectedAssets
//
const styleContent = `
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
.print-header {
text-align: right;
font-size: 14px;
color: #666;
margin-bottom: 20px;
}
.label-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.label-item {
border: 1px solid #ddd;
border-radius: 5px;
padding: 10px;
background-color: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
page-break-inside: avoid;
margin-bottom: 20px;
}
.label-content {
display: flex;
gap: 10px;
}
.qrcode-section {
display: flex;
flex-direction: column;
align-items: center;
width: 120px;
border-right: 1px dashed #ddd;
padding-right: 10px;
}
.asset-icon {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.qrcode img {
width: 80px;
height: 80px;
}
.asset-code {
font-size: 12px;
text-align: center;
word-break: break-all;
color: #666;
}
.info-section {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.info-item {
font-size: 12px;
margin-bottom: 5px;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@media print {
body {
padding: 0;
margin: 0;
}
@page {
size: A4;
margin: 1cm;
}
}
`
// HTML
printWindow.document.write('<html><head><title>资产标签打印</title>')
printWindow.document.write('<style>' + styleContent + '</style>')
printWindow.document.write('<script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"><\/script>')
printWindow.document.write('</head><body>')
//
printWindow.document.write('<div class="print-header">' + this.currentDate + ' ' + this.title + '</div>')
printWindow.document.write('<div class="label-grid">')
//
assetsToUse.forEach(asset => {
const assetId = asset.id || asset.assetId
printWindow.document.write('<div class="label-item">')
printWindow.document.write('<div class="label-content">')
printWindow.document.write('<div class="qrcode-section">')
// printWindow.document.write('<div class="asset-icon"></div>')
printWindow.document.write('<div class="qrcode" id="qrcode-' + assetId + '"></div>')
// printWindow.document.write('<div class="asset-code">: ' + (asset.assetCode || asset.code) + '</div>')
printWindow.document.write('</div>')
printWindow.document.write('<div class="info-section">')
//
this.displayFields.forEach(field => {
//
console.log('Checking field:', field.key, 'in asset:', asset)
//
let value = asset[field.key];
// 使
if (!value && field.possibleKeys) {
for (const possibleKey of field.possibleKeys) {
if (asset[possibleKey] !== undefined && asset[possibleKey] !== null) {
value = asset[possibleKey];
console.log('Found value with alternative key:', possibleKey, value);
break;
}
}
</style>
</head>
<body>
`
this.assetsToPrint.forEach((asset, index) => {
printContent += `
<div class="label-item">
<div class="label-header">资产标签</div>
<div class="label-content">
${this.printParams.showFields.includes('code') ? `
<div class="label-field">
<span class="field-label">资产编码:</span>
<span class="field-value">${asset.code}</span>
</div>
` : ''}
${this.printParams.showFields.includes('name') ? `
<div class="label-field">
<span class="field-label">资产名称:</span>
<span class="field-value">${asset.name}</span>
</div>
` : ''}
${this.printParams.showFields.includes('qrCode') ? `
<div class="label-qrcode">
<div class="mock-qrcode"></div>
</div>
` : ''}
${this.printParams.showFields.includes('barCode') ? `
<div class="label-barcode">
<div class="mock-barcode"></div>
</div>
` : ''}
</div>
</div>
${(index + 1) % 6 === 0 ? '<div class="print-page"></div>' : ''}
`
}
console.log('Final value:', value);
// 使
printWindow.document.write('<div class="info-item">')
printWindow.document.write('<span>' + field.label + ': ' + (value || '-') + '</span>')
printWindow.document.write('</div>')
})
printWindow.document.write('</div>')
printWindow.document.write('</div>')
printWindow.document.write('</div>')
})
printContent += `
</body>
</html>
`
printWindow.document.write('</div>')
//
printWindow.document.write('<script>')
printWindow.document.write('window.onload = function() {')
//
assetsToUse.forEach(asset => {
const assetId = asset.id || asset.assetId
printWindow.document.write(`
(function() {
var typeNumber = 0;
var errorCorrectionLevel = "H";
var qr = qrcode(typeNumber, errorCorrectionLevel);
qr.addData("${assetId}");
qr.make();
document.getElementById("qrcode-${assetId}").innerHTML = qr.createImgTag(3);
})();
`)
})
//
printWindow.document.write(`
//
setTimeout(function() {
//
var mediaQueryList = window.matchMedia('print');
//
mediaQueryList.addListener(function(mql) {
if (!mql.matches) {
//
setTimeout(function() {
window.close();
}, 100);
}
});
//
window.print();
//
var cancelBtn = document.createElement('button');
cancelBtn.innerText = '关闭窗口';
cancelBtn.style.display = 'block';
cancelBtn.style.margin = '20px auto';
cancelBtn.style.padding = '10px 20px';
cancelBtn.style.backgroundColor = '#409EFF';
cancelBtn.style.color = 'white';
cancelBtn.style.border = 'none';
cancelBtn.style.borderRadius = '4px';
cancelBtn.style.cursor = 'pointer';
cancelBtn.onclick = function() {
window.close();
};
document.body.appendChild(cancelBtn);
}, 500);
`)
printWindow.document.write('}')
printWindow.document.write('<\/script>')
printWindow.document.write('</body></html>')
//
printWindow.document.open()
printWindow.document.write(printContent)
printWindow.document.close()
//
printWindow.onload = function() {
printWindow.print()
printWindow.close()
}
},
/** 取消按钮 */
@ -256,103 +390,110 @@ export default {
<style lang="scss" scoped>
.asset-print-container {
padding: 10px;
}
.print-page {
background-color: #fff;
padding: 20px;
.print-options {
display: flex;
flex-wrap: wrap;
align-items: center;
margin-bottom: 20px;
.option-item {
margin-right: 20px;
margin-bottom: 10px;
display: flex;
align-items: center;
.option-label {
margin-right: 10px;
font-weight: bold;
}
}
}
.preview-container {
margin-bottom: 20px;
border: 1px solid #eee;
padding: 15px;
border-radius: 5px;
.preview-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
padding-bottom: 5px;
border-bottom: 1px solid #eee;
}
.preview-content {
display: flex;
flex-wrap: wrap;
.label-item {
width: 300px;
margin: 0 15px 15px 0;
border: 1px solid #ddd;
border-radius: 5px;
padding: 10px;
.label-header {
font-size: 16px;
font-weight: bold;
text-align: center;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #eee;
}
.label-content {
padding: 5px;
.label-field {
margin-bottom: 5px;
.field-label {
font-weight: bold;
display: inline-block;
width: 80px;
}
.field-value {
display: inline-block;
}
}
.label-qrcode, .label-barcode {
text-align: center;
margin-top: 10px;
}
.mock-qrcode {
width: 80px;
height: 80px;
background: repeating-conic-gradient(#000 0% 25%, #fff 0% 50%) 50% / 10px 10px;
margin: 0 auto;
}
.mock-barcode {
width: 150px;
height: 40px;
background: repeating-linear-gradient(to right, #000, #000 2px, #fff 2px, #fff 4px);
margin: 0 auto;
}
}
}
}
}
.print-actions {
text-align: center;
margin-bottom: 20px;
}
.print-header {
text-align: right;
font-size: 14px;
color: #666;
margin-bottom: 20px;
}
.label-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.label-item {
border: 1px solid #ddd;
border-radius: 5px;
padding: 10px;
background-color: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
page-break-inside: avoid;
}
.label-content {
display: flex;
gap: 10px;
}
.qrcode-section {
display: flex;
flex-direction: column;
align-items: center;
width: 120px;
border-right: 1px dashed #ddd;
padding-right: 10px;
}
.asset-icon {
font-size: 14px;
color: #666;
margin-bottom: 5px;
i {
font-size: 16px;
color: #409EFF;
}
}
</style>
.qrcode {
margin: 5px 0;
}
.asset-code {
font-size: 12px;
text-align: center;
word-break: break-all;
color: #666;
}
.info-section {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.info-item {
font-size: 12px;
margin-bottom: 5px;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.print-actions {
text-align: center;
margin-top: 20px;
}
@media print {
.print-actions {
display: none;
}
.print-page {
margin: 0;
padding: 0;
}
.label-grid {
gap: 10px;
}
@page {
size: A4;
margin: 1cm;
}
}
</style>

View File

@ -25,21 +25,26 @@
</el-button>
</el-form-item>
<el-form-item>
<el-dropdown @command="handleColumnDisplay" split-button type="primary" size="mini">
列显示
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(item, index) in columns" :key="index" :command="index">
<el-checkbox v-model="item.visible">{{ item.label }}</el-checkbox>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-popover
placement="bottom"
width="200"
trigger="click"
v-model="columnFilterVisible"
style="margin-left: 10px;">
<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-form v-show="advanced" :model="queryParams" ref="advancedQueryForm" :inline="true" label-width="100px">
<el-form-item label="资产状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择资产状态" clearable style="width: 180px">
<el-form-item label="资产状态" prop="assetStatus">
<el-select 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>
@ -52,27 +57,35 @@
style="width: 180px"
/>
</el-form-item>
<el-form-item label="资产名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入资产名称" clearable style="width: 180px" />
<el-form-item label="资产名称" prop="assetName">
<el-input v-model="queryParams.assetName" placeholder="请输入资产名称" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="所在位置" prop="locationId">
<el-form-item label="所在位置">
<el-cascader
v-model="queryParams.locationId"
:options="locationOptions"
:props="{ checkStrictly: true, value: 'id', label: 'name', emitPath: false }"
: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="code">
<el-input v-model="queryParams.code" placeholder="请输入资产编码" clearable style="width: 180px" />
<el-form-item label="资产编码" prop="assetCode">
<el-input v-model="queryParams.assetCode" placeholder="请输入资产编码" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="设备序列号" prop="serialNumber">
<el-input v-model="queryParams.serialNumber" placeholder="请输入设备序列号" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="管理员" prop="administratorId">
<el-form-item label="管理员" prop="adminUserId">
<el-select
v-model="queryParams.administratorId"
v-model="queryParams.adminUserId"
placeholder="请选择管理员"
clearable
filterable
@ -112,40 +125,43 @@
style="width: 100%; margin-top: 15px;"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="资产编码" prop="code" width="120" :show-overflow-tooltip="true">
<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.code }}</el-link>
<el-link type="primary" @click="handleDetail(scope.row)">{{ scope.row.assetCode }}</el-link>
</template>
</el-table-column>
<el-table-column label="标签链接" width="100" v-if="columns[1].visible" align="center">
<el-table-column label="资产分类" width="120" v-if="isColumnVisible('classification')" :show-overflow-tooltip="true">
<template slot-scope="scope">
<el-button type="text" icon="el-icon-printer" @click="handlePrintSingle(scope.row)" />
{{ getClassificationName(scope.row.classificationId) }}
</template>
</el-table-column>
<el-table-column label="资产分类" prop="classificationName" width="120" v-if="columns[2].visible" :show-overflow-tooltip="true" />
<el-table-column label="资产名称" prop="name" width="120" v-if="columns[3].visible" :show-overflow-tooltip="true" />
<el-table-column label="资产状态" prop="status" width="100" v-if="columns[4].visible">
<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.status)">{{ getStatusText(scope.row.status) }}</el-tag>
<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="columns[5].visible" :show-overflow-tooltip="true" />
<el-table-column label="型号" prop="model" width="100" v-if="columns[6].visible" :show-overflow-tooltip="true" />
<el-table-column label="设备序列号" prop="serialNumber" width="140" v-if="columns[7].visible" :show-overflow-tooltip="true" />
<el-table-column label="管理员" prop="administratorName" width="100" v-if="columns[8].visible" :show-overflow-tooltip="true" />
<el-table-column label="所属公司" prop="companyName" width="140" v-if="columns[9].visible" :show-overflow-tooltip="true" />
<el-table-column label="所在位置" prop="locationName" width="140" v-if="columns[10].visible" :show-overflow-tooltip="true" />
<el-table-column label="购置时间" prop="purchaseTime" width="120" v-if="columns[11].visible" />
<el-table-column label="购置方式" prop="purchaseType" width="100" v-if="columns[12].visible">
<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="columns[13].visible" />
<el-table-column label="入库时间" prop="storageTime" width="120" v-if="columns[14].visible" />
<el-table-column label="预计使用期限(月)" prop="expectedUsePeriod" width="150" v-if="columns[15].visible" />
<el-table-column label="备注" prop="remark" width="150" v-if="columns[16].visible" :show-overflow-tooltip="true" />
<el-table-column label="资产照片" width="100" v-if="columns[17].visible" align="center">
<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"
@ -156,13 +172,13 @@
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="保养到期时间" prop="maintenanceTime" width="130" v-if="columns[18].visible" />
<el-table-column label="保养说明" prop="maintenanceDescription" width="150" v-if="columns[19].visible" :show-overflow-tooltip="true" />
<el-table-column label="预计折旧期限(月)" prop="expectedDepreciationPeriod" width="150" v-if="columns[20].visible" />
<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.status === '0'" style="color: #F56C6C;">删除</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>
@ -181,7 +197,7 @@
</el-card>
<!-- 导入对话框 -->
<el-dialog :title="importTitle" :visible.sync="importOpen" width="500px" append-to-body>
<el-dialog :title="upload.title" :visible.sync="upload.open" width="500px" append-to-body>
<el-upload
ref="upload"
:limit="1"
@ -189,8 +205,9 @@
:headers="upload.headers"
:action="upload.url"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-progress="handleFileUpload"
:on-success="handleFileSuccess"
:on-error="handleFileError"
:auto-upload="false"
drag>
<i class="el-icon-upload"></i>
@ -202,7 +219,7 @@
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="importOpen = false"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</el-dialog>
@ -227,29 +244,26 @@
<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 {
listAsset,
getAsset,
delAsset,
delAssetBatch,
exportAsset,
downloadTemplate,
importAsset,
listAssetClassTree,
listAssetLocationTree,
listCompanies,
listUsers,
printAssetLabel,
getAssetStatusOptions
} from '@/api/asset/inventory'
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'
@ -263,6 +277,7 @@ export default {
},
data() {
return {
printOpen: false,
//
loading: false,
//
@ -275,6 +290,8 @@ export default {
total: 0,
//
assetList: [],
//
assetsToPrint: [],
//
title: '',
//
@ -283,8 +300,6 @@ export default {
detailOpen: false,
//
importOpen: false,
//
printOpen: false,
//
importTitle: '导入资产数据',
//
@ -310,48 +325,53 @@ export default {
pageNum: 1,
pageSize: 10,
keyword: '',
status: '',
assetStatus: '',
classificationId: undefined,
name: '',
assetName: '',
locationId: undefined,
code: '',
assetCode: '',
serialNumber: '',
administratorId: undefined,
adminUserId: undefined,
maintenanceExpired: ''
},
//
upload: {
//
//
open: false,
//
title: '',
isUploading: false,
//
headers: { },
// URL
url: process.env.VUE_APP_BASE_API + '/asset/inventory/import'
headers: {},
//
url: process.env.VUE_APP_BASE_API + '/asset/import'
},
//
columns: [
{ label: '资产编码', visible: true, disabled: true },
{ label: '标签链接', visible: true },
{ label: '资产分类', visible: true },
{ label: '资产名称', visible: true },
{ label: '资产状态', visible: true },
{ label: '品牌', visible: true },
{ label: '型号', visible: true },
{ label: '设备序列号', visible: true },
{ label: '管理员', visible: true },
{ label: '所属公司', visible: true },
{ label: '所在位置', visible: true },
{ label: '购置时间', visible: true },
{ label: '购置方式', visible: true },
{ label: '购置金额(含税)', visible: true },
{ label: '入库时间', visible: true },
{ label: '预计使用期限(月)', visible: true },
{ label: '备注', visible: true },
{ label: '资产照片', visible: true },
{ label: '保养到期时间', visible: true },
{ label: '保养说明', visible: true },
{ label: '预计折旧期限(月)', visible: true }
]
{ 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() {
@ -359,54 +379,153 @@ export default {
this.getStatusOptions()
this.getClassificationOptions()
this.getLocationOptions()
this.getCompanyOptions()
// 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
listAsset(this.queryParams).then(response => {
listAssets(this.queryParams).then(response => {
if (response.code === '000000') {
this.assetList = response.data.list || []
this.total = response.data.total
} else {
this.$message.error(response.msg || '获取资产列表失败')
this.assetList = []
this.total = 0
}
this.loading = false
}).catch(() => {
this.loading = false
})
},
/** 获取资产状态选项 */
getStatusOptions() {
getAssetStatusOptions().then(response => {
if (response.code === '000000') {
this.statusOptions = response.data || []
}
})
this.statusOptions = [
{ value: '空闲', label: '空闲' },
{ value: '在用', label: '在用' },
{ value: '借用', label: '借用' },
{ value: '维修中', label: '维修中' },
{ value: '报废', label: '报废' }
]
},
/** 获取资产分类树形选项 */
getClassificationOptions() {
listAssetClassTree().then(response => {
getClassificationTree({ status: '1' }).then(response => {
if (response.code === '000000') {
this.classificationOptions = response.data || []
this.classificationOptions = this.processClassificationTree(response.data || [])
}
})
},
/** 获取资产位置树形选项 */
getLocationOptions() {
listAssetLocationTree().then(response => {
getLocationTree().then(response => {
if (response.code === '000000') {
this.locationOptions = response.data || []
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 => {
@ -432,33 +551,30 @@ export default {
},
/** 获取资产状态显示文本 */
getStatusText(status) {
getStatusText(assetStatus) {
const statusMap = {
'0': '空闲',
'1': '在用',
'2': '借用',
'3': '派发中',
'4': '退库中',
'5': '借出中',
'6': '归还中',
'7': '维修中'
'3': '维修中',
'4': '报废'
}
return statusMap[status] || '未知'
return statusMap[assetStatus] || '未知'
},
/** 获取资产状态显示类型 */
getStatusType(status) {
getStatusType(assetStatus) {
const typeMap = {
'0': 'success',
'1': 'primary',
'2': 'info',
'3': 'warning',
'4': 'warning',
'空闲': 'success',
'在用': 'primary',
'借用': 'info',
'维修中': 'warning',
'报废': 'warning',
'5': 'warning',
'6': 'warning',
'7': 'danger'
}
return typeMap[status] || 'info'
return typeMap[assetStatus] || 'info'
},
/** 切换高级搜索 */
@ -474,6 +590,22 @@ export default {
}
},
/** 全选/取消全选处理 */
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']
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
@ -490,13 +622,13 @@ export default {
pageNum: 1,
pageSize: 10,
keyword: '',
status: '',
assetStatus: '',
classificationId: undefined,
name: '',
assetName: '',
locationId: undefined,
code: '',
assetCode: '',
serialNumber: '',
administratorId: undefined,
adminUserId: undefined,
maintenanceExpired: ''
}
this.handleQuery()
@ -521,42 +653,60 @@ export default {
this.open = true
this.title = '修改资产'
this.isEdit = true
this.currentAssetId = row.id
this.currentAssetId = row.assetCode
},
/** 详情按钮操作 */
handleDetail(row) {
this.detailOpen = true
this.detailTitle = '资产详情'
this.currentAssetId = row.id
this.currentAssetId = row.assetCode
},
/** 删除按钮操作 */
handleDelete(row) {
const assetId = row.id
this.$confirm('是否确认删除该资产?', '警告', {
this.$confirm('是否确认删除资产编码为"' + row.assetCode + '"的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delAsset(assetId).then(response => {
if (response.code === '000000') {
this.$message.success('删除成功')
this.getList()
} else {
this.$message.error(response.msg || '删除失败')
}
})
return deleteAsset(row.assetCode)
}).then(response => {
if (response.code === '000000') {
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 === '000000') {
this.$message.success('批量删除成功')
this.getList()
}
}).catch(() => {})
},
/** 导出按钮操作 */
handleExport() {
const queryParams = { ...this.queryParams }
//
delete queryParams.pageNum
delete queryParams.pageSize
queryParams.pageNum = undefined
queryParams.pageSize = undefined
this.$confirm('是否确认导出所有资产数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@ -565,27 +715,28 @@ export default {
this.exportLoading = true
return exportAsset(queryParams)
}).then(response => {
this.download(response, '资产清单_' + new Date().getTime() + '.xlsx')
this.download(response, '资产数据.xlsx')
this.exportLoading = false
}).catch(() => {
this.exportLoading = false
})
},
/** 导入按钮操作 */
handleImport() {
this.importOpen = true
},
/** 下载导入模板 */
/** 下载模板操作 */
downloadImportTemplate() {
downloadTemplate().then(response => {
this.download(response, 'property_import.xlsx')
this.download(response, '资产导入模板.xlsx')
})
},
/** 文件上传中处理 */
handleFileUploadProgress() {
/** 导入按钮操作 */
handleImport() {
this.upload.open = true
this.upload.title = '导入资产数据'
},
/** 处理文件上传中 */
handleFileUpload() {
this.upload.isUploading = true
},
@ -593,40 +744,64 @@ export default {
handleFileSuccess(response) {
this.upload.isUploading = false
this.$refs.upload.clearFiles()
this.importOpen = false
this.upload.open = false
if (response.code === '000000') {
this.$message.success('导入成功')
this.$message.success('导入成功' + (response.data && response.data.successCount ? response.data.successCount : 0) + '条数据')
this.getList()
} else {
this.$message.error(response.msg || '导入失败')
}
},
/** 文件上传失败处理 */
handleFileError() {
this.upload.isUploading = false
this.$message.error('导入失败')
},
/** 提交上传文件 */
submitFileForm() {
this.$refs.upload.submit()
},
/** 打印标签按钮操作 */
/** 打印标签 */
handlePrint() {
if (this.ids.length === 0) {
this.$message.warning('请选择要打印的资产')
this.$message.warning('请选择要打印的资产')
return
}
this.printOpen = true
//
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) {
const ids = [row.id]
printAssetLabel(ids).then(response => {
if (response.code === '000000') {
//
this.$message.success('打印标签成功')
} else {
this.$message.error(response.msg || '打印标签失败')
}
})
this.ids = [row.id || row.assetId]
this.assetsToPrint = [row]
this.handlePrint()
},
/** 分页大小改变 */
@ -639,6 +814,50 @@ export default {
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 || '未知位置'
}
}
}
@ -661,4 +880,4 @@ export default {
margin-left: 0;
}
}
</style>
</style>

View File

@ -0,0 +1,654 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<!-- 左侧费用分类 -->
<el-col :span="6">
<div class="category-container">
<div class="category-header">
<h3>费用分类</h3>
<el-button type="primary" size="mini" icon="el-icon-plus"
@click="handleAddCategory">新增</el-button>
</div>
<div class="category-search">
<el-input v-model="categoryQuery" placeholder="请输入分类名称" clearable size="small"
@keyup.enter.native="handleCategorySearch">
<el-button slot="append" icon="el-icon-search" @click="handleCategorySearch"></el-button>
</el-input>
</div>
<el-menu :default-active="selectedCategoryId ? selectedCategoryId.toString() : ''"
class="category-menu" @select="handleCategorySelect">
<el-menu-item v-for="item in categoryList" :key="item.id" :index="item.id.toString()"
class="category-item">
<span>{{ item.categoryName }}</span>
<div class="category-actions">
<el-tooltip content="编辑" placement="top" :disabled="item.defauVerFlag === '1'">
<el-button type="text" icon="el-icon-edit" @click.stop="handleEditCategory(item)"
:disabled="item.defauVerFlag === '1'"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top" :disabled="item.defauVerFlag === '1'">
<el-button type="text" icon="el-icon-delete"
@click.stop="handleDeleteCategory(item)"
:disabled="item.defauVerFlag === '1'"></el-button>
</el-tooltip>
</div>
</el-menu-item>
</el-menu>
</div>
</el-col>
<!-- 右侧费用类型 -->
<el-col :span="18">
<div class="fee-type-container">
<div class="fee-type-header">
<div class="header-title">
<h3>费用类型</h3>
<span v-if="selectedCategory">- {{ selectedCategory.categoryName }}</span>
</div>
<el-button type="primary" icon="el-icon-plus" @click="handleAddType"
:disabled="!selectedCategoryId">新增费用类型</el-button>
</div>
<!-- 费用类型搜索区域 -->
<div class="fee-type-search">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="费用名称">
<el-input v-model="queryParams.feeTypeName" placeholder="请输入费用名称" clearable
@keyup.enter.native="handleQuery"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 费用类型表格 -->
<el-table v-loading="loading" :data="typeList" border>
<el-table-column type="index" width="50" align="center" label="序号"></el-table-column>
<el-table-column prop="feeTypeName" label="费用名称" min-width="140"></el-table-column>
<el-table-column label="费用分类" min-width="120" align="center">
<template slot-scope="scope">
{{ getCategoryNameById(scope.row.categoryId) }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.status === '1' ? 'success' : 'info'">
{{ scope.row.status === '1' ? '开启' : '关闭' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit"
@click="handleEditType(scope.row)">编辑</el-button>
<el-button size="mini" type="text" icon="el-icon-delete"
@click="handleDeleteType(scope.row)"
:disabled="scope.row.defauVerFlag === '1'">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<el-pagination v-show="total > 0" @size-change="handleSizeChange"
@current-change="handleCurrentChange" :current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]" :page-size="queryParams.pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
</el-col>
</el-row>
<!-- 添加或修改费用分类对话框 -->
<el-dialog :title="categoryTitle" :visible.sync="categoryOpen" width="500px" append-to-body
@open="onCategoryDialogOpen">
<el-form ref="categoryForm" :model="categoryForm" :rules="categoryRules" label-width="100px">
<el-form-item label="分类名称" prop="categoryName">
<el-input v-model="categoryForm.categoryName" placeholder="请输入分类名称" :disabled="categoryEdit"
style="width: 100%"></el-input>
</el-form-item>
<el-form-item label="保证金类型" prop="feeTpNo">
<el-radio-group v-model="categoryForm.feeTpNo">
<el-radio label="1"></el-radio>
<el-radio label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="categoryOpen = false"> </el-button>
<el-button type="primary" @click="submitCategoryForm"> </el-button>
</div>
</el-dialog>
<!-- 添加或修改费用类型对话框 -->
<el-dialog :title="typeTitle" :visible.sync="typeOpen" width="500px" append-to-body @open="onTypeDialogOpen">
<el-form ref="typeForm" :model="typeForm" :rules="typeRules" label-width="120px">
<el-form-item label="费用分类" prop="categoryId">
<el-select v-model="typeForm.categoryId" placeholder="请选择费用分类" style="width: 100%">
<el-option
v-for="item in categoryList"
:key="item.id"
:label="item.categoryName"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="费用类型名称" prop="feeTypeName">
<el-input v-model="typeForm.feeTypeName" placeholder="请输入费用类型名称" :disabled="typeEdit"
style="width: 100%"></el-input>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="typeForm.status">
<el-radio label="1">开启</el-radio>
<el-radio label="0">关闭</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="typeOpen = false"> </el-button>
<el-button type="primary" @click="submitTypeForm"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
getFeeCategories,
getAllFeeCategories,
getFeeCategory,
addFeeCategory,
updateFeeCategory,
deleteFeeCategory,
getFeeTypes,
getFeeType,
addFeeType,
updateFeeType,
deleteFeeType
} from '@/api/finance'
export default {
name: 'FeeTypeManagement',
data() {
return {
//
loading: false,
//
categoryList: [],
//
typeList: [],
//
total: 0,
//
categoryQuery: '',
// ID
selectedCategoryId: null,
//
selectedCategory: null,
//
queryParams: {
pageNum: 1,
pageSize: 10,
feeTypeName: undefined,
categoryId: null
},
//
categoryOpen: false,
categoryTitle: '',
categoryEdit: false,
categoryForm: {
id: undefined,
categoryName: undefined,
feeTpNo: '0',
defauVerFlag: '0'
},
categoryRules: {
categoryName: [
{ required: true, message: '分类名称不能为空', trigger: 'blur' },
{ min: 2, max: 50, message: '分类名称长度必须在2-50个字符之间', trigger: 'blur' }
],
feeTpNo: [
{ required: true, message: '保证金类型不能为空', trigger: 'change' }
]
},
//
typeOpen: false,
typeTitle: '',
typeEdit: false,
typeForm: {
id: undefined,
feeTypeName: '',
categoryId: null,
status: '1',
defauVerFlag: '0'
},
typeRules: {
feeTypeName: [
{ required: true, message: '费用类型名称不能为空', trigger: 'blur' },
{ min: 2, max: 50, message: '费用类型名称长度必须在2-50个字符之间', trigger: 'blur' }
],
status: [
{ required: true, message: '状态不能为空', trigger: 'change' }
]
}
}
},
created() {
this.getCategoryList()
},
methods: {
//
getCategoryList() {
getAllFeeCategories().then(response => {
if (response.code === '000000') {
this.categoryList = response.data
if (this.categoryList.length > 0 && !this.selectedCategoryId) {
this.handleCategorySelect(this.categoryList[0].id.toString())
}
} else {
this.$message.error(response.message || '获取费用分类失败')
}
})
},
//
handleCategorySearch() {
if (this.categoryQuery) {
const params = { categoryName: this.categoryQuery, pageNum: 1, pageSize: 100 }
getFeeCategories(params).then(response => {
if (response.code === '000000') {
this.categoryList = response.data.list
} else {
this.$message.error(response.message || '查询失败')
}
})
} else {
this.getCategoryList()
}
},
//
handleCategorySelect(categoryId) {
this.selectedCategoryId = categoryId
this.selectedCategory = this.categoryList.find(item => item.id.toString() === categoryId)
this.queryParams.categoryId = parseInt(categoryId)
this.queryParams.pageNum = 1
this.getTypeList()
},
//
getTypeList() {
this.loading = true
getFeeTypes(this.queryParams).then(response => {
if (response.code === '000000') {
this.typeList = response.data.list
this.total = response.data.total
} else {
this.$message.error(response.message || '查询失败')
this.typeList = []
this.total = 0
}
this.loading = false
}).catch(() => {
this.loading = false
})
},
//
handleQuery() {
this.queryParams.pageNum = 1
this.getTypeList()
},
//
resetQuery() {
this.queryParams.feeTypeName = undefined
this.handleQuery()
},
//
handleSizeChange(size) {
this.queryParams.pageSize = size
this.getTypeList()
},
//
handleCurrentChange(page) {
this.queryParams.pageNum = page
this.getTypeList()
},
//
handleAddCategory() {
this.categoryForm = {
id: undefined,
categoryName: undefined,
feeTpNo: '0',
defauVerFlag: '0'
}
this.categoryTitle = '新增费用分类'
this.categoryEdit = false
this.categoryOpen = true
this.$nextTick(() => {
if (this.$refs.categoryForm) {
this.$refs.categoryForm.resetFields()
}
})
},
//
handleEditCategory(row) {
getFeeCategory(row.id).then(response => {
if (response.code === '000000') {
const categoryData = response.data
//
if (categoryData.feeTpNo !== undefined) {
categoryData.feeTpNo = categoryData.feeTpNo.toString()
}
this.categoryTitle = '修改费用分类'
this.categoryEdit = true
//
this.categoryForm = categoryData
this.categoryOpen = true
} else {
this.$message.error(response.message || '获取详情失败')
}
})
},
//
submitCategoryForm() {
this.$refs.categoryForm.validate(valid => {
if (valid) {
if (this.categoryForm.id) {
updateFeeCategory(this.categoryForm.id, this.categoryForm).then(response => {
if (response.code === '000000') {
this.$message.success('修改成功')
this.categoryOpen = false
this.getCategoryList()
} else {
this.$message.error(response.message || '修改失败')
}
})
} else {
addFeeCategory(this.categoryForm).then(response => {
if (response.code === '000000') {
this.$message.success('新增成功')
this.categoryOpen = false
this.getCategoryList()
} else {
this.$message.error(response.message || '新增失败')
}
})
}
}
})
},
//
handleDeleteCategory(row) {
this.$confirm('是否确认删除该费用分类?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteFeeCategory(row.id).then(response => {
if (response.code === '000000') {
this.$message.success('删除成功')
this.getCategoryList()
//
if (this.selectedCategoryId === row.id.toString()) {
this.selectedCategoryId = null
this.selectedCategory = null
this.typeList = []
}
} else {
this.$message.error(response.message || '删除失败')
}
})
}).catch(() => { })
},
//
handleAddType() {
this.typeTitle = '新增费用类型'
this.typeEdit = false
this.typeOpen = true
//
this.$nextTick(() => {
//
this.typeForm = {
id: undefined,
feeTypeName: '',
categoryId: this.selectedCategoryId ? parseInt(this.selectedCategoryId) : null,
status: '1',
defauVerFlag: '0'
}
if (this.$refs.typeForm) {
this.$refs.typeForm.resetFields()
}
})
},
//
handleEditType(row) {
getFeeType(row.id).then(response => {
if (response.code === '000000') {
//
const typeData = response.data
//
if (typeData.status !== undefined) {
typeData.status = typeData.status.toString()
}
// categoryId
if (typeData.categoryId !== undefined) {
// selectvalue
typeData.categoryId = parseInt(typeData.categoryId)
}
this.typeTitle = '修改费用类型'
this.typeEdit = true
//
this.typeForm = typeData
this.typeOpen = true
} else {
this.$message.error(response.message || '获取详情失败')
}
})
},
//
submitTypeForm() {
this.$refs.typeForm.validate(valid => {
if (valid) {
//
const submitData = {
id: this.typeForm.id,
feeTypeName: this.typeForm.feeTypeName,
categoryId: this.typeForm.categoryId,
status: this.typeForm.status
}
if (this.typeForm.id) {
updateFeeType(this.typeForm.id, submitData).then(response => {
if (response.code === '000000') {
this.$message.success('修改成功')
this.typeOpen = false
this.getTypeList()
} else {
this.$message.error(response.message || '修改失败')
}
})
} else {
addFeeType(submitData).then(response => {
if (response.code === '000000') {
this.$message.success('新增成功')
this.typeOpen = false
this.getTypeList()
} else {
this.$message.error(response.message || '新增失败')
}
})
}
}
})
},
//
handleDeleteType(row) {
this.$confirm('是否确认删除该费用类型?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteFeeType(row.id).then(response => {
if (response.code === '000000') {
this.$message.success('删除成功')
this.getTypeList()
} else {
this.$message.error(response.message || '删除失败')
}
})
}).catch(() => { })
},
//
getCurrentTypeCategoryName() {
//
if (this.typeForm.categoryId) {
const category = this.categoryList.find(item => item.id.toString() === this.typeForm.categoryId.toString())
if (category) {
return category.categoryName
} else {
// typeFormcategoryName使
if (this.typeForm.categoryName) {
return this.typeForm.categoryName
}
}
}
return ''
},
//
loadFullCategoryList() {
getAllFeeCategories().then(response => {
if (response.code === '000000') {
this.categoryList = response.data
}
})
},
//
getCategoryNameById(id) {
const category = this.categoryList.find(item => item.id.toString() === id.toString())
if (category) {
return category.categoryName
}
return ''
},
//
onTypeDialogOpen() {
//
this.loadFullCategoryList()
//
if (!this.typeForm.id && this.selectedCategoryId) {
this.typeForm.categoryId = parseInt(this.selectedCategoryId)
this.$nextTick(() => {
if (this.$refs.typeForm) {
this.$refs.typeForm.validateField('categoryId')
}
})
}
},
//
onCategoryDialogOpen() {
//
this.loadFullCategoryList()
}
}
}
</script>
<style scoped>
.app-container {
padding: 20px;
}
.category-container {
background-color: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
height: calc(100vh - 140px);
display: flex;
flex-direction: column;
}
.category-header {
padding: 15px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.category-header h3 {
margin: 0;
font-size: 16px;
}
.category-search {
padding: 10px 15px;
border-bottom: 1px solid #eee;
}
.category-menu {
flex: 1;
overflow-y: auto;
border-right: none;
}
.category-item {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
}
.category-actions {
display: none;
position: absolute;
right: 10px;
}
.category-item:hover .category-actions {
display: block;
}
.fee-type-container {
background-color: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
padding: 15px;
min-height: calc(100vh - 140px);
}
.fee-type-header {
margin-bottom: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header-title {
display: flex;
align-items: center;
}
.header-title h3 {
margin: 0;
font-size: 16px;
}
.fee-type-search {
margin-bottom: 15px;
}
/* 确保表格不会超出容器 */
.el-table {
width: 100%;
margin-bottom: 15px;
}
/* 分页组件样式 */
.el-pagination {
padding: 10px 0;
text-align: right;
}
</style>

View File

@ -55,12 +55,36 @@
icon="el-icon-plus"
size="mini"
@click="handleAdd">新增楼宇</el-button>
<el-popover
</div>
<!-- 搜索表单 -->
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
<el-form-item label="所属项目">
<el-select v-model="queryParams.projectId" placeholder="请选择所属项目" clearable size="small">
<el-option
v-for="item in projectOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="楼宇名称">
<el-input v-model="queryParams.buildingName" placeholder="请输入楼宇名称" clearable size="small" />
</el-form-item>
<el-form-item label="楼宇编号">
<el-input v-model="queryParams.buildingCode" placeholder="请输入楼宇编号" clearable size="small" />
</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" size="mini" @click="resetQuery">重置</el-button>
<el-popover
placement="bottom"
width="200"
trigger="click"
v-model="columnFilterVisible"
style="float: right; margin: 3px 10px;">
style="float: right; margin: 0 0 0 10px;">
<el-checkbox-group v-model="selectedColumns">
<el-checkbox label="all" @change="handleCheckAllChange">全部</el-checkbox>
<el-divider></el-divider>
@ -87,31 +111,8 @@
<el-checkbox label="availableRentArea">待租面积()</el-checkbox>
<el-checkbox label="availableRoomCount">待租房间数</el-checkbox>
</el-checkbox-group>
<i slot="reference" class="el-icon-s-operation" style="cursor: pointer; font-size: 16px; color: #409EFF;"></i>
<el-button slot="reference" type="primary" size="mini" icon="el-icon-s-operation">列显示</el-button>
</el-popover>
</div>
<!-- 搜索表单 -->
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
<el-form-item label="所属项目">
<el-select v-model="queryParams.projectId" placeholder="请选择所属项目" clearable size="small">
<el-option
v-for="item in projectOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="楼宇名称">
<el-input v-model="queryParams.buildingName" placeholder="请输入楼宇名称" clearable size="small" />
</el-form-item>
<el-form-item label="楼宇编号">
<el-input v-model="queryParams.buildingCode" placeholder="请输入楼宇编号" clearable size="small" />
</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" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
@ -287,7 +288,7 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="选择账户" prop="accountId">
<el-select v-model="form.accountId" placeholder="请选择默认收支账户(必填)" clearable @change="handleAccountChange">
<el-select v-model="form.accountId" placeholder="请选择默认收支账户(必填)" clearable @change="handleAccountChange">
<el-option
v-for="item in accountOptions"
:key="item.id"
@ -791,7 +792,9 @@ export default {
parkingArea: [
{ type: 'number', message: '车位面积必须为数字', trigger: 'blur' }
],
accountId: []
accountId: [
{ required: true, message: '请选择默认收支账户', trigger: 'blur' },
]
},
//
floorRules: {