lion 6 months ago
commit fbd3d0e59d

@ -3,7 +3,7 @@ import request from '@/utils/request'
// 绑定/更新物资与运维规则的关系
export function saveStocksItem(data) {
return request({
url: '/api/admin/stocks-item/save',
url: '/api/admin/material-infos/save',
method: 'post',
data
})

@ -42,9 +42,22 @@
<template slot="action" slot-scope="{ row }">
<div style="display: flex; gap: 8px; justify-content: center;">
<Button type="primary" size="small" style="border-radius: 6px;" @click="showInventorySelectModal(row.id)"></Button>
<Button type="primary" size="small" style="border-radius: 6px;" ghost @click="showPlanModal('edit', row)">编辑</Button>
<Button type="error" size="small" style="border-radius: 6px;" ghost @click="deletePlan(row.id)"></Button>
<Button type="info" size="small" style="border-radius: 6px;" ghost @click="viewPlanDetail(row)"></Button>
<Button v-if="row.status === 2" type="success" size="small" style="border-radius: 6px;" ghost @click="showSummaryModal(row)"></Button>
<template v-if="row.status === 2">
<el-dropdown trigger="hover" @command="command => handleMoreCommand(command, row)">
<Button type="info" size="small" style="border-radius: 6px;" ghost>更多<i class="el-icon-arrow-down el-icon--right" /></Button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="edit">编辑</el-dropdown-item>
<el-dropdown-item command="delete">删除</el-dropdown-item>
<el-dropdown-item command="view">查看</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<template v-else>
<Button type="primary" size="small" style="border-radius: 6px;" ghost @click="showPlanModal('edit', row)">编辑</Button>
<Button type="error" size="small" style="border-radius: 6px;" ghost @click="deletePlan(row.id)"></Button>
<Button type="info" size="small" style="border-radius: 6px;" ghost @click="viewPlanDetail(row)"></Button>
</template>
</div>
</template>
</Table>
@ -152,6 +165,12 @@
@on-clear="handleDateClear"
/>
</FormItem>
<FormItem label="盘点类型" prop="type">
<Select v-model="planModal.form.type" style="width: 200px">
<Option :value="1">日常检查</Option>
<Option :value="2">年度盘点</Option>
</Select>
</FormItem>
<FormItem label="备注" prop="remark">
<Input v-model="planModal.form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
</FormItem>
@ -328,7 +347,7 @@
<template slot="photos" slot-scope="{ row }">
<div v-if="row.files && row.files.length" class="photo-list">
<div v-for="(file, index) in row.files.slice(0, 3)" :key="index" class="photo-item">
<img :src="file.url" @click="previewImage(file.url)" />
<img :src="file.url" @click="previewImage(file.url)">
</div>
<div v-if="row.files.length > 3" class="photo-more">
+{{ row.files.length - 3 }}
@ -357,7 +376,7 @@
<!-- 预览弹窗 -->
<div v-if="previewUrl" class="image-preview-modal" @click="closePreview">
<img :src="previewUrl" class="image-preview-large" />
<img :src="previewUrl" class="image-preview-large">
</div>
<!-- Plan Detail Modal -->
@ -411,12 +430,12 @@
<Tag :color="getInventoryStatusColor(row.status)">{{ getInventoryStatusText(row.status) }}</Tag>
</template>
<template slot="photos" slot-scope="{ row }">
<div v-if="row.photos && row.photos.length" class="photo-list">
<div v-for="(photo, index) in row.photos.slice(0, 3)" :key="index" class="photo-item">
<img :src="photo" @click="previewImage(photo)" />
<div v-if="row.files && row.files.length" class="photo-list">
<div v-for="(file, index) in row.files.slice(0, 3)" :key="index" class="photo-item">
<img :src="file.url" @click="previewImage(file.url)">
</div>
<div v-if="row.photos.length > 3" class="photo-more">
+{{ row.photos.length - 3 }}
<div v-if="row.files.length > 3" class="photo-more">
+{{ row.files.length - 3 }}
</div>
</div>
<span v-else>-</span>
@ -439,11 +458,94 @@
<Button @click="planDetailModal.visible = false">关闭</Button>
</template>
</Modal>
<!-- 盘点小结 Modal -->
<Modal
v-model="summaryModal.visible"
title="盘点小结"
width="800"
>
<div>
<div id="print-table">
<div style="text-align: center; font-size: 20px; font-weight: bold; margin-bottom: 12px;">盘点小结</div>
<table class="summary-table">
<tr>
<td>盘点计划名称</td>
<td>{{ summaryModal.data ? summaryModal.data.name : '' }}</td>
</tr>
<tr>
<td>计划开始日期</td>
<td>{{ summaryModal.data ? summaryModal.data.planStart : '' }}</td>
</tr>
<tr>
<td>计划结束日期</td>
<td>{{ summaryModal.data ? summaryModal.data.planEnd : '' }}</td>
</tr>
<tr>
<td>实际开始日期</td>
<td>{{ summaryModal.data ? summaryModal.data.actualStart : '' }}</td>
</tr>
<tr>
<td>实际结束日期</td>
<td>{{ summaryModal.data ? summaryModal.data.actualEnd : '' }}</td>
</tr>
<tr>
<td>计划盘点清单数量</td>
<td>{{ summaryModal.data ? summaryModal.data.planCount : '' }}</td>
</tr>
<tr>
<td>实际盘点清单数量</td>
<td>{{ summaryModal.data ? summaryModal.data.actualCount : '' }}</td>
</tr>
<tr>
<td class="sign-cell">签字区</td>
<td style="text-align: center">
</td>
</tr>
</table>
</div>
<div style="margin: 24px 0 8px 0;">
<span style="font-weight: bold;">签字图片上传</span>
<div v-if="summaryModal.signImage" style="margin-top: 8px;">
<div style="position: relative; display: inline-block;">
<img
:src="summaryModal.signImage"
style="max-width: 200px; max-height: 100px; border: 1px solid #eee; cursor: pointer;"
@click="previewImage(summaryModal.signImage)"
>
<Button
type="error"
size="small"
style="position: absolute; top: 5px; right: 5px;"
@click.stop="summaryModal.signImage = null; summaryModal.signImageId = null"
>
重新上传
</Button>
</div>
</div>
<div v-else style="margin-top: 8px;">
<Upload
:before-upload="beforeSignUpload"
:on-success="handleSignUpload"
:show-upload-list="false"
:action="baseUrl + 'api/admin/upload-file'"
>
<Button type="primary" size="small">上传签字图片</Button>
</Upload>
</div>
</div>
</div>
<template slot="footer">
<Button v-print="'#print-table'"></Button>
<Button type="primary" style="margin-left: 8px;" @click="handleSummarySubmit"></Button>
</template>
</Modal>
</div>
</template>
<script>
import { saveStocktakingPlan, getStocktakingPlanList, deleteStocktakingPlan, getStocktakingPlanLinkList, getStocktakingPlanLinkDetail, getStocktakingHistoryList } from '@/api/system/stocktaking'
import { saveStocktakingPlan, getStocktakingPlanList, deleteStocktakingPlan, getStocktakingPlanLinkList, getStocktakingHistoryList } from '@/api/system/stocktaking'
import { getStorehouseTypeList } from '@/api/system/storehouseType'
import { getparameteritem } from '@/api/system/dictionary'
import { index as getWarehouseList } from '@/api/system/baseForm'
@ -453,6 +555,7 @@ import request from '@/utils/request'
export default {
data() {
return {
baseUrl: process.env.VUE_APP_BASE_API || window.location.origin + '/',
planSearch: {
keyword: '',
dateRange: [],
@ -466,6 +569,17 @@ export default {
planColumns: [
{ title: '计划编号', key: 'no' },
{ title: '计划名称', key: 'name' },
{
title: '盘点类型',
key: 'type',
render: (h, params) => {
const typeMap = {
1: '日常检查',
2: '年度盘点'
}
return h('span', typeMap[params.row.type] || '-')
}
},
{ title: '开始日期', key: 'start_date' },
{ title: '结束日期', key: 'end_date' },
{
@ -541,6 +655,7 @@ export default {
id: '',
name: '',
dateRange: [],
type: 1, //
remark: ''
},
rules: {
@ -660,27 +775,44 @@ export default {
planDetailColumns: [
{
title: '物资名称',
key: 'name',
minWidth: 150
key: 'zichanmingcheng',
minWidth: 150,
render: (h, params) => {
return h('span', params.row.material_info ? params.row.material_info.zichanmingcheng : '-')
}
},
{
title: '物资型号',
key: 'spec',
width: 120
key: 'guigexinghao',
width: 120,
render: (h, params) => {
return h('span', params.row.material_info ? params.row.material_info.guigexinghao : '-')
}
},
{
title: '单位',
key: 'unit',
width: 80
key: 'jiliangdanwei',
width: 80,
render: (h, params) => {
return h('span', params.row.material_info ? params.row.material_info.jiliangdanwei : '-')
}
},
{
title: '盘点人',
key: 'responsible_admin_name',
width: 120,
render: (h, params) => {
return h('span', params.row.responsible_admin ? params.row.responsible_admin.name : '-')
}
},
{
title: '盘点数量',
key: 'checkQuantity',
key: 'check_num',
width: 100
},
{
title: '盘点日期',
key: 'checkDate',
key: 'check_date',
width: 150
},
{
@ -699,7 +831,14 @@ export default {
slot: 'photos',
width: 150
}
]
],
summaryModal: {
visible: false,
data: null,
signImageId: null,
signImage: null,
type: 1 //
}
}
},
watch: {
@ -712,7 +851,6 @@ export default {
this.materialList.forEach((item, index) => {
if (this.materialModal.selectedMaterialIds.has(item.id)) {
this.$refs.materialTable.toggleRowSelection(item, true)
console.log('item.id1111111111111111111111', item.id)
}
})
}
@ -842,6 +980,7 @@ export default {
id: plan.id,
name: plan.name,
dateRange: [plan.start_date, plan.end_date],
type: plan.type,
remark: plan.remark
}
this.planModal.visible = true
@ -850,6 +989,7 @@ export default {
id: '',
name: '',
dateRange: [],
type: 1, //
remark: ''
}
this.planModal.visible = true
@ -881,7 +1021,7 @@ export default {
return
}
const { id, name, dateRange, remark } = this.planModal.form
const { id, name, dateRange,type, remark } = this.planModal.form
const formatDate = (date) => {
const d = new Date(date)
const year = d.getFullYear()
@ -898,6 +1038,7 @@ export default {
formData.append('name', name)
formData.append('start_date', formatDate(dateRange[0]))
formData.append('end_date', formatDate(dateRange[1]))
formData.append('type', type)
if (remark) formData.append('remark', remark)
if (id) formData.append('id', id)
@ -1358,33 +1499,26 @@ export default {
this.planDetailModal.pageIndex = 1
this.loadPlanDetailData()
},
loadPlanDetailData() {
async loadPlanDetailData() {
this.planDetailModal.loading = true
//
setTimeout(() => {
const mockData = []
for (let i = 0; i < 20; i++) {
mockData.push({
id: i + 1,
name: `测试物资${i + 1}`,
spec: `规格${i + 1}`,
unit: '个',
checkQuantity: Math.floor(Math.random() * 100),
checkDate: '2024-03-15',
status: Math.random() > 0.5 ? 1 : 0,
remark: `这是第${i + 1}个物资的备注信息`,
photos: [
'https://picsum.photos/200/200?random=1',
'https://picsum.photos/200/200?random=2',
'https://picsum.photos/200/200?random=3',
'https://picsum.photos/200/200?random=4'
]
})
try {
const formData = new FormData()
formData.append('page', this.planDetailModal.pageIndex)
formData.append('page_size', this.planDetailModal.pageSize)
formData.append('filter[0][key]', 'material_infos_plan_id')
formData.append('filter[0][op]', 'eq')
formData.append('filter[0][value]', this.planDetailModal.data.id)
const res = await getStocktakingPlanLinkList(formData)
if (res && res.list) {
this.planDetailModal.inventoryList = res.list.data
this.planDetailModal.total = res.list.total
}
this.planDetailModal.inventoryList = mockData
this.planDetailModal.total = 100 //
} catch (e) {
this.$Message.error('获取盘点物资列表失败')
} finally {
this.planDetailModal.loading = false
}, 500)
}
},
handlePlanDetailPageChange(page) {
this.planDetailModal.pageIndex = page
@ -1394,6 +1528,66 @@ export default {
this.planDetailModal.pageSize = size
this.planDetailModal.pageIndex = 1
this.loadPlanDetailData()
},
showSummaryModal(row) {
//
const planData = this.planList.find(plan => plan.id === row.id)
if (!planData) {
this.$Message.error('未找到计划数据')
return
}
this.summaryModal.data = {
id: planData.id,
name: planData.name,
planStart: planData.start_date,
planEnd: planData.end_date,
actualStart: planData.act_start_date || '-',
actualEnd: planData.act_end_date || '-',
planCount: planData.chart_total || '-',
actualCount: planData.chart_done || '-'
}
this.summaryModal.signImageId = planData.sign_id || null
this.summaryModal.signImage = planData.sign.url || null
this.summaryModal.visible = true
},
beforeSignUpload(file) {
// /
return true
},
handleSignUpload(response) {
// response.url
this.summaryModal.signImageId = response.id
this.summaryModal.signImage = response.url
},
handleMoreCommand(command, row) {
if (command === 'edit') {
this.showPlanModal('edit', row)
} else if (command === 'delete') {
this.deletePlan(row.id)
} else if (command === 'view') {
this.viewPlanDetail(row)
}
},
async handleSummarySubmit() {
if (!this.summaryModal.signImageId) {
this.$Message.warning('请上传签字图片')
return
}
try {
const params = {
id: this.summaryModal.data.id,
sign_id: this.summaryModal.signImageId,
}
await saveStocktakingPlan(params)
this.$Message.success('提交成功')
this.summaryModal.visible = false
this.searchPlans() //
} catch (error) {
this.$Message.error('提交失败:' + (error.message || '未知错误'))
}
}
}
}
@ -1434,7 +1628,7 @@ export default {
}
.page-title {
font-size: 1.3rem;
font-size: 16px;
font-weight: 600;
color: #333;
margin: 0;
@ -1688,4 +1882,31 @@ export default {
}
}
}
.summary-table {
width: 100%;
border-collapse: collapse;
font-size: 16px;
background: #fff;
margin-bottom: 16px;
}
.summary-table td {
border: 1px solid #333;
padding: 14px 16px;
min-width: 120px;
background: #fff;
color: #222;
font-weight: normal;
}
.summary-table .sign-cell {
height: 80px;
min-height: 80px;
line-height: 80px;
}
.summary-table td:first-child {
background: #fff;
color: #111;
font-weight: bold;
width: 160px;
}
</style>

@ -32,6 +32,7 @@
</div>
<Button type="primary" style="margin-left: 8px;" @click="getList"></Button>
<Button type="primary" style="margin-left: 8px;" @click="resetSearch"></Button>
<Button type="primary" style="margin-left: 8px;" @click="createMaintenance"></Button>
</div>
</slot>
</lx-header>
@ -69,7 +70,7 @@
align="center"
/>
<el-table-column
prop="stocks_item.zichanmingcheng"
prop="material_info.zichanmingcheng"
label="维护物资"
min-width="120"
align="center"
@ -101,7 +102,11 @@
label="负责人"
min-width="100"
align="center"
/>
>
<template slot-scope="scope">
{{ (scope.row.responsible_admin && scope.row.responsible_admin.name) || '-' }}
</template>
</el-table-column>
<el-table-column
label="状态"
min-width="100"
@ -142,138 +147,213 @@
</div>
<!-- 完成维护 Modal -->
<div class="modal" :class="{ show: showModal }">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">完成维护</h3>
<button class="close-button" @click="closeModal">&times;</button>
</div>
<div class="modal-body">
<Form ref="maintenanceForm" :model="form" :rules="formRules" :label-width="120">
<FormItem label="实际维护日期" prop="actual_date">
<DatePicker v-model="form.actual_date" type="date" placeholder="请选择实际维护日期" style="width: 100%" />
</FormItem>
<FormItem label="维护备注" prop="notes">
<Input v-model="form.notes" type="textarea" :rows="4" placeholder="请输入维护备注" />
</FormItem>
<FormItem label="上传图片" prop="photos">
<Upload
ref="upload"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleRemove"
:max-size="2048"
multiple
type="drag"
:action="baseUrl + 'api/admin/upload-file'"
>
<div style="padding: 20px 0">
<Icon type="ios-cloud-upload" size="52" style="color: #3399ff" />
<p>点击或拖拽文件到此处上传</p>
</div>
</Upload>
</FormItem>
<FormItem label="签名" prop="signature">
<div class="signature-pad-wrapper">
<canvas ref="signaturePad" class="signature-canvas" />
<Button size="small" style="margin-top: 8px;" @click="clearSignature"></Button>
</div>
</FormItem>
</Form>
</div>
<div class="modal-footer">
<Button @click="closeModal"></Button>
<Button type="primary" @click="submitMaintenance"></Button>
<Modal
v-model="showModal"
title="完成维护"
width="600"
:mask-closable="false"
>
<Form ref="maintenanceForm" :model="form" :rules="formRules" :label-width="120">
<FormItem label="实际维护日期" prop="actual_date">
<DatePicker v-model="form.actual_date" type="date" placeholder="请选择实际维护日期" style="width: 100%" />
</FormItem>
<FormItem label="维护备注" prop="notes">
<Input v-model="form.notes" type="textarea" :rows="4" placeholder="请输入维护备注" />
</FormItem>
<FormItem label="上传图片" prop="photos">
<Upload
ref="upload"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleRemove"
:max-size="2048"
multiple
type="drag"
:action="baseUrl + 'api/admin/upload-file'"
>
<div style="padding: 20px 0">
<Icon type="ios-cloud-upload" size="52" style="color: #3399ff" />
<p>点击或拖拽文件到此处上传</p>
</div>
</Upload>
</FormItem>
<FormItem label="签名" prop="signature">
<div class="signature-pad-wrapper">
<canvas ref="signaturePad" class="signature-canvas" />
<Button size="small" style="margin-top: 8px;" @click="clearSignature"></Button>
</div>
</div>
</div>
</div>
</FormItem>
</Form>
<template slot="footer">
<Button @click="closeModal"></Button>
<Button type="primary" @click="submitMaintenance"></Button>
</template>
</Modal>
<!-- 查看详情 Modal -->
<div class="modal" :class="{ show: showViewModal }">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">维护记录详情</h3>
<button class="close-button" @click="closeViewModal">&times;</button>
</div>
<div class="modal-body">
<div class="form-group">
<label>记录编号:</label>
<div class="form-value">{{ currentRecord.no }}</div>
</div>
<div class="form-group">
<label>维护物资:</label>
<div class="form-value">{{ currentRecord.stocks_item_name }}</div>
</div>
<div class="form-group">
<label>计划维护日期:</label>
<div class="form-value">{{ currentRecord.planned_maintenance_date }}</div>
</div>
<div class="form-group">
<label>实际维护日期:</label>
<div class="form-value">{{ currentRecord.maintenance_date || '-' }}</div>
</div>
<div class="form-group">
<label>负责人:</label>
<div class="form-value">{{ currentRecord.responsible_admin_name }}</div>
</div>
<div class="form-group">
<label>状态:</label>
<div class="form-value">{{ getStatusText(currentRecord.status) }}</div>
</div>
<div class="form-group">
<label>维护备注:</label>
<div class="form-value">{{ currentRecord.maintenance_notes || '-' }}</div>
</div>
<div class="form-group">
<label>维护照片:</label>
<div v-if="currentRecord.files && currentRecord.files.length" class="photo-gallery">
<img
v-for="(file, idx) in currentRecord.files"
:key="'file-' + idx"
:src="file.url"
class="photo-preview"
style="cursor:pointer;"
alt="维护图片"
@click="previewImage(file.url)"
/>
</div>
<div v-else class="form-value">-</div>
</div>
<div class="form-group">
<label>签名照片:</label>
<div v-if="currentRecord.sign && currentRecord.sign.url" class="photo-gallery">
<img
:src="currentRecord.sign.url"
class="sign-preview"
style="cursor:pointer;"
alt="签名图片"
@click="previewImage(currentRecord.sign.url)"
/>
</div>
<div v-else class="form-value">-</div>
</div>
<Modal
v-model="showViewModal"
title="维护记录详情"
width="900"
:mask-closable="true"
>
<div class="modal-body">
<div class="form-group">
<label>记录编号:</label>
<div class="form-value">{{ currentRecord.no }}</div>
</div>
<div class="form-group">
<label>维护物资:</label>
<div class="form-value">{{ currentRecord.stocks_item_name }}</div>
</div>
<div class="form-group">
<label>计划维护日期:</label>
<div class="form-value">{{ currentRecord.planned_maintenance_date }}</div>
</div>
<div class="form-group">
<label>实际维护日期:</label>
<div class="form-value">{{ currentRecord.maintenance_date || '-' }}</div>
</div>
<div class="form-group">
<label>负责人:</label>
<div class="form-value">{{ currentRecord.responsible_admin_name }}</div>
</div>
<div class="form-group">
<label>状态:</label>
<div class="form-value">{{ getStatusText(currentRecord.status) }}</div>
</div>
<div class="form-group">
<label>维护备注:</label>
<div class="form-value">{{ currentRecord.maintenance_notes || '-' }}</div>
</div>
<div class="form-group">
<label>维护照片:</label>
<div v-if="currentRecord.files && currentRecord.files.length" class="photo-gallery">
<img
v-for="(file, idx) in currentRecord.files"
:key="'file-' + idx"
:src="file.url"
class="photo-preview"
style="cursor:pointer;"
alt="维护图片"
@click="previewImage(file.url)"
>
</div>
<div class="modal-footer" style="justify-content: center;">
<Button @click="closeViewModal"></Button>
<div v-else class="form-value">-</div>
</div>
<div class="form-group">
<label>签名照片:</label>
<div v-if="currentRecord.sign && currentRecord.sign.url" class="photo-gallery">
<img
:src="currentRecord.sign.url"
class="sign-preview"
style="cursor:pointer;"
alt="签名图片"
@click="previewImage(currentRecord.sign.url)"
>
</div>
<div v-else class="form-value">-</div>
</div>
</div>
</div>
<template slot="footer">
<Button @click="closeViewModal"></Button>
</template>
</Modal>
<!-- 预览弹窗 -->
<div v-if="previewUrl" class="image-preview-modal" @click="closePreview">
<img :src="previewUrl" class="image-preview-large" />
<img :src="previewUrl" class="image-preview-large">
</div>
<!-- 新增维护记录 Modal -->
<Modal
v-model="showCreateModal"
title="新增维护记录"
width="500"
:mask-closable="false"
>
<Form ref="createForm" :model="createForm" :rules="createFormRules" :label-width="120">
<FormItem label="维护物资" prop="material_id">
<div class="material-select">
<Input v-model="createForm.material_name" readonly placeholder="请选择维护物资" style="width: calc(100% - 100px);" />
<input v-model="createForm.material_id" type="hidden">
<Button type="primary" style="margin-left: 8px;" @click="openMaterialModal"></Button>
</div>
</FormItem>
<FormItem label="计划维护日期" prop="planned_maintenance_date">
<DatePicker v-model="createForm.planned_maintenance_date" type="date" placeholder="请选择计划维护日期" style="width: 100%" />
</FormItem>
</Form>
<template slot="footer">
<Button @click="closeCreateModal"></Button>
<Button type="primary" @click="submitCreateMaintenance"></Button>
</template>
</Modal>
<!-- 关联物资 Modal -->
<Modal
v-model="showMaterialModal"
title="选择物资"
width="800"
:mask-closable="false"
append-to-body
class="material-modal-top"
>
<div class="associate-content">
<div class="associate-toolbar">
<Input v-model="materialSearch" placeholder="搜索物资名称..." style="width: 250px; margin-right: 10px;" clearable />
<Select v-model="select.storehouses_id" style="width: 120px; margin-right: 10px;" clearable placeholder="仓库类型" @on-change="getWarehouseNames">
<Option v-for="item in warehouseTypes" :key="item.id" :value="item.id">{{ item.name }}</Option>
</Select>
<Select v-model="select.area" style="width: 120px; margin-right: 10px;" clearable placeholder="所在区域" @on-change="getWarehouseNames">
<Option v-for="item in warehouseAreas" :key="item.id" :value="item.value">{{ item.value }}</Option>
</Select>
<Select v-model="materialWarehouseName" style="width: 120px; margin-right: 10px;" clearable placeholder="仓库名称">
<Option v-for="warehouse in warehouseNames" :key="warehouse.value" :value="warehouse.label">{{ warehouse.label }}</Option>
</Select>
<Button type="primary" @click="searchMaterials"></Button>
<Button style="margin-left: 8px;" @click="resetMaterialSearch"></Button>
</div>
<Table :columns="materialColumns" :data="materialList" :height="400" style="margin-top: 15px;">
<template slot="action" slot-scope="{ row }">
<div style="display: flex; gap: 8px; justify-content: center;">
<Button
type="primary"
size="small"
ghost
style="border-radius: 6px;"
@click="selectMaterial(row)"
>
选择
</Button>
</div>
</template>
</Table>
<div class="pagination-container">
<el-pagination
:current-page="materialPageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="materialPageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="materialTotal"
@size-change="handleMaterialPageSizeChange"
@current-change="handleMaterialPageChange"
/>
</div>
</div>
</Modal>
</div>
</template>
<script>
import { Button, Select, Option, DatePicker, Input, Form, FormItem, Upload, Icon } from 'view-design'
import { Button, Select, Option, DatePicker, Input, Form, FormItem, Upload, Icon, Modal, Table } from 'view-design'
import { getOperationList, saveOperation, getOperationDetail } from '@/api/maintenance/maintenance'
import { getStorehouseTypeList } from '@/api/system/storehouseType'
import { getparameteritem } from '@/api/system/dictionary.js'
import { index } from '@/api/system/baseForm.js'
import request from '@/utils/request'
import qs from 'qs'
export default {
components: {
@ -285,7 +365,9 @@ export default {
Form,
FormItem,
Upload,
Icon
Icon,
Modal,
Table
},
data() {
return {
@ -307,12 +389,50 @@ export default {
list: [],
showModal: false,
showViewModal: false,
showCreateModal: false,
currentRecord: {},
form: {
actual_date: '',
notes: '',
photos: []
},
createForm: {
material_id: '',
material_name: '',
planned_maintenance_date: ''
},
createFormRules: {
material_id: [
{
required: true,
message: '请选择维护物资',
trigger: 'change',
validator: (rule, value, callback) => {
if (!value) {
callback(new Error('请选择维护物资'))
} else {
callback()
}
}
}
],
planned_maintenance_date: [
{
required: true,
message: '请选择计划维护日期',
trigger: 'change',
validator: (rule, value, callback) => {
if (!value) {
callback(new Error('请选择计划维护日期'))
} else {
callback()
}
}
}
]
},
materialOptions: [],
adminOptions: [],
formRules: {
actual_date: [
{
@ -363,7 +483,51 @@ export default {
currentUser: '张三',
total: 0,
tableHeight: 550,
previewUrl: ''
previewUrl: '',
showMaterialModal: false,
materialSearch: '',
materialWarehouseName: '',
materialPageSize: 10,
materialPageIndex: 1,
materialTotal: 0,
materialColumns: [
{
title: '物资名称',
key: 'zichanmingcheng',
minWidth: 120
},
{
title: '物资代码',
key: 'wuzibianma',
minWidth: 120
},
{
title: '物资型号',
key: 'guigexinghao',
minWidth: 120
},
{
title: '仓库名称',
key: 'material_info',
minWidth: 120,
render: (h, params) => {
return h('span', params.row.material_info?.suozaicangku || '')
}
},
{
title: '操作',
slot: 'action',
width: 100,
fixed: 'right',
align: 'center',
headerAlign: 'center',
className: 'table-col-action'
}
],
materialList: [],
warehouseTypes: [],
warehouseAreas: [],
warehouseNames: []
}
},
watch: {
@ -377,6 +541,9 @@ export default {
},
created() {
this.getList()
this.getWarehouseTypes()
this.getWarehouseAreas()
this.getWarehouseNames()
},
mounted() {
this.calcTableHeight()
@ -424,6 +591,17 @@ export default {
}
this.getList()
},
createMaintenance() {
this.showCreateModal = true
},
closeCreateModal() {
this.showCreateModal = false
this.createForm = {
material_id: '',
material_name: '',
planned_maintenance_date: ''
}
},
pageChange(e) {
this.select.page = e
this.getList()
@ -461,7 +639,7 @@ export default {
// 2.
this.currentRecord = {
...detail,
stocks_item_name: detail.stocks_item?.zichanmingcheng || '-',
stocks_item_name: detail.material_info?.zichanmingcheng || '-',
responsible_admin_name: detail.responsible_admin?.name || '-'
}
// 3.
@ -500,8 +678,9 @@ export default {
// response.id idresponse.url
this.form.photos.push({ id: response.id, url: response.url })
},
handleUploadError(error, file) {
handleUploadError(err, file) {
//
console.error('上传图片失败:', err)
this.$message.error('上传图片失败')
},
handleRemove(file) {
@ -658,12 +837,160 @@ export default {
},
closePreview() {
this.previewUrl = ''
},
async submitCreateMaintenance() {
try {
const valid = await this.$refs.createForm.validate()
if (!valid) {
return
}
const data = {
material_info_id: this.createForm.material_id,
planned_maintenance_date: this.formatDate(this.createForm.planned_maintenance_date)
}
const res = await saveOperation(data)
if (res) {
this.$Message.success('创建成功')
this.closeCreateModal()
this.getList()
}
} catch (error) {
this.$Message.error('创建失败:' + (error.message || '未知错误'))
}
},
openMaterialModal() {
this.showMaterialModal = true
this.searchMaterials()
},
async searchMaterials() {
try {
const data = {
page_size: this.materialPageSize,
page: this.materialPageIndex,
'show_relation[0]': 'equipmentMaintainConfig',
'show_relation[1]': 'materialInfo.materialstorage',
'materialstorages_cangkumingcheng': this.materialWarehouseName ? this.materialWarehouseName : '',
'materialstorages_suozaiquyu': this.select.area ? this.select.area : '',
'storehouses_id': this.select.storehouses_id ? this.select.storehouses_id : '',
'filter[0][key]': 'zichanmingcheng',
'filter[0][op]': 'like',
'filter[0][value]': this.materialSearch ? this.materialSearch : '',
'filter[1][key]': 'wuzileixing',
'filter[1][op]': 'eq',
'filter[1][value]': '一物一码'
}
const res = await request({
url: '/api/admin/material-infos/index',
method: 'post',
data: qs.stringify(data),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
if (res && res.data) {
this.materialList = res.data.map(material => ({
...material,
selected: false
}))
this.materialTotal = res.total
}
} catch (e) {
console.error('获取物资列表失败:', e)
this.$Message.error('获取物资列表失败')
}
},
selectMaterial(row) {
this.createForm.material_id = row.id
this.createForm.material_name = row.zichanmingcheng
this.showMaterialModal = false
},
handleMaterialSubmit() {
this.showMaterialModal = false
},
handleMaterialCancel() {
this.showMaterialModal = false
},
handleMaterialPageChange(page) {
this.materialPageIndex = page
this.searchMaterials()
},
handleMaterialPageSizeChange(size) {
this.materialPageSize = size
this.materialPageIndex = 1
this.searchMaterials()
},
resetMaterialSearch() {
this.materialSearch = ''
this.select.storehouses_id = ''
this.select.area = ''
this.materialWarehouseName = ''
this.materialPageIndex = 1
this.searchMaterials()
},
async getWarehouseTypes() {
try {
const res = await getStorehouseTypeList({
page: 1,
page_size: 999
})
if (res && res.data) {
this.warehouseTypes = res.data
}
} catch (e) {
console.error('获取仓库类型列表失败:', e)
this.$Message.error('获取仓库类型列表失败')
}
},
async getWarehouseAreas() {
try {
const res = await getparameteritem('area')
if (res && res.detail) {
this.warehouseAreas = res.detail
}
} catch (e) {
console.error('获取仓库区域列表失败:', e)
this.$Message.error('获取仓库区域列表失败')
}
},
async getWarehouseNames() {
try {
const res = await index({
page_size: this.select.page_size,
page: this.select.page,
table_name: 'materialstorages',
filter: [{
key: 'cangkumingcheng',
op: 'like',
value: this.select.keyword ? this.select.keyword : ''
}, {
key: 'quyu_id',
op: 'eq',
value: this.select.area ? this.select.area : ''
}, {
key: 'storehouses_id',
op: 'eq',
value: this.select.storehouses_id ? this.select.storehouses_id : ''
}]
})
if (res && res.data) {
this.warehouseNames = res.data.map(warehouse => ({
value: warehouse.id,
label: warehouse.cangkumingcheng
}))
}
} catch (e) {
console.error('获取仓库名称列表失败:', e)
this.$Message.error('获取仓库名称列表失败')
}
}
}
}
</script>
<style scoped>
<style lang="scss" scoped>
.table-page-container {
padding: 20px;
background-color: #f0f2f5;
@ -873,12 +1200,10 @@ export default {
}
.modal-body {
background: #fcfcfd;
padding: 20px 15px 15px 15px;
border-radius: 0 0 12px 12px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px 32px;
padding: 0;
}
.form-group {
@ -900,95 +1225,45 @@ export default {
font-weight: 500;
}
.form-group input[type="date"],
.form-group textarea,
.form-group input[type="file"] {
display: block;
width: 100%;
padding: 5px 7.5px;
font-size: 16px;
font-weight: 400;
.form-value {
color: #333;
font-size: 14px;
line-height: 1.5;
color: #495057;
background-color: #fff !important;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: 0.25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
.form-group textarea {
resize: vertical;
word-break: break-word;
}
.form-group input[type="file"] {
padding: 3px 7.5px;
}
.form-text {
font-size: 16px;
color: #6c757d;
.photo-gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px;
margin-top: 8px;
}
.signature-area {
width: 100%;
min-width: 0;
}
.signature-pad-wrapper {
width: 100%;
min-width: 0;
display: flex;
flex-direction: column;
align-items: flex-start;
}
.signature-canvas {
border: 1px dashed #ced4da;
background: #fff;
border-radius: 4px;
.photo-preview {
width: 100%;
height: 120px;
cursor: crosshair;
display: block;
box-sizing: border-box;
margin: 0;
padding: 0;
min-width: 0;
}
.form-value {
color: #222;
font-size: 12px;
font-weight: 500;
background: transparent;
border: none;
min-height: 32px;
padding: 0;
object-fit: cover;
border-radius: 4px;
border: 1px solid #eaeaea;
cursor: pointer;
transition: transform 0.2s;
}
.photo-gallery {
display: flex;
flex-wrap: wrap;
gap: 10px;
background: #f8f9fa;
border-radius: 6px;
padding: 8px;
margin-top: 4px;
.photo-preview:hover {
transform: scale(1.05);
}
.photo-preview {
width: 64px;
height: 64px;
object-fit: cover;
.sign-preview {
max-width: 200px;
margin-top: 8px;
border-radius: 4px;
border: 1.5px solid #eaeaea;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
cursor: pointer;
transition: transform 0.15s;
border: 1px solid #eaeaea;
}
.photo-preview:hover {
transform: scale(1.08);
box-shadow: 0 4px 16px rgba(0,0,0,0.13);
@media (max-width: 900px) {
.modal-body {
grid-template-columns: 1fr;
}
}
.pagination-container {
@ -1040,28 +1315,6 @@ export default {
}
}
@media (max-width: 900px) {
.modal-dialog {
max-width: 98vw;
}
.modal-body {
padding: 10px 5px;
grid-template-columns: 1fr;
}
}
.modal-footer {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
padding: 7.5px;
border-top: 1px solid #dee2e6;
border-bottom-right-radius: 12px;
border-bottom-left-radius: 12px;
gap: 0.5rem;
}
.image-preview-modal {
position: fixed;
z-index: 2000;
@ -1079,19 +1332,6 @@ export default {
background: #fff;
}
.sign-preview {
width: 100%;
max-width: 100%;
height: auto;
object-fit: contain;
border-radius: 4px;
border: 1.5px solid #eaeaea;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
margin-top: 4px;
cursor: pointer;
background: #fff;
}
/* 分割线 */
hr {
border: none;
@ -1127,4 +1367,49 @@ hr {
border-top: 1.5px solid #e0e3e8;
margin: 16px 0;
}
.material-select {
display: flex;
align-items: center;
}
.associate-content {
padding: 16px;
}
.associate-toolbar {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
:deep(.material-modal-top .ivu-modal) {
z-index: 9999 !important;
}
:deep(.material-modal-top .ivu-modal-mask) {
z-index: 9998 !important;
}
:deep(.ivu-modal-body) {
padding: 20px;
max-height: 70vh;
overflow-y: auto;
}
:deep(.ivu-modal-header) {
padding: 16px;
border-bottom: 1px solid #eaeaea;
}
:deep(.ivu-modal-header-inner) {
font-size: 16px;
font-weight: 600;
color: #222;
}
:deep(.ivu-modal-footer) {
padding: 16px;
border-top: 1px solid #eaeaea;
text-align: center;
}
</style>

@ -190,7 +190,7 @@
</template>
<script>
import { Button, Modal, Form, FormItem, Input, Select, Option, InputNumber, Table, Checkbox, Page } from 'view-design'
import { Button, Modal, Form, FormItem, Input, Select, Option, InputNumber, Table } from 'view-design'
import { getStorehouseTypeList } from '@/api/system/storehouseType'
import { getparameteritem } from '@/api/system/dictionary.js'
import { index } from '@/api/system/baseForm.js'
@ -209,9 +209,7 @@ export default {
Select,
Option,
InputNumber,
Table,
Checkbox,
Page
Table
},
data() {
return {
@ -242,11 +240,11 @@ export default {
table: [
{ label: '序号', type: 'index', width: 60 },
{ label: '规则名称', prop: 'name', minWidth: 160 },
{ label: '重复周期', prop: 'month_frequency', minWidth: 120, formatter: row => this.cycleOptions.find(opt => opt.value == row.month_frequency)?.label || row.month_frequency },
{ label: '起始日期设定', prop: 'start_standard', minWidth: 160, formatter: row => this.startStandardOptions.find(opt => opt.value == row.start_standard)?.label || row.start_standard },
{ label: '重复周期', prop: 'month_frequency', minWidth: 120, formatter: row => this.cycleOptions.find(opt => opt.value === row.month_frequency)?.label || row.month_frequency },
{ label: '起始日期设定', prop: 'start_standard', minWidth: 160, formatter: row => this.startStandardOptions.find(opt => opt.value === row.start_standard)?.label || row.start_standard },
{ label: '生成计划时机(天)', prop: 'advance_days', minWidth: 120 },
{ label: '已用于物资数', prop: 'stocks_items_count', minWidth: 120 },
{ label: '未完成时下次计划', prop: 'next_plan_type', minWidth: 160, formatter: row => this.nextPlanOptions.find(opt => opt.value == row.next_plan_type)?.label || row.next_plan_type }
{ label: '未完成时下次计划', prop: 'next_plan_type', minWidth: 160, formatter: row => this.nextPlanOptions.find(opt => opt.value === row.next_plan_type)?.label || row.next_plan_type }
],
tableHeight: 550,
showModal: false,
@ -514,7 +512,7 @@ export default {
'filter[1][value]': '一物一码'
}
const res = await request({
url: '/api/admin/stocks-item/index',
url: '/api/admin/material-infos/index',
method: 'post',
data: qs.stringify(data),
headers: {
@ -543,7 +541,7 @@ export default {
id: row.id,
equipment_maintain_config_id: null
})
this.$message.success(`已解除绑定物资"${row.material_info.suozaicangku}"`)
this.$message.success('已解除绑定物资')
this.searchMaterialsPost()
} catch (e) {
this.$message.error('解除绑定失败')
@ -555,10 +553,10 @@ export default {
id: row.id,
equipment_maintain_config_id: this.currentRule.id
})
this.$message.success(`已成功绑定物资"${row.material_info.suozaicangku}"`)
this.$message.success('已成功绑定物资')
this.searchMaterialsPost()
} catch (e) {
this.$message.error('绑定失败')
this.$message.error('绑定失败:' + e.message)
}
}
},
@ -654,12 +652,12 @@ export default {
clearArea(e) {
this.select.area = e || ''
this.getWarehouseNames()
this.filterWarehouses()
},
clearType(e) {
this.select.storehouses_id = e || ''
this.getWarehouseNames()
this.filterWarehouses()
},
async getAllWarehouses() {
try {
@ -703,15 +701,6 @@ export default {
}))
},
clearArea(e) {
this.select.area = e || ''
this.filterWarehouses()
},
clearType(e) {
this.select.storehouses_id = e || ''
this.filterWarehouses()
},
async handleCustomSubmit() {
this.$refs.form.validate(async(valid) => {
if (valid) {

Loading…
Cancel
Save