添加物品列表 及审核页面
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import request from '@/utils/http'
|
||||
|
||||
export class invitemService {
|
||||
// 物品列表
|
||||
static getinvitemlist(params: Api.Common.PaginatingParams) {
|
||||
return request.get<Api.Common.CommonListData>({
|
||||
url: '/admin/inv/item/list',
|
||||
params
|
||||
})
|
||||
}
|
||||
// 添加物品
|
||||
static invitemadd(params: any) {
|
||||
return request.post<Api.Common.AddResponse>({
|
||||
url: '/admin/inv/item/store',
|
||||
params
|
||||
})
|
||||
}
|
||||
// 修改物品
|
||||
static invitemupdate(params: any) {
|
||||
return request.post<Api.Common.AddResponse>({
|
||||
url: '/admin/inv/item/update',
|
||||
params
|
||||
})
|
||||
}
|
||||
// 删除物品
|
||||
static invitemdelete(params: any) {
|
||||
return request.post<Api.Common.AddResponse>({
|
||||
url: '/admin/inv/item/destroy',
|
||||
params
|
||||
})
|
||||
}
|
||||
// 借用审批相关接口
|
||||
static getBorrowList(params: Api.Common.PaginatingParams) {
|
||||
return request.get<Api.Common.CommonListData>({
|
||||
url: '/admin/inv/requisition/list',
|
||||
params
|
||||
})
|
||||
}
|
||||
// 借用申请详情
|
||||
static getBorrowDetail(params: { id: number }) {
|
||||
return request.get<any>({
|
||||
url: '/admin/inv/requisition/show',
|
||||
params
|
||||
})
|
||||
}
|
||||
// 借用审批
|
||||
static borrowApproval(params: any) {
|
||||
return request.post<Api.Common.AddResponse>({
|
||||
url: '/admin/inv/requisition/approval',
|
||||
params
|
||||
})
|
||||
}
|
||||
// 归还审批列表
|
||||
static getReturnList(params: Api.Common.PaginatingParams) {
|
||||
return request.get<Api.Common.CommonListData>({
|
||||
url: '/mobile/inv/requisition/return/list',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 归还申请详情
|
||||
static getReturnDetail(params: { id: number }) {
|
||||
return request.get<any>({
|
||||
url: '/admin/inv/requisition/return/show',
|
||||
params
|
||||
})
|
||||
}
|
||||
// 确认归还
|
||||
static returnconfirm(params: any) {
|
||||
return request.post<Api.Common.AddResponse>({
|
||||
url: '/admin/inv/requisition/return/confirm',
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 582 B |
Binary file not shown.
|
After Width: | Height: | Size: 615 B |
@@ -0,0 +1,810 @@
|
||||
<template>
|
||||
<div class="borrow-approval-page">
|
||||
<div class="borrow-toolbar boxshadow">
|
||||
<div class="toolbar-right">
|
||||
<el-select
|
||||
v-model="searchForm.status"
|
||||
placeholder="选择状态"
|
||||
clearable
|
||||
style="width: 150px; margin-right: 12px"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in statusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="请输入关键字"
|
||||
clearable
|
||||
style="width: 260px"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon class="clickable" @click="handleSearch"><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="boxshadow">
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:currentPage="pagination.currentPage"
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn label="申请单号" prop="order_no" min-width="160" />
|
||||
<ElTableColumn label="申请人" prop="applicant_name" min-width="120" />
|
||||
<ElTableColumn label="借用物品" prop="item" min-width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ getItemNames(scope.row.item) }}</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn
|
||||
label="借用原因"
|
||||
prop="borrow_reason"
|
||||
min-width="180"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<ElTableColumn label="使用开始日期" prop="use_start_date" min-width="120" />
|
||||
<ElTableColumn label="使用结束日期" prop="use_end_date" min-width="120" />
|
||||
<ElTableColumn label="状态" prop="status" min-width="120">
|
||||
<template #default="scope">
|
||||
<ElTag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusLabel(scope.row.status) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="创建时间" prop="created_at" min-width="160" />
|
||||
<ElTableColumn label="操作" fixed="right" min-width="180" align="center">
|
||||
<template #default="scope">
|
||||
<ElRow :gutter="10" justify="center">
|
||||
<el-link
|
||||
type="primary"
|
||||
underline="never"
|
||||
@click="handleViewDetail(scope.row)"
|
||||
style="margin-right: 8px"
|
||||
v-if="hasAuth('borrow_approval@show')"
|
||||
>
|
||||
<i class="iconfont-sys"></i> 查看详情
|
||||
</el-link>
|
||||
<el-link
|
||||
v-if="canApprove(scope.row) && hasAuth('borrow_approval@approve')"
|
||||
type="success"
|
||||
underline="never"
|
||||
@click="handleApprove(scope.row)"
|
||||
>
|
||||
<i class="iconfont-sys"></i> 审批
|
||||
</el-link>
|
||||
</ElRow>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</div>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<el-dialog
|
||||
v-model="detailDialogVisible"
|
||||
title="借用申请详情"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div v-if="detailData.id" class="detail-content">
|
||||
<!-- 基本信息 -->
|
||||
<div class="info-section">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>基本信息</span>
|
||||
</div>
|
||||
<ElRow :gutter="16" class="info-row">
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>申请单号</label>
|
||||
<span>{{ detailData.order_no }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>申请人</label>
|
||||
<span>{{ detailData.applicant_name }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="16" class="info-row">
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>使用开始日期</label>
|
||||
<span>{{ detailData.use_start_date }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>使用结束日期</label>
|
||||
<span>{{ detailData.use_end_date }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="16" class="info-row">
|
||||
<ElCol :span="24">
|
||||
<div class="info-item">
|
||||
<label>借用原因</label>
|
||||
<span>{{ detailData.borrow_reason }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="16" class="info-row" v-if="detailData.remark">
|
||||
<ElCol :span="24">
|
||||
<div class="info-item">
|
||||
<label>备注</label>
|
||||
<span>{{ detailData.remark }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</div>
|
||||
|
||||
<!-- 借用物品 -->
|
||||
<div class="info-section" v-if="detailData.item && detailData.item.length > 0">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>借用物品</span>
|
||||
</div>
|
||||
<div class="item-list">
|
||||
<div
|
||||
v-for="(item, index) in detailData.item"
|
||||
:key="index"
|
||||
class="item-card"
|
||||
>
|
||||
<div class="item-row">
|
||||
<span class="item-label">物品名称</span>
|
||||
<span class="item-value">{{ item.item_name_snap }}</span>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<span class="item-label">申请数量</span>
|
||||
<span class="item-value">{{ item.request_qty }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 附件 -->
|
||||
<div class="info-section" v-if="detailData.attachment && detailData.attachment.length > 0">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>附件</span>
|
||||
</div>
|
||||
<div class="image-attachment-list">
|
||||
<div
|
||||
v-for="(file, index) in detailData.attachment"
|
||||
:key="index"
|
||||
class="image-attachment-item"
|
||||
>
|
||||
<el-image
|
||||
:src="file.file_path_pull"
|
||||
:preview-src-list="getImagePreviewUrls(detailData.attachment)"
|
||||
:initial-index="Number(index)"
|
||||
fit="cover"
|
||||
class="attachment-image"
|
||||
>
|
||||
<template #error>
|
||||
<div class="image-error">
|
||||
<el-icon><Picture /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<div class="image-name">{{ file.file_name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 审批记录 -->
|
||||
<div class="info-section" v-if="detailData.approve && detailData.approve.length > 0">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>审批记录</span>
|
||||
</div>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(record, index) in detailData.approve"
|
||||
:key="index"
|
||||
:timestamp="record.action_at != 0 ? record.action_at : ''"
|
||||
placement="top"
|
||||
>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-header">
|
||||
<span class="approver">{{ record.approver_name }}</span>
|
||||
<ElTag :type="getActionType(record.action)" size="small">
|
||||
{{ getActionLabel(record.action) }}
|
||||
</ElTag>
|
||||
</div>
|
||||
<div v-if="record.comment" class="timeline-comment">
|
||||
{{ record.comment }}
|
||||
</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批弹窗 -->
|
||||
<el-dialog
|
||||
v-model="approvalDialogVisible"
|
||||
title="借用申请审批"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<div v-if="approvalDetail.id" class="approval-content">
|
||||
<!-- 申请详情展示 -->
|
||||
<div class="info-section">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>申请信息</span>
|
||||
</div>
|
||||
<ElRow :gutter="16" class="info-row">
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>申请单号</label>
|
||||
<span>{{ approvalDetail.order_no }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>申请人</label>
|
||||
<span>{{ approvalDetail.applicant_name }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="16" class="info-row">
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>使用开始日期</label>
|
||||
<span>{{ approvalDetail.use_start_date }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>使用结束日期</label>
|
||||
<span>{{ approvalDetail.use_end_date }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="16" class="info-row">
|
||||
<ElCol :span="24">
|
||||
<div class="info-item">
|
||||
<label>借用原因</label>
|
||||
<span>{{ approvalDetail.borrow_reason }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</div>
|
||||
|
||||
<!-- 借用物品 -->
|
||||
<div class="info-section" v-if="approvalDetail.item && approvalDetail.item.length > 0">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>借用物品</span>
|
||||
</div>
|
||||
<div class="item-list">
|
||||
<div v-for="(item, index) in approvalDetail.item" :key="index" class="item-card">
|
||||
<div class="item-row">
|
||||
<span class="item-label">物品名称</span>
|
||||
<span class="item-value">{{ item.item_name_snap }}</span>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<span class="item-label">申请数量</span>
|
||||
<span class="item-value">{{ item.request_qty }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 审批意见 -->
|
||||
<div class="info-section">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>审批意见</span>
|
||||
</div>
|
||||
<ElInput
|
||||
v-model="approvalForm.comment"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入审批意见"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 上传附件 -->
|
||||
<div class="info-section">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>附件(可选)</span>
|
||||
</div>
|
||||
<el-upload
|
||||
v-model:file-list="approvalFileList"
|
||||
:action="uploadAction"
|
||||
:headers="uploadHeaders"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-remove="handleUploadRemove"
|
||||
:limit="5"
|
||||
multiple
|
||||
>
|
||||
<el-button type="primary">点击上传</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持图片、PDF等格式,最多上传5个文件</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton
|
||||
type="danger"
|
||||
plain
|
||||
size="large"
|
||||
class="submit-btn"
|
||||
@click="handleSubmitApprove('reject')"
|
||||
>
|
||||
拒绝
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
size="large"
|
||||
class="submit-btn"
|
||||
@click="handleSubmitApprove('approve')"
|
||||
>
|
||||
同意
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { UploadUserFile, UploadProps } from 'element-plus'
|
||||
import { invitemService } from '@/api/admin/Itemrequisition'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useUploadHeaders } from '@/composables/useUploadHeaders'
|
||||
|
||||
defineOptions({ name: 'BorrowApprovalPage' })
|
||||
|
||||
const { hasAuth } = useAuth()
|
||||
const { uploadHeaders } = useUploadHeaders()
|
||||
|
||||
// 上传地址
|
||||
const uploadAction = `${import.meta.env.VITE_API_URL}/ticket/upload?scene=borrow_approval`
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = [
|
||||
{ label: '待审核', value: 10 },
|
||||
{ label: '审批中', value: 11 },
|
||||
{ label: '已审批(待归还)', value: 12 },
|
||||
{ label: '拒绝', value: 13 }
|
||||
]
|
||||
|
||||
// 获取状态标签颜色
|
||||
const getStatusType = (status: number): 'success' | 'danger' | 'info' | 'warning' | 'primary' => {
|
||||
const typeMap: Record<number, 'success' | 'danger' | 'info' | 'warning' | 'primary'> = {
|
||||
10: 'warning', // 待审核
|
||||
11: 'primary', // 审批中
|
||||
12: 'success', // 已审批(待归还)
|
||||
13: 'danger', // 拒绝
|
||||
14: 'info', // 申请归还
|
||||
15: 'primary', // 归还审核中
|
||||
16: 'success', // 部分归还
|
||||
17: 'success', // 已全部归还
|
||||
18: 'danger' // 已拒绝
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusLabel = (status: number) => {
|
||||
const labelMap: Record<number, string> = {
|
||||
10: '待审核',
|
||||
11: '审批中',
|
||||
12: '已审批(待归还)',
|
||||
13: '拒绝',
|
||||
14: '申请归还',
|
||||
15: '归还审核中',
|
||||
16: '部分归还',
|
||||
17: '已全部归还',
|
||||
18: '已拒绝'
|
||||
}
|
||||
return labelMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 判断是否可以审批
|
||||
const canApprove = (row: any) => {
|
||||
return row.status === 10 || row.status === 11 || row.status === 14 || row.status === 15
|
||||
}
|
||||
|
||||
// 获取审批动作标签颜色
|
||||
const getActionType = (action: number): 'success' | 'danger' | 'info' | 'warning' | 'primary' => {
|
||||
const typeMap: Record<number, 'success' | 'danger' | 'info' | 'warning' | 'primary'> = {
|
||||
0: 'info', // 待审核
|
||||
1: 'success', // 同意
|
||||
2: 'danger' // 拒绝
|
||||
}
|
||||
return typeMap[action] || 'info'
|
||||
}
|
||||
|
||||
// 获取审批动作文本
|
||||
const getActionLabel = (action: number) => {
|
||||
const labelMap: Record<number, string> = {
|
||||
0: '待审核',
|
||||
1: '同意',
|
||||
2: '拒绝'
|
||||
}
|
||||
return labelMap[action] || '未知'
|
||||
}
|
||||
|
||||
// 获取物品名称列表
|
||||
const getItemNames = (items: any[]) => {
|
||||
if (!items || items.length === 0) return '-'
|
||||
return items.map((item) => item.item_name_snap).join('、')
|
||||
}
|
||||
|
||||
// 获取图片预览URL列表
|
||||
const getImagePreviewUrls = (attachments: any[]) => {
|
||||
if (!attachments || attachments.length === 0) return []
|
||||
return attachments.map((item) => item.file_path_pull)
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const tableRef = ref()
|
||||
const tableData = ref<any[]>([])
|
||||
const pagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
keyword: '',
|
||||
status: undefined as number | undefined
|
||||
})
|
||||
|
||||
// 详情弹窗数据
|
||||
const detailDialogVisible = ref(false)
|
||||
const detailData = ref<any>({})
|
||||
|
||||
// 审批弹窗数据
|
||||
const approvalDialogVisible = ref(false)
|
||||
const currentRowId = ref<number | null>(null)
|
||||
const approvalDetail = ref<any>({})
|
||||
const approvalForm = reactive({
|
||||
comment: '',
|
||||
attachment: [] as any[]
|
||||
})
|
||||
const approvalFileList = ref<UploadUserFile[]>([])
|
||||
|
||||
// 上传成功处理
|
||||
const handleUploadSuccess: UploadProps['onSuccess'] = (response) => {
|
||||
if (response.code === 200) {
|
||||
approvalForm.attachment.push({
|
||||
original_name: response.data.original_name || '文件',
|
||||
file_path: response.data.file_path || response.data.file_url,
|
||||
file_url: response.data.file_url
|
||||
})
|
||||
ElMessage.success('文件上传成功')
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文件处理
|
||||
const handleUploadRemove: UploadProps['onRemove'] = (file) => {
|
||||
const index = approvalForm.attachment.findIndex(
|
||||
(item) => item.file_url === file.url || item.original_name === file.name
|
||||
)
|
||||
if (index > -1) {
|
||||
approvalForm.attachment.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
// 获取列表数据
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const { currentPage, pageSize } = pagination
|
||||
const params: any = {
|
||||
current: currentPage,
|
||||
size: pageSize
|
||||
}
|
||||
if (searchForm.keyword) {
|
||||
params.keyword = searchForm.keyword
|
||||
}
|
||||
if (searchForm.status !== undefined) {
|
||||
params.status = searchForm.status
|
||||
}
|
||||
|
||||
const { records, current, size, total } = await invitemService.getBorrowList(params)
|
||||
tableData.value = records || []
|
||||
Object.assign(pagination, { currentPage: current, pageSize: size, total })
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.currentPage = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
const handleSizeChange = (size: number) => {
|
||||
pagination.pageSize = size
|
||||
getList()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (page: number) => {
|
||||
pagination.currentPage = page
|
||||
getList()
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = async (row: any) => {
|
||||
try {
|
||||
const detail = await invitemService.getBorrowDetail({ id: row.id })
|
||||
detailData.value = detail
|
||||
detailDialogVisible.value = true
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 打开审批弹窗 - 获取完整详情
|
||||
const handleApprove = async (row: any) => {
|
||||
try {
|
||||
const detail = await invitemService.getBorrowDetail({ id: row.id })
|
||||
currentRowId.value = row.id
|
||||
approvalDetail.value = detail
|
||||
approvalForm.comment = ''
|
||||
approvalForm.attachment = []
|
||||
approvalFileList.value = []
|
||||
approvalDialogVisible.value = true
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交审批
|
||||
const handleSubmitApprove = async (action: 'approve' | 'reject') => {
|
||||
if (!approvalForm.comment) {
|
||||
ElMessage.warning('请输入审批意见')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { message } = await invitemService.borrowApproval({
|
||||
id: currentRowId.value!,
|
||||
action,
|
||||
comment: approvalForm.comment,
|
||||
attachment: approvalForm.attachment.map((item) => ({
|
||||
original_name: item.original_name,
|
||||
file_path: item.file_path
|
||||
}))
|
||||
})
|
||||
|
||||
ElMessage.success(message)
|
||||
approvalDialogVisible.value = false
|
||||
approvalForm.comment = ''
|
||||
approvalForm.attachment = []
|
||||
currentRowId.value = null
|
||||
approvalDetail.value = {}
|
||||
approvalFileList.value = []
|
||||
getList()
|
||||
} catch (error) {
|
||||
console.error('审批失败:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.borrow-approval-page {
|
||||
background: var(--art-main-bg-color);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.borrow-toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px 16px;
|
||||
background: var(--art-main-bg-color);
|
||||
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// 详情和审批弹窗样式
|
||||
.detail-content,
|
||||
.approval-content {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
margin: 20px 0;
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.title-bar {
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
background: #409eff;
|
||||
margin-right: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-row {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
label {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
// 物品列表
|
||||
.item-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.item-card {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
|
||||
.item-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 图片附件列表
|
||||
.image-attachment-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.image-attachment-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.attachment-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
overflow: hidden;
|
||||
background: #f5f7fa;
|
||||
|
||||
:deep(.el-image__inner) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.image-error {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.image-name {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 审批记录时间轴
|
||||
.timeline-content {
|
||||
.timeline-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.approver {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-comment {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗底部按钮
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
|
||||
.submit-btn {
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,335 @@
|
||||
<template>
|
||||
<div class="customer-page">
|
||||
<div class="customer-toolbar boxshadow">
|
||||
<div class="customer-search-group">
|
||||
<el-form-item label="关键字:" label-width="70px">
|
||||
<el-input style="min-width: 120px" v-model="keyword" placeholder="请输入关键字" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="boxshadow">
|
||||
<ElRow>
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<ElButton @click="showDialog('add')" v-if="hasAuth('item_list@add')" v-ripple
|
||||
>新增</ElButton
|
||||
>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:currentPage="pagination.currentPage"
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn label="ID" prop="id" width="80" />
|
||||
<ElTableColumn label="公司" prop="company_name" />
|
||||
<ElTableColumn label="名称" prop="item_name" />
|
||||
<ElTableColumn label="物品管理员" prop="manage_user_name" />
|
||||
<ElTableColumn label="备注" prop="remark" />
|
||||
<!-- <ElTableColumn label="是否耗材" prop="is_consumable" width="180" >
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.is_consumable ? 'primary' : 'info'">
|
||||
{{ scope.row.is_consumable ? '是' : '否' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn> -->
|
||||
<ElTableColumn label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.status ? 'primary' : 'info'">
|
||||
{{ scope.row.status ? '启用' : '禁用' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="创建时间" prop="created_at" />
|
||||
<ElTableColumn fixed="right" label="操作">
|
||||
<template #default="scope">
|
||||
<ElRow>
|
||||
<el-button
|
||||
link
|
||||
@click="showDialog('edit', scope.row)"
|
||||
v-if="hasAuth('item_list@edit')"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="deleteRole(scope.row)"
|
||||
v-if="hasAuth('item_list@del')"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</ElRow>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</div>
|
||||
<ElDialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增' : '编辑'" align-center>
|
||||
<ElForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
style="padding: 5px 20px"
|
||||
label-width="100px"
|
||||
>
|
||||
<ElFormItem label="公司" prop="company_id">
|
||||
<ElSelect
|
||||
v-model="form.company_id"
|
||||
placeholder="请选择公司"
|
||||
filterable
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in companyList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="名称" prop="item_name">
|
||||
<ElInput v-model="form.item_name" placeholder="请输入名称" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="物品管理员" prop="manage_user_id">
|
||||
<ElSelect
|
||||
v-model="form.manage_user_id"
|
||||
placeholder="请选择负责人"
|
||||
filterable
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in manageUserList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<!-- <ElFormItem label="规格型号" prop="spec_model">
|
||||
<ElInput v-model="form.spec_model" placeholder="请输入规格型号" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="计量单位" prop="unit">
|
||||
<ElInput v-model="form.unit" placeholder="请输入计量单位" />
|
||||
</ElFormItem> -->
|
||||
<!-- <ElFormItem label="是否耗材" prop="is_consumable">
|
||||
<el-radio-group v-model="form.is_consumable">
|
||||
<el-radio :label="1">是</el-radio>
|
||||
<el-radio :label="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</ElFormItem> -->
|
||||
<ElFormItem label="备注" prop="remark">
|
||||
<ElInput v-model="form.remark" placeholder="请输入备注" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="状态" prop="status">
|
||||
<ElSwitch v-model="form.status" :active-value="1" :inactive-value="0" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { invitemService } from '@/api/admin/Itemrequisition'
|
||||
import { manageService } from '@/api/adminApi'
|
||||
import { UserService } from '@/api/usersApi'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
const { hasAuth } = useAuth()
|
||||
defineOptions({ name: 'ItemList' })
|
||||
const keyword = ref<string>('')
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const rules = reactive<FormRules>({
|
||||
company_id: [{ required: true, message: '请选择公司', trigger: 'change' }],
|
||||
item_name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
|
||||
manage_user_id: [{ required: true, message: '请选择负责人', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
company_id: '',
|
||||
manage_user_id: '',
|
||||
remark: '',
|
||||
item_name: '',
|
||||
status: 1,
|
||||
id: ''
|
||||
})
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const companyList = ref<any[]>([])
|
||||
const getcompanyList = async () => {
|
||||
const response = await UserService.getCompanyList()
|
||||
const list = (response as any)?.data ?? (response as any)?.records ?? response
|
||||
companyList.value = Array.isArray(list) ? list : []
|
||||
}
|
||||
const manageUserList = ref<any>()
|
||||
// 获取用户列表
|
||||
const getCompanyUserList = async () => {
|
||||
const response = await manageService.presetusers()
|
||||
manageUserList.value = response || []
|
||||
}
|
||||
// 表格数据
|
||||
const tableData = ref<any[]>([])
|
||||
|
||||
// 表格实例引用
|
||||
const tableRef = ref()
|
||||
|
||||
onMounted(() => {
|
||||
getWorkList()
|
||||
getcompanyList()
|
||||
getCompanyUserList()
|
||||
})
|
||||
watch([keyword], () => {
|
||||
pagination.currentPage = 1
|
||||
getWorkList()
|
||||
})
|
||||
// 获取列表数据
|
||||
const getWorkList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const { currentPage, pageSize } = pagination
|
||||
const params: any = {
|
||||
current: currentPage,
|
||||
size: pageSize
|
||||
}
|
||||
if (keyword.value) {
|
||||
params.keyword = keyword.value
|
||||
}
|
||||
const { records, current, size, total } = await invitemService.getinvitemlist(params)
|
||||
tableData.value = records.map((item: any, index: number) => ({
|
||||
...item
|
||||
}))
|
||||
Object.assign(pagination, { currentPage: current, pageSize: size, total })
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
// 处理表格分页变化
|
||||
const handleSizeChange = (newPageSize: number) => {
|
||||
pagination.pageSize = newPageSize
|
||||
getWorkList()
|
||||
}
|
||||
const handleCurrentChange = (newCurrentPage: number) => {
|
||||
pagination.currentPage = newCurrentPage
|
||||
getWorkList()
|
||||
}
|
||||
|
||||
const dialogType = ref('add')
|
||||
|
||||
const showDialog = (type: string, row?: any) => {
|
||||
dialogVisible.value = true
|
||||
dialogType.value = type
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
// 编辑模式,填充所有字段
|
||||
Object.assign(form, {
|
||||
company_id: row.company_id,
|
||||
item_name: row.item_name,
|
||||
manage_user_id: row.manage_user_id,
|
||||
remark: row.remark,
|
||||
status: row.status,
|
||||
id: row.id
|
||||
})
|
||||
} else {
|
||||
// 新增模式,重置所有字段
|
||||
Object.assign(form, {
|
||||
company_id: '',
|
||||
item_name: '',
|
||||
manage_user_id: '',
|
||||
remark: '',
|
||||
status: 1,
|
||||
id: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
const deleteRole = (row?: any) => {
|
||||
ElMessageBox.confirm('确定删除吗?', '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
}).then(async () => {
|
||||
const { message } = await invitemService.invitemdelete({
|
||||
id: row.id
|
||||
})
|
||||
ElMessage.success(message)
|
||||
getWorkList()
|
||||
})
|
||||
}
|
||||
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
if (dialogType.value === 'add') {
|
||||
const { message } = await invitemService.invitemadd({
|
||||
company_id: form.company_id,
|
||||
item_name: form.item_name,
|
||||
manage_user_id: form.manage_user_id,
|
||||
remark: form.remark,
|
||||
status: form.status
|
||||
})
|
||||
ElMessage.success(message)
|
||||
} else {
|
||||
const { message } = await invitemService.invitemupdate({
|
||||
id: form.id,
|
||||
company_id: form.company_id,
|
||||
item_name: form.item_name,
|
||||
manage_user_id: form.manage_user_id,
|
||||
remark: form.remark,
|
||||
status: form.status
|
||||
})
|
||||
ElMessage.success(message)
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getWorkList()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-table__empty-block) {
|
||||
height: 100% !important;
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.page-content {
|
||||
.svg-icon {
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
overflow: hidden;
|
||||
vertical-align: -8px;
|
||||
fill: currentcolor;
|
||||
}
|
||||
}
|
||||
.customer-page {
|
||||
background: var(--art-main-bg-color);
|
||||
min-height: 100vh;
|
||||
}
|
||||
.customer-search-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,742 @@
|
||||
<template>
|
||||
<div class="return-approval-page">
|
||||
<div class="return-toolbar boxshadow">
|
||||
<div class="toolbar-right">
|
||||
<el-select
|
||||
v-model="searchForm.status"
|
||||
placeholder="选择状态"
|
||||
clearable
|
||||
style="width: 150px; margin-right: 12px"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in statusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="请输入关键字"
|
||||
clearable
|
||||
style="width: 260px"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon class="clickable" @click="handleSearch"><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="boxshadow">
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:currentPage="pagination.currentPage"
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn label="归还单号" prop="order_no" min-width="160" />
|
||||
<ElTableColumn label="申请人" prop="applicant_name" min-width="120" />
|
||||
<ElTableColumn label="归还物品" prop="return_item" min-width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ getReturnItemNames(scope.row.return_item) }}</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="归还时间" prop="return_at" min-width="120" />
|
||||
<ElTableColumn label="状态" prop="status" min-width="120">
|
||||
<template #default="scope">
|
||||
<ElTag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusLabel(scope.row.status) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="创建时间" prop="created_at" min-width="160" />
|
||||
<ElTableColumn label="操作" fixed="right" min-width="180" align="center">
|
||||
<template #default="scope">
|
||||
<ElRow :gutter="10" justify="center">
|
||||
<el-link
|
||||
type="primary"
|
||||
underline="never"
|
||||
@click="handleViewDetail(scope.row)"
|
||||
style="margin-right: 8px"
|
||||
v-if="hasAuth('return_approval@show')"
|
||||
>
|
||||
<i class="iconfont-sys"></i> 查看详情
|
||||
</el-link>
|
||||
<el-link
|
||||
v-if="canApprove(scope.row) && hasAuth('return_approval@approve')"
|
||||
type="success"
|
||||
underline="never"
|
||||
@click="handleApprove(scope.row)"
|
||||
>
|
||||
<i class="iconfont-sys"></i> 审批
|
||||
</el-link>
|
||||
</ElRow>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</div>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<el-dialog
|
||||
v-model="detailDialogVisible"
|
||||
title="归还申请详情"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div v-if="detailData.id" class="detail-content">
|
||||
<!-- 基本信息 -->
|
||||
<div class="info-section">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>基本信息</span>
|
||||
</div>
|
||||
<ElRow :gutter="16" class="info-row">
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>归还单号</label>
|
||||
<span>{{ detailData.order_no }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>申请人</label>
|
||||
<span>{{ detailData.applicant_name }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="16" class="info-row">
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>归还时间</label>
|
||||
<span>{{ detailData.return_at }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>状态</label>
|
||||
<span>{{ getStatusLabel(detailData.status) }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="16" class="info-row" v-if="detailData.apply_remark">
|
||||
<ElCol :span="24">
|
||||
<div class="info-item">
|
||||
<label>申请备注</label>
|
||||
<span>{{ detailData.apply_remark }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="16" class="info-row" v-if="detailData.handle_remark">
|
||||
<ElCol :span="24">
|
||||
<div class="info-item">
|
||||
<label>处理备注</label>
|
||||
<span>{{ detailData.handle_remark }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</div>
|
||||
|
||||
<!-- 归还物品 -->
|
||||
<div class="info-section" v-if="detailData.item_return && detailData.item_return.length > 0">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>归还物品</span>
|
||||
</div>
|
||||
<div class="item-list">
|
||||
<div
|
||||
v-for="(item, index) in detailData.item_return"
|
||||
:key="index"
|
||||
class="item-card"
|
||||
>
|
||||
<div class="item-row">
|
||||
<span class="item-label">物品名称</span>
|
||||
<span class="item-value">{{ item.requisition_item.item_name_snap }}</span>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<span class="item-label">申请数量</span>
|
||||
<span class="item-value">{{ item.requisition_item.request_qty }}</span>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<span class="item-label">已归还数量</span>
|
||||
<span class="item-value">{{ item.requisition_item.returned_qty }}</span>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<span class="item-label">本次归还</span>
|
||||
<span class="item-value">{{ item.return_qty }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 附件 -->
|
||||
<div class="info-section" v-if="detailData.return_attachment && detailData.return_attachment.length > 0">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>附件</span>
|
||||
</div>
|
||||
<div class="image-attachment-list">
|
||||
<div
|
||||
v-for="(file, index) in detailData.return_attachment"
|
||||
:key="index"
|
||||
class="image-attachment-item"
|
||||
>
|
||||
<el-image
|
||||
:src="file.file_path_pull"
|
||||
:preview-src-list="getImagePreviewUrls(detailData.return_attachment)"
|
||||
:initial-index="Number(index)"
|
||||
fit="cover"
|
||||
class="attachment-image"
|
||||
>
|
||||
<template #error>
|
||||
<div class="image-error">
|
||||
<el-icon><Picture /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<div class="image-name">{{ file.file_name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批弹窗 -->
|
||||
<el-dialog
|
||||
v-model="approvalDialogVisible"
|
||||
title="归还申请审批"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<div v-if="approvalDetail.id" class="approval-content">
|
||||
<!-- 申请详情展示 -->
|
||||
<div class="info-section">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>申请信息</span>
|
||||
</div>
|
||||
<ElRow :gutter="16" class="info-row">
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>归还单号</label>
|
||||
<span>{{ approvalDetail.order_no }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>申请人</label>
|
||||
<span>{{ approvalDetail.applicant_name }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="16" class="info-row">
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>归还时间</label>
|
||||
<span>{{ approvalDetail.return_at }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<div class="info-item">
|
||||
<label>申请备注</label>
|
||||
<span>{{ approvalDetail.apply_remark || '-' }}</span>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</div>
|
||||
|
||||
<!-- 归还物品 -->
|
||||
<div class="info-section" v-if="approvalDetail.item_return && approvalDetail.item_return.length > 0">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>归还物品</span>
|
||||
</div>
|
||||
<div class="item-list">
|
||||
<div
|
||||
v-for="(item, index) in approvalDetail.item_return"
|
||||
:key="index"
|
||||
class="item-card"
|
||||
>
|
||||
<div class="item-row">
|
||||
<span class="item-label">物品名称</span>
|
||||
<span class="item-value">{{ item.requisition_item.item_name_snap }}</span>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<span class="item-label">申请数量</span>
|
||||
<span class="item-value">{{ item.requisition_item.request_qty }}</span>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<span class="item-label">已归还数量</span>
|
||||
<span class="item-value">{{ item.requisition_item.returned_qty }}</span>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<span class="item-label">本次归还</span>
|
||||
<span class="item-value">{{ item.return_qty }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 审批意见 -->
|
||||
<div class="info-section">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>审批意见</span>
|
||||
</div>
|
||||
<ElInput
|
||||
v-model="approvalForm.comment"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入审批意见"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 上传附件 -->
|
||||
<div class="info-section">
|
||||
<div class="section-title">
|
||||
<div class="title-bar"></div>
|
||||
<span>附件(可选)</span>
|
||||
</div>
|
||||
<el-upload
|
||||
v-model:file-list="approvalFileList"
|
||||
:action="uploadAction"
|
||||
:headers="uploadHeaders"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-remove="handleUploadRemove"
|
||||
:limit="5"
|
||||
multiple
|
||||
>
|
||||
<el-button type="primary">点击上传</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持图片、PDF等格式,最多上传5个文件</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton
|
||||
type="danger"
|
||||
plain
|
||||
size="large"
|
||||
class="submit-btn"
|
||||
@click="handleSubmitApprove('reject')"
|
||||
>
|
||||
拒绝
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
size="large"
|
||||
class="submit-btn"
|
||||
@click="handleSubmitApprove('approve')"
|
||||
>
|
||||
同意
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { UploadUserFile, UploadProps } from 'element-plus'
|
||||
import { invitemService } from '@/api/admin/Itemrequisition'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useUploadHeaders } from '@/composables/useUploadHeaders'
|
||||
|
||||
defineOptions({ name: 'ReturnApprovalPage' })
|
||||
|
||||
const { hasAuth } = useAuth()
|
||||
const { uploadHeaders } = useUploadHeaders()
|
||||
|
||||
// 上传地址
|
||||
const uploadAction = `${import.meta.env.VITE_API_URL}/ticket/upload?scene=return_approval`
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = [
|
||||
{ label: '待审核', value: 0 },
|
||||
{ label: '已通过', value: 1 },
|
||||
{ label: '未通过', value: 2 }
|
||||
]
|
||||
|
||||
// 获取状态标签颜色
|
||||
const getStatusType = (status: number): 'success' | 'danger' | 'info' | 'warning' | 'primary' => {
|
||||
const typeMap: Record<number, 'success' | 'danger' | 'info' | 'warning' | 'primary'> = {
|
||||
0: 'warning', // 待审核
|
||||
1: 'success', // 已通过
|
||||
2: 'danger' // 未通过
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusLabel = (status: number) => {
|
||||
const labelMap: Record<number, string> = {
|
||||
0: '待审核',
|
||||
1: '已通过',
|
||||
2: '未通过'
|
||||
}
|
||||
return labelMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 判断是否可以审批
|
||||
const canApprove = (row: any) => {
|
||||
return row.status === 0
|
||||
}
|
||||
|
||||
// 获取归还物品名称列表
|
||||
const getReturnItemNames = (returnItems: any[]) => {
|
||||
if (!returnItems || returnItems.length === 0) return '-'
|
||||
const names = returnItems
|
||||
.map((item) => {
|
||||
if (item.item && item.item.length > 0) {
|
||||
return item.item[0].item_name
|
||||
}
|
||||
return ''
|
||||
})
|
||||
.filter(Boolean)
|
||||
return names.join('、')
|
||||
}
|
||||
|
||||
// 获取图片预览URL列表
|
||||
const getImagePreviewUrls = (attachments: any[]) => {
|
||||
if (!attachments || attachments.length === 0) return []
|
||||
return attachments.map((item) => item.file_path_pull)
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const tableRef = ref()
|
||||
const tableData = ref<any[]>([])
|
||||
const pagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
keyword: '',
|
||||
status: undefined as number | undefined
|
||||
})
|
||||
|
||||
// 详情弹窗数据
|
||||
const detailDialogVisible = ref(false)
|
||||
const detailData = ref<any>({})
|
||||
|
||||
// 审批弹窗数据
|
||||
const approvalDialogVisible = ref(false)
|
||||
const currentRowId = ref<number | null>(null)
|
||||
const approvalDetail = ref<any>({})
|
||||
const approvalForm = reactive({
|
||||
comment: '',
|
||||
attachment: [] as any[]
|
||||
})
|
||||
const approvalFileList = ref<UploadUserFile[]>([])
|
||||
|
||||
// 上传成功处理
|
||||
const handleUploadSuccess: UploadProps['onSuccess'] = (response) => {
|
||||
if (response.code === 200) {
|
||||
approvalForm.attachment.push({
|
||||
original_name: response.data.original_name || '文件',
|
||||
file_path: response.data.file_path || response.data.file_url,
|
||||
file_url: response.data.file_url
|
||||
})
|
||||
ElMessage.success('文件上传成功')
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文件处理
|
||||
const handleUploadRemove: UploadProps['onRemove'] = (file) => {
|
||||
const index = approvalForm.attachment.findIndex(
|
||||
(item) => item.file_url === file.url || item.original_name === file.name
|
||||
)
|
||||
if (index > -1) {
|
||||
approvalForm.attachment.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
// 获取列表数据
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const { currentPage, pageSize } = pagination
|
||||
const params: any = {
|
||||
current: currentPage,
|
||||
size: pageSize
|
||||
}
|
||||
if (searchForm.keyword) {
|
||||
params.keyword = searchForm.keyword
|
||||
}
|
||||
if (searchForm.status !== undefined) {
|
||||
params.status = searchForm.status
|
||||
}
|
||||
|
||||
const { records, current, size, total } = await invitemService.getReturnList(params)
|
||||
tableData.value = records || []
|
||||
Object.assign(pagination, { currentPage: current, pageSize: size, total })
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.currentPage = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
const handleSizeChange = (size: number) => {
|
||||
pagination.pageSize = size
|
||||
getList()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (page: number) => {
|
||||
pagination.currentPage = page
|
||||
getList()
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = async (row: any) => {
|
||||
try {
|
||||
const detail = await invitemService.getReturnDetail({ id: row.id })
|
||||
detailData.value = detail
|
||||
detailDialogVisible.value = true
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 打开审批弹窗 - 获取完整详情
|
||||
const handleApprove = async (row: any) => {
|
||||
try {
|
||||
const detail = await invitemService.getReturnDetail({ id: row.id })
|
||||
currentRowId.value = row.id
|
||||
approvalDetail.value = detail
|
||||
approvalForm.comment = ''
|
||||
approvalForm.attachment = []
|
||||
approvalFileList.value = []
|
||||
approvalDialogVisible.value = true
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交审批
|
||||
const handleSubmitApprove = async (action: 'approve' | 'reject') => {
|
||||
if (!approvalForm.comment) {
|
||||
ElMessage.warning('请输入审批意见')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { message } = await invitemService.returnconfirm({
|
||||
id: currentRowId.value!,
|
||||
action,
|
||||
comment: approvalForm.comment,
|
||||
attachment: approvalForm.attachment.map((item) => ({
|
||||
original_name: item.original_name,
|
||||
file_path: item.file_path
|
||||
}))
|
||||
})
|
||||
|
||||
ElMessage.success(message)
|
||||
approvalDialogVisible.value = false
|
||||
approvalForm.comment = ''
|
||||
approvalForm.attachment = []
|
||||
currentRowId.value = null
|
||||
approvalDetail.value = {}
|
||||
approvalFileList.value = []
|
||||
getList()
|
||||
} catch (error) {
|
||||
console.error('审批失败:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.return-approval-page {
|
||||
background: var(--art-main-bg-color);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.return-toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px 16px;
|
||||
background: var(--art-main-bg-color);
|
||||
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// 详情和审批弹窗样式
|
||||
.detail-content,
|
||||
.approval-content {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
margin: 20px 0;
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.title-bar {
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
background: #409eff;
|
||||
margin-right: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-row {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
label {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
// 物品列表
|
||||
.item-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.item-card {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
|
||||
.item-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 图片附件列表
|
||||
.image-attachment-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.image-attachment-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.attachment-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
overflow: hidden;
|
||||
background: #f5f7fa;
|
||||
|
||||
:deep(.el-image__inner) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.image-error {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.image-name {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 弹窗底部按钮
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
|
||||
.submit-btn {
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user