Merge branch 'devlopment' of http://pms.dtyx.net:3000/wuxueyu/Industrial-image-management-system---web into devlopment
This commit is contained in:
commit
b504f7f755
|
@ -100,9 +100,12 @@ export const equipmentProcurementApi = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 确认收货
|
* 确认收货(扩展版,支持完整设备数据)
|
||||||
*/
|
*/
|
||||||
receiveGoods: (equipmentId: string, data: ReceiptRequest) => {
|
receiveGoods: (equipmentId: string, data: ReceiptRequest) => {
|
||||||
|
console.log('📦 收货API被调用,设备ID:', equipmentId)
|
||||||
|
console.log('📦 收货数据:', data)
|
||||||
|
|
||||||
return http.post<ApiRes<null>>(`/equipment/procurement/receipt/${equipmentId}`, data)
|
return http.post<ApiRes<null>>(`/equipment/procurement/receipt/${equipmentId}`, data)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -410,33 +410,63 @@ export interface EquipmentApprovalResp {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 收货请求参数
|
* 收货请求参数(扩展版,包含完整设备信息)
|
||||||
*/
|
*/
|
||||||
export interface ReceiptRequest {
|
export interface ReceiptRequest {
|
||||||
/** 收货时间 */
|
// 收货特有信息
|
||||||
receiptTime: string
|
receiptTime: string
|
||||||
/** 收货人 */
|
|
||||||
receiptPerson: string
|
receiptPerson: string
|
||||||
/** 收货数量 */
|
|
||||||
receiptQuantity: number
|
receiptQuantity: number
|
||||||
/** 收货备注 */
|
|
||||||
receiptRemark?: string
|
receiptRemark?: string
|
||||||
/** 外观检查结果 */
|
|
||||||
appearanceCheck: string
|
appearanceCheck: string
|
||||||
/** 功能测试结果 */
|
|
||||||
functionTest: string
|
functionTest: string
|
||||||
/** 包装完整性 */
|
|
||||||
packageIntegrity: string
|
packageIntegrity: string
|
||||||
/** 配件完整性 */
|
|
||||||
accessoryIntegrity: string
|
accessoryIntegrity: string
|
||||||
/** 检查结果 */
|
|
||||||
checkResult: 'PASS' | 'FAIL' | 'CONDITIONAL'
|
checkResult: 'PASS' | 'FAIL' | 'CONDITIONAL'
|
||||||
/** 检查备注 */
|
|
||||||
checkRemark?: string
|
checkRemark?: string
|
||||||
/** 入库位置 */
|
|
||||||
storageLocation: string
|
storageLocation: string
|
||||||
/** 库管员 */
|
|
||||||
storageManager: string
|
storageManager: string
|
||||||
|
|
||||||
|
// 设备基本信息(从采购数据继承)
|
||||||
|
equipmentName?: string
|
||||||
|
equipmentModel?: string
|
||||||
|
equipmentType?: string
|
||||||
|
equipmentSn?: string
|
||||||
|
brand?: string
|
||||||
|
specification?: string
|
||||||
|
assetCode?: string
|
||||||
|
|
||||||
|
// 采购信息(从采购数据继承)
|
||||||
|
purchaseOrder?: string
|
||||||
|
supplierName?: string
|
||||||
|
purchasePrice?: number
|
||||||
|
purchaseTime?: string
|
||||||
|
quantity?: number
|
||||||
|
unitPrice?: number
|
||||||
|
totalPrice?: number
|
||||||
|
|
||||||
|
// 入库信息
|
||||||
|
inStockTime?: string
|
||||||
|
physicalLocation?: string
|
||||||
|
locationStatus?: string
|
||||||
|
responsiblePerson?: string
|
||||||
|
inventoryBarcode?: string
|
||||||
|
|
||||||
|
// 状态信息
|
||||||
|
equipmentStatus?: string
|
||||||
|
useStatus?: string
|
||||||
|
healthStatus?: string
|
||||||
|
receiptStatus?: string
|
||||||
|
|
||||||
|
// 其他管理信息
|
||||||
|
depreciationMethod?: string
|
||||||
|
depreciationYears?: number
|
||||||
|
salvageValue?: number
|
||||||
|
currentNetValue?: number
|
||||||
|
|
||||||
|
// 系统字段
|
||||||
|
createTime?: string
|
||||||
|
updateTime?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,6 +18,8 @@ export interface ProjectResp {
|
||||||
projectCategory?: string // 项目类型/服务
|
projectCategory?: string // 项目类型/服务
|
||||||
projectManagerId?: string // 项目经理ID
|
projectManagerId?: string // 项目经理ID
|
||||||
projectManagerName?: string // 项目经理姓名
|
projectManagerName?: string // 项目经理姓名
|
||||||
|
projectOrigin?: string // 项目来源
|
||||||
|
|
||||||
projectStaff?: string[] // 施工人员
|
projectStaff?: string[] // 施工人员
|
||||||
startDate?: string // 开始日期
|
startDate?: string // 开始日期
|
||||||
endDate?: string // 结束日期
|
endDate?: string // 结束日期
|
||||||
|
@ -28,10 +30,10 @@ export interface ProjectResp {
|
||||||
coverUrl?: string // 封面URL
|
coverUrl?: string // 封面URL
|
||||||
createDt?: Date
|
createDt?: Date
|
||||||
updateDt?: Date
|
updateDt?: Date
|
||||||
|
|
||||||
// 为了保持向后兼容,添加一些别名字段
|
// 为了保持向后兼容,添加一些别名字段
|
||||||
id?: string // projectId的别名
|
id?: string // projectId的别名
|
||||||
fieldName?: string // farmName的别名
|
fieldName?: string // farmName的别名
|
||||||
fieldLocation?: string // farmAddress的别名
|
fieldLocation?: string // farmAddress的别名
|
||||||
commissionUnit?: string // client的别名
|
commissionUnit?: string // client的别名
|
||||||
commissionContact?: string // clientContact的别名
|
commissionContact?: string // clientContact的别名
|
||||||
|
@ -85,7 +87,7 @@ export interface TaskQuery {
|
||||||
status?: string
|
status?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TaskPageQuery extends TaskQuery, PageQuery {}
|
export interface TaskPageQuery extends TaskQuery, PageQuery {}
|
||||||
|
|
||||||
// ==================== 人员调度相关类型 ====================
|
// ==================== 人员调度相关类型 ====================
|
||||||
|
|
||||||
|
@ -339,4 +341,4 @@ export interface PageRes<T> {
|
||||||
total: number
|
total: number
|
||||||
page: number
|
page: number
|
||||||
pageSize: number
|
pageSize: number
|
||||||
}
|
}
|
|
@ -70,6 +70,6 @@ declare global {
|
||||||
// for type re-export
|
// for type re-export
|
||||||
declare global {
|
declare global {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||||
import('vue')
|
import('vue')
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,70 @@ export {}
|
||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
ApprovalAssistant: typeof import('./../components/ApprovalAssistant/index.vue')['default']
|
||||||
|
ApprovalMessageItem: typeof import('./../components/NotificationCenter/ApprovalMessageItem.vue')['default']
|
||||||
|
Avatar: typeof import('./../components/Avatar/index.vue')['default']
|
||||||
|
Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default']
|
||||||
|
CellCopy: typeof import('./../components/CellCopy/index.vue')['default']
|
||||||
|
Chart: typeof import('./../components/Chart/index.vue')['default']
|
||||||
|
CircularProgress: typeof import('./../components/CircularProgress/index.vue')['default']
|
||||||
|
ColumnSetting: typeof import('./../components/GiTable/src/components/ColumnSetting.vue')['default']
|
||||||
|
CronForm: typeof import('./../components/GenCron/CronForm/index.vue')['default']
|
||||||
|
CronModal: typeof import('./../components/GenCron/CronModal/index.vue')['default']
|
||||||
|
DateRangePicker: typeof import('./../components/DateRangePicker/index.vue')['default']
|
||||||
|
DayForm: typeof import('./../components/GenCron/CronForm/component/day-form.vue')['default']
|
||||||
|
FilePreview: typeof import('./../components/FilePreview/index.vue')['default']
|
||||||
|
GiCellAvatar: typeof import('./../components/GiCell/GiCellAvatar.vue')['default']
|
||||||
|
GiCellGender: typeof import('./../components/GiCell/GiCellGender.vue')['default']
|
||||||
|
GiCellStatus: typeof import('./../components/GiCell/GiCellStatus.vue')['default']
|
||||||
|
GiCellTag: typeof import('./../components/GiCell/GiCellTag.vue')['default']
|
||||||
|
GiCellTags: typeof import('./../components/GiCell/GiCellTags.vue')['default']
|
||||||
|
GiCodeView: typeof import('./../components/GiCodeView/index.vue')['default']
|
||||||
|
GiDot: typeof import('./../components/GiDot/index.tsx')['default']
|
||||||
|
GiEditTable: typeof import('./../components/GiEditTable/GiEditTable.vue')['default']
|
||||||
|
GiFooter: typeof import('./../components/GiFooter/index.vue')['default']
|
||||||
|
GiForm: typeof import('./../components/GiForm/src/GiForm.vue')['default']
|
||||||
|
GiIconBox: typeof import('./../components/GiIconBox/index.vue')['default']
|
||||||
|
GiIconSelector: typeof import('./../components/GiIconSelector/index.vue')['default']
|
||||||
|
GiIframe: typeof import('./../components/GiIframe/index.vue')['default']
|
||||||
|
GiOption: typeof import('./../components/GiOption/index.vue')['default']
|
||||||
|
GiOptionItem: typeof import('./../components/GiOptionItem/index.vue')['default']
|
||||||
|
GiPageLayout: typeof import('./../components/GiPageLayout/index.vue')['default']
|
||||||
|
GiSpace: typeof import('./../components/GiSpace/index.vue')['default']
|
||||||
|
GiSplitButton: typeof import('./../components/GiSplitButton/index.vue')['default']
|
||||||
|
GiSplitPane: typeof import('./../components/GiSplitPane/index.vue')['default']
|
||||||
|
GiSplitPaneFlexibleBox: typeof import('./../components/GiSplitPane/components/GiSplitPaneFlexibleBox.vue')['default']
|
||||||
|
GiSvgIcon: typeof import('./../components/GiSvgIcon/index.vue')['default']
|
||||||
|
GiTable: typeof import('./../components/GiTable/src/GiTable.vue')['default']
|
||||||
|
GiTag: typeof import('./../components/GiTag/index.tsx')['default']
|
||||||
|
GiThemeBtn: typeof import('./../components/GiThemeBtn/index.vue')['default']
|
||||||
|
HourForm: typeof import('./../components/GenCron/CronForm/component/hour-form.vue')['default']
|
||||||
|
Icon403: typeof import('./../components/icons/Icon403.vue')['default']
|
||||||
|
Icon404: typeof import('./../components/icons/Icon404.vue')['default']
|
||||||
|
Icon500: typeof import('./../components/icons/Icon500.vue')['default']
|
||||||
|
IconBorders: typeof import('./../components/icons/IconBorders.vue')['default']
|
||||||
|
IconTableSize: typeof import('./../components/icons/IconTableSize.vue')['default']
|
||||||
|
IconTreeAdd: typeof import('./../components/icons/IconTreeAdd.vue')['default']
|
||||||
|
IconTreeReduce: typeof import('./../components/icons/IconTreeReduce.vue')['default']
|
||||||
|
ImageImport: typeof import('./../components/ImageImport/index.vue')['default']
|
||||||
|
ImageImportWizard: typeof import('./../components/ImageImportWizard/index.vue')['default']
|
||||||
|
IndustrialImageList: typeof import('./../components/IndustrialImageList/index.vue')['default']
|
||||||
|
JsonPretty: typeof import('./../components/JsonPretty/index.vue')['default']
|
||||||
|
MinuteForm: typeof import('./../components/GenCron/CronForm/component/minute-form.vue')['default']
|
||||||
|
MonthForm: typeof import('./../components/GenCron/CronForm/component/month-form.vue')['default']
|
||||||
|
NotificationCenter: typeof import('./../components/NotificationCenter/index.vue')['default']
|
||||||
|
ParentView: typeof import('./../components/ParentView/index.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
SecondForm: typeof import('./../components/GenCron/CronForm/component/second-form.vue')['default']
|
||||||
|
SplitPanel: typeof import('./../components/SplitPanel/index.vue')['default']
|
||||||
|
TextCopy: typeof import('./../components/TextCopy/index.vue')['default']
|
||||||
|
TurbineGrid: typeof import('./../components/TurbineGrid/index.vue')['default']
|
||||||
|
UserSelect: typeof import('./../components/UserSelect/index.vue')['default']
|
||||||
|
Verify: typeof import('./../components/Verify/index.vue')['default']
|
||||||
|
VerifyPoints: typeof import('./../components/Verify/Verify/VerifyPoints.vue')['default']
|
||||||
|
VerifySlide: typeof import('./../components/Verify/Verify/VerifySlide.vue')['default']
|
||||||
|
WeekForm: typeof import('./../components/GenCron/CronForm/component/week-form.vue')['default']
|
||||||
|
YearForm: typeof import('./../components/GenCron/CronForm/component/year-form.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,105 @@
|
||||||
|
<template>
|
||||||
|
<div class="file-header">
|
||||||
|
<div class="breadcrumbs">
|
||||||
|
<a-breadcrumb>
|
||||||
|
<a-breadcrumb-item
|
||||||
|
v-for="(item, index) in breadcrumbPath"
|
||||||
|
:key="index"
|
||||||
|
:class="{ 'clickable': index < breadcrumbPath.length - 1 }"
|
||||||
|
@click="handleBreadcrumbClick(index)"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</a-breadcrumb-item>
|
||||||
|
</a-breadcrumb>
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
shape="circle"
|
||||||
|
@click="handleRefresh"
|
||||||
|
:loading="refreshing"
|
||||||
|
tooltip="刷新数据"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<icon-refresh :spin="refreshing" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<a-space>
|
||||||
|
<a-button type="outline" @click="handleUpload">
|
||||||
|
<template #icon><icon-upload /></template>
|
||||||
|
上传文件
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" @click="handleCreateFolder">
|
||||||
|
<template #icon><icon-plus /></template>
|
||||||
|
新建文件夹
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { IconRefresh, IconUpload, IconPlus } from '@arco-design/web-vue/es/icon';
|
||||||
|
|
||||||
|
// 定义props
|
||||||
|
const props = defineProps({
|
||||||
|
breadcrumbPath: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
refreshing: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义emit事件
|
||||||
|
const emit = defineEmits(['breadcrumb-click', 'refresh', 'upload', 'create-folder']);
|
||||||
|
|
||||||
|
// 处理面包屑点击
|
||||||
|
const handleBreadcrumbClick = (index) => {
|
||||||
|
emit('breadcrumb-click', index);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理刷新
|
||||||
|
const handleRefresh = () => {
|
||||||
|
emit('refresh');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理上传文件
|
||||||
|
const handleUpload = () => {
|
||||||
|
emit('upload');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理新建文件夹
|
||||||
|
const handleCreateFolder = () => {
|
||||||
|
emit('create-folder');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.file-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 24px;
|
||||||
|
height: 64px;
|
||||||
|
background: var(--color-bg-1);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumbs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-primary);
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable:hover {
|
||||||
|
color: var(--color-primary-light-1);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,669 @@
|
||||||
|
<template>
|
||||||
|
<div class="file-list-container">
|
||||||
|
<!-- 文件列表标题和搜索框在同一行 -->
|
||||||
|
<div v-if="currentFolderId" class="file-header-container">
|
||||||
|
<div class="file-title">
|
||||||
|
<span class="file-list-title">文件列表 ({{ files.length }})</span>
|
||||||
|
</div>
|
||||||
|
<div class="file-search-container">
|
||||||
|
<a-input-search
|
||||||
|
v-model="fileSearchKeyword"
|
||||||
|
placeholder="搜索文件名..."
|
||||||
|
class="file-search-input"
|
||||||
|
@search="handleFileSearch"
|
||||||
|
@input="handleFileSearchInput"
|
||||||
|
@clear="handleFileSearchClear"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-divider size="small" v-if="currentFolderId" />
|
||||||
|
|
||||||
|
<template v-if="!currentFolderId">
|
||||||
|
<div class="initial-state">
|
||||||
|
<icon-folder-add class="initial-icon" />
|
||||||
|
<div class="initial-text">请从左侧选择一个文件夹</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 文件列表加载状态 -->
|
||||||
|
<a-skeleton
|
||||||
|
:loading="loading && currentFolderId"
|
||||||
|
:rows="8"
|
||||||
|
v-if="loading && currentFolderId"
|
||||||
|
animation="pulse"
|
||||||
|
>
|
||||||
|
<template #skeleton>
|
||||||
|
<a-row class="table-data-row" v-for="i in 8" :key="i">
|
||||||
|
<a-col :span="10" class="table-column name-column">
|
||||||
|
<div class="file-main">
|
||||||
|
<div class="w-8 h-8 rounded bg-gray-200 mr-3"></div>
|
||||||
|
<div class="file-name-wrap">
|
||||||
|
<div class="h-5 bg-gray-200 rounded w-1/2 mb-1"></div>
|
||||||
|
<div class="h-4 bg-gray-200 rounded w-1/3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="4" class="table-column type-column">
|
||||||
|
<div class="h-4 bg-gray-200 rounded w-1/3"></div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="3" class="table-column size-column">
|
||||||
|
<div class="h-4 bg-gray-200 rounded w-1/2"></div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="5" class="table-column time-column">
|
||||||
|
<div class="h-4 bg-gray-200 rounded w-2/3"></div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="2" class="table-column action-column">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div class="w-6 h-6 rounded bg-gray-200"></div>
|
||||||
|
<div class="w-6 h-6 rounded bg-gray-200"></div>
|
||||||
|
<div class="w-6 h-6 rounded bg-gray-200"></div>
|
||||||
|
<div class="w-6 h-6 rounded bg-gray-200"></div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
</a-skeleton>
|
||||||
|
|
||||||
|
<!-- 文件表格 -->
|
||||||
|
<div class="file-grid-container" v-if="currentFolderId && !loading">
|
||||||
|
<!-- 表头行 -->
|
||||||
|
<a-row class="table-header-row">
|
||||||
|
<a-col :span="10" class="table-column name-column">
|
||||||
|
<div class="sortable-header" @click="handleSortChange('fileName')">
|
||||||
|
<span>文件名</span>
|
||||||
|
<div class="sort-indicator">
|
||||||
|
<div class="sort-arrow up" :class="{ active: props.sortField === 'file_name' && props.sortOrder === 'asc' }"></div>
|
||||||
|
<div class="sort-arrow down" :class="{ active: props.sortField === 'file_name' && props.sortOrder === 'desc' }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="4" class="table-column type-column">
|
||||||
|
<div class="sortable-header" @click="handleSortChange('fileType')">
|
||||||
|
<span>类型</span>
|
||||||
|
<div class="sort-indicator">
|
||||||
|
<div class="sort-arrow up" :class="{ active: props.sortField === 'file_type' && props.sortOrder === 'asc' }"></div>
|
||||||
|
<div class="sort-arrow down" :class="{ active: props.sortField === 'file_type' && props.sortOrder === 'desc' }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="3" class="table-column size-column">
|
||||||
|
<div class="sortable-header" @click="handleSortChange('fileSize')">
|
||||||
|
<span>大小</span>
|
||||||
|
<div class="sort-indicator">
|
||||||
|
<div class="sort-arrow up" :class="{ active: props.sortField === 'file_size' && props.sortOrder === 'asc' }"></div>
|
||||||
|
<div class="sort-arrow down" :class="{ active: props.sortField === 'file_size' && props.sortOrder === 'desc' }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="5" class="table-column time-column">
|
||||||
|
<div class="sortable-header" @click="handleSortChange('uploadTime')">
|
||||||
|
<span>修改时间</span>
|
||||||
|
<div class="sort-indicator">
|
||||||
|
<div class="sort-arrow up" :class="{ active: props.sortField === 'upload_time' && props.sortOrder === 'asc' }"></div>
|
||||||
|
<div class="sort-arrow down" :class="{ active: props.sortField === 'upload_time' && props.sortOrder === 'desc' }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="2" class="table-column action-column">操作</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<!-- 数据行 -->
|
||||||
|
<a-row
|
||||||
|
v-for="file in files"
|
||||||
|
:key="file.fileId"
|
||||||
|
class="table-data-row"
|
||||||
|
>
|
||||||
|
<!-- 文件名列 -->
|
||||||
|
<a-col :span="10" class="table-column name-column">
|
||||||
|
<div class="file-main">
|
||||||
|
<icon-file :style="{ color: fileColor(getFileExtension(file.fileName || file.name)) }" class="file-icon-large" />
|
||||||
|
<div class="file-name-wrap">
|
||||||
|
<a-typography-title :heading="6" class="file-name">{{ file.fileName || file.name }}</a-typography-title>
|
||||||
|
<div class="file-name-small">{{ file.fileName || file.name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 类型列 -->
|
||||||
|
<a-col :span="4" class="table-column type-column">
|
||||||
|
<div class="cell-content">{{ fileTypeText(getFileExtension(file.fileName || file.name)) }}</div>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 大小列 -->
|
||||||
|
<a-col :span="3" class="table-column size-column">
|
||||||
|
<div class="cell-content">{{ formatFileListSize(file.fileSize || file.size) }}</div>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 时间列 -->
|
||||||
|
<a-col :span="5" class="table-column time-column">
|
||||||
|
<div class="cell-content">{{ formatUploadTime(file.uploadTime || file.uploadTime) }}</div>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 操作列 -->
|
||||||
|
<a-col :span="2" class="table-column action-column">
|
||||||
|
<div class="file-actions">
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
shape="circle"
|
||||||
|
size="small"
|
||||||
|
tooltip="预览"
|
||||||
|
@click="handlePreview(file)"
|
||||||
|
>
|
||||||
|
<icon-eye />
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
shape="circle"
|
||||||
|
size="small"
|
||||||
|
tooltip="下载"
|
||||||
|
@click="handleDownload(file)"
|
||||||
|
>
|
||||||
|
<icon-download />
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
shape="circle"
|
||||||
|
size="small"
|
||||||
|
tooltip="重命名"
|
||||||
|
@click="handleEditFile(file)"
|
||||||
|
>
|
||||||
|
<icon-edit />
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
shape="circle"
|
||||||
|
size="small"
|
||||||
|
tooltip="删除"
|
||||||
|
@click="handleDelete(file)"
|
||||||
|
class="action-btn delete-btn"
|
||||||
|
>
|
||||||
|
<icon-delete />
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<a-empty
|
||||||
|
v-if="!loading && currentFolderId && files.length === 0"
|
||||||
|
description="暂无文件"
|
||||||
|
class="empty-state"
|
||||||
|
>
|
||||||
|
<template #image><icon-file /></template>
|
||||||
|
<template #actions>
|
||||||
|
<a-button type="primary" @click="handleUpload">
|
||||||
|
<template #icon><icon-upload /></template>
|
||||||
|
上传文件
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-empty>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// 导入依赖
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import {
|
||||||
|
IconFolder,
|
||||||
|
IconFile,
|
||||||
|
IconMore,
|
||||||
|
IconDownload,
|
||||||
|
IconDelete,
|
||||||
|
IconEdit,
|
||||||
|
IconEye,
|
||||||
|
IconCopy,
|
||||||
|
IconFolderAdd,
|
||||||
|
IconUpload
|
||||||
|
} from '@arco-design/web-vue/es/icon';
|
||||||
|
|
||||||
|
// 定义props
|
||||||
|
const props = defineProps({
|
||||||
|
files: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
currentFolderId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
sortField: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
sortOrder: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义emit事件
|
||||||
|
const emit = defineEmits([
|
||||||
|
'file-click',
|
||||||
|
'file-download',
|
||||||
|
'file-delete',
|
||||||
|
'file-edit',
|
||||||
|
'file-preview',
|
||||||
|
'file-copy',
|
||||||
|
'file-more',
|
||||||
|
'file-search',
|
||||||
|
'file-search-input',
|
||||||
|
'file-search-clear',
|
||||||
|
'sort-change',
|
||||||
|
'upload'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 本地状态
|
||||||
|
const fileSearchKeyword = ref('');
|
||||||
|
|
||||||
|
// 监听排序状态变化
|
||||||
|
watch(() => props.sortField, (newVal, oldVal) => {
|
||||||
|
console.log('👀 FileList组件 - sortField变化:', oldVal, '->', newVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.sortOrder, (newVal, oldVal) => {
|
||||||
|
console.log('👀 FileList组件 - sortOrder变化:', oldVal, '->', newVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理文件搜索
|
||||||
|
const handleFileSearch = (value) => {
|
||||||
|
emit('file-search', value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理文件搜索输入
|
||||||
|
const handleFileSearchInput = (value) => {
|
||||||
|
emit('file-search-input', value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理文件搜索清除
|
||||||
|
const handleFileSearchClear = () => {
|
||||||
|
emit('file-search-clear');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理排序变化
|
||||||
|
const handleSortChange = (field) => {
|
||||||
|
console.log('🎯 FileList组件 - 排序点击:', field);
|
||||||
|
console.log('🎯 FileList组件 - 当前sortField:', props.sortField);
|
||||||
|
console.log('🎯 FileList组件 - 当前sortOrder:', props.sortOrder);
|
||||||
|
emit('sort-change', field);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理预览
|
||||||
|
const handlePreview = (file) => {
|
||||||
|
emit('file-preview', file);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理下载
|
||||||
|
const handleDownload = (file) => {
|
||||||
|
emit('file-download', file);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理编辑文件
|
||||||
|
const handleEditFile = (file) => {
|
||||||
|
emit('file-edit', file);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理删除
|
||||||
|
const handleDelete = (file) => {
|
||||||
|
emit('file-delete', file);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理上传
|
||||||
|
const handleUpload = () => {
|
||||||
|
emit('upload');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 工具函数 - 获取文件扩展名
|
||||||
|
const getFileExtension = (filename) => {
|
||||||
|
if (!filename) return '';
|
||||||
|
return filename.split('.').pop().toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 工具函数 - 文件颜色
|
||||||
|
const fileColor = (extension) => {
|
||||||
|
const colorMap = {
|
||||||
|
pdf: '#ff4d4f',
|
||||||
|
doc: '#1890ff',
|
||||||
|
docx: '#1890ff',
|
||||||
|
xls: '#52c41a',
|
||||||
|
xlsx: '#52c41a',
|
||||||
|
ppt: '#fa8c16',
|
||||||
|
pptx: '#fa8c16',
|
||||||
|
zip: '#722ed1',
|
||||||
|
rar: '#722ed1',
|
||||||
|
txt: '#8c8c8c',
|
||||||
|
jpg: '#fadb14',
|
||||||
|
jpeg: '#fadb14',
|
||||||
|
png: '#fadb14',
|
||||||
|
gif: '#fadb14',
|
||||||
|
bmp: '#fadb14',
|
||||||
|
webp: '#fadb14'
|
||||||
|
};
|
||||||
|
return colorMap[extension] || '#8c8c8c';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 工具函数 - 文件类型文本
|
||||||
|
const fileTypeText = (extension) => {
|
||||||
|
const typeMap = {
|
||||||
|
pdf: 'PDF文档',
|
||||||
|
doc: 'Word文档',
|
||||||
|
docx: 'Word文档',
|
||||||
|
xls: 'Excel表格',
|
||||||
|
xlsx: 'Excel表格',
|
||||||
|
ppt: 'PPT演示',
|
||||||
|
pptx: 'PPT演示',
|
||||||
|
zip: '压缩文件',
|
||||||
|
rar: '压缩文件',
|
||||||
|
txt: '文本文件',
|
||||||
|
jpg: '图片文件',
|
||||||
|
jpeg: '图片文件',
|
||||||
|
png: '图片文件',
|
||||||
|
gif: '图片文件',
|
||||||
|
bmp: '图片文件',
|
||||||
|
webp: '图片文件'
|
||||||
|
};
|
||||||
|
return typeMap[extension] || '未知文件';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 工具函数 - 格式化文件大小
|
||||||
|
const formatFileListSize = (bytes) => {
|
||||||
|
if (!bytes || bytes === 0) return '0 B';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 工具函数 - 格式化上传时间
|
||||||
|
const formatUploadTime = (time) => {
|
||||||
|
if (!time) return '';
|
||||||
|
const date = new Date(time);
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.file-list-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-header-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-search-container {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-search-input {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.initial-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 64px 0;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
background-color: var(--color-fill-1);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.initial-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: var(--color-text-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-grid-container {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: var(--color-bg-1);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
margin-bottom: 0;
|
||||||
|
min-height: 300px;
|
||||||
|
max-height: calc(100vh - 380px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header-row {
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
background-color: var(--color-fill-1);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-data-row {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 64px;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: var(--color-bg-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-data-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-data-row:hover {
|
||||||
|
background-color: rgba(22, 93, 255, 0.1);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-column {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-column {
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-column {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-column {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-column {
|
||||||
|
flex: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-column {
|
||||||
|
flex: 0.5;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortable-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortable-header:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-indicator {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 4px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-arrow {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
border-right: 3px solid transparent;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-arrow.up {
|
||||||
|
border-bottom: 3px solid var(--color-text-4);
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-arrow.down {
|
||||||
|
border-top: 3px solid var(--color-text-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-arrow.active {
|
||||||
|
border-bottom-color: var(--color-primary);
|
||||||
|
border-top-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon-large {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-right: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name-wrap {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
line-height: 1.4;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name-small {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
margin-top: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-content {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-data-row:hover .file-actions {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions .action-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions .action-btn:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
background: var(--color-fill-3);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions .delete-btn:hover {
|
||||||
|
color: var(--color-danger);
|
||||||
|
background: var(--color-danger-light-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 64px 0;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
background-color: var(--color-fill-1);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.empty-state .arco-btn) {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.empty-state .arco-btn:hover) {
|
||||||
|
background-color: var(--color-primary-dark-1);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.empty-state .arco-btn:active) {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,116 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="visible" class="pagination-container">
|
||||||
|
<a-pagination
|
||||||
|
:total="total"
|
||||||
|
:current="current"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:show-total="true"
|
||||||
|
:show-page-size="true"
|
||||||
|
:page-size-options="[10, 20, 50, 100]"
|
||||||
|
:show-jumper="true"
|
||||||
|
:hide-on-single-page="false"
|
||||||
|
size="default"
|
||||||
|
@change="handlePageChange"
|
||||||
|
@page-size-change="handlePageSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// 定义props
|
||||||
|
const props = defineProps({
|
||||||
|
total: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
current: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 10
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义emit事件
|
||||||
|
const emit = defineEmits(['page-change', 'page-size-change']);
|
||||||
|
|
||||||
|
// 处理页码变化
|
||||||
|
const handlePageChange = (page) => {
|
||||||
|
emit('page-change', page);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理每页条数变化
|
||||||
|
const handlePageSizeChange = (pageSize) => {
|
||||||
|
emit('page-size-change', pageSize);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pagination-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--color-bg-1);
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container :deep(.arco-pagination) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container :deep(.arco-pagination-item) {
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 0 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container :deep(.arco-pagination-item:hover) {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container :deep(.arco-pagination-item-active) {
|
||||||
|
background: var(--color-primary);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container :deep(.arco-pagination-prev),
|
||||||
|
.pagination-container :deep(.arco-pagination-next) {
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container :deep(.arco-pagination-prev:hover),
|
||||||
|
.pagination-container :deep(.arco-pagination-next:hover) {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container :deep(.arco-pagination-size-changer) {
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container :deep(.arco-pagination-jumper) {
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container :deep(.arco-pagination-total) {
|
||||||
|
color: var(--color-text-2);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,557 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:visible="visible"
|
||||||
|
title="上传文件"
|
||||||
|
width="620px"
|
||||||
|
:mask-closable="false"
|
||||||
|
@ok="handleSubmit"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
:confirm-loading="uploading"
|
||||||
|
:ok-disabled="!canUpload"
|
||||||
|
@update:visible="(val) => emit('update:visible', val)"
|
||||||
|
>
|
||||||
|
<a-form :model="uploadForm" ref="uploadFormRef" layout="vertical">
|
||||||
|
<!-- 选择文件 -->
|
||||||
|
<a-form-item
|
||||||
|
label="选择文件"
|
||||||
|
:validate-status="!hasFiles ? 'error' : ''"
|
||||||
|
:help="!hasFiles ? '请选择需要上传的文件' : ''"
|
||||||
|
>
|
||||||
|
<div class="upload-container">
|
||||||
|
<!-- 上传按钮 -->
|
||||||
|
<a-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
:key="visible ? 'upload-open' : 'upload-closed'"
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
@change="handleFileChange"
|
||||||
|
:accept="allowedFileTypes"
|
||||||
|
multiple
|
||||||
|
>
|
||||||
|
<a-button type="primary" class="upload-btn">
|
||||||
|
<icon-upload />
|
||||||
|
点击选择文件
|
||||||
|
</a-button>
|
||||||
|
</a-upload>
|
||||||
|
|
||||||
|
<!-- 文件类型提示 -->
|
||||||
|
<div class="upload-hint">
|
||||||
|
支持 {{ allowedFileTypesText }} 等格式,单个文件不超过 {{ maxFileSizeText }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文件列表 -->
|
||||||
|
<div class="upload-file-list" v-if="fileListTemp.length > 0">
|
||||||
|
<div
|
||||||
|
class="upload-file-item"
|
||||||
|
v-for="file in fileListTemp"
|
||||||
|
:key="file.uid"
|
||||||
|
:class="{ 'file-error': file.error }"
|
||||||
|
>
|
||||||
|
<div class="file-info">
|
||||||
|
<icon-file
|
||||||
|
:style="{ color: fileColor(getFileExtension(file.name)) }"
|
||||||
|
class="file-icon"
|
||||||
|
/>
|
||||||
|
<div class="file-details">
|
||||||
|
<div class="file-name">{{ file.name }}</div>
|
||||||
|
<div class="file-meta">
|
||||||
|
{{ formatFileSize(file.size) }}
|
||||||
|
<span v-if="file.error" class="error-text">{{ file.error }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 进度条 -->
|
||||||
|
<div class="file-progress" v-if="file.status === 'uploading'">
|
||||||
|
<a-progress
|
||||||
|
:percent="file.percent || 0"
|
||||||
|
size="small"
|
||||||
|
:status="file.percent === 100 ? 'success' : 'processing'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="file-actions">
|
||||||
|
<a-button
|
||||||
|
v-if="file.status !== 'uploading'"
|
||||||
|
type="text"
|
||||||
|
shape="circle"
|
||||||
|
size="small"
|
||||||
|
@click="removeFile(file)"
|
||||||
|
class="remove-btn"
|
||||||
|
>
|
||||||
|
<icon-delete />
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
v-else
|
||||||
|
type="text"
|
||||||
|
shape="circle"
|
||||||
|
size="small"
|
||||||
|
@click="cancelUpload(file)"
|
||||||
|
class="cancel-btn"
|
||||||
|
>
|
||||||
|
<icon-stop />
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 目标文件夹选择 -->
|
||||||
|
<a-form-item
|
||||||
|
label="上传至目录"
|
||||||
|
field="folderId"
|
||||||
|
:rules="[{ required: true, message: '请选择目标文件夹' }]"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model="uploadForm.folderId"
|
||||||
|
placeholder="请选择目标文件夹"
|
||||||
|
allow-clear
|
||||||
|
>
|
||||||
|
<a-option value="0">根目录</a-option>
|
||||||
|
<a-option
|
||||||
|
v-for="folder in folderList"
|
||||||
|
:key="folder.id"
|
||||||
|
:value="folder.id"
|
||||||
|
>
|
||||||
|
{{ folder.name }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, watch } from 'vue'
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
import { IconUpload, IconFile, IconDelete, IconStop } from '@arco-design/web-vue/es/icon'
|
||||||
|
import { uploadFileApi } from '@/apis/bussiness'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
// Props
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
folderList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
currentFolderId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits([
|
||||||
|
'update:visible',
|
||||||
|
'upload-success'
|
||||||
|
])
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const uploadForm = reactive({
|
||||||
|
folderId: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const fileListTemp = ref([])
|
||||||
|
const uploadFormRef = ref(null)
|
||||||
|
const uploadRef = ref(null)
|
||||||
|
const uploading = ref(false)
|
||||||
|
const cancelTokens = ref({})
|
||||||
|
|
||||||
|
// 常量
|
||||||
|
const allowedFileTypes = '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.zip,.txt,.jpg,.jpeg,.png,.gif,.bmp,.webp'
|
||||||
|
const allowedFileTypesText = 'PDF, Word, Excel, PPT, 压缩文件, 文本文件, 图片文件'
|
||||||
|
const maxFileSize = 1000 * 1024 * 1024 // 1000MB
|
||||||
|
const maxFileSizeText = '1000MB'
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const hasFiles = computed(() => {
|
||||||
|
const validFiles = fileListTemp.value.filter(file => {
|
||||||
|
return !file.error && file.status !== 'removed' && file.status !== 'canceled'
|
||||||
|
})
|
||||||
|
return validFiles.length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const canUpload = computed(() => {
|
||||||
|
return hasFiles.value && !uploading.value && uploadForm.folderId
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听 visible 变化
|
||||||
|
watch(() => props.visible, (visible) => {
|
||||||
|
if (visible) {
|
||||||
|
// 重置表单
|
||||||
|
uploadForm.folderId = props.currentFolderId || ''
|
||||||
|
fileListTemp.value = []
|
||||||
|
|
||||||
|
// 重置上传组件
|
||||||
|
if (uploadRef.value) {
|
||||||
|
try {
|
||||||
|
uploadRef.value.reset()
|
||||||
|
} catch (error) {
|
||||||
|
console.log('重置上传组件时出错:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 文件变化处理
|
||||||
|
const handleFileChange = (info) => {
|
||||||
|
if (!info || !Array.isArray(info) || !props.visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileList = info
|
||||||
|
if (fileList.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前已存在的文件UID列表,用于去重
|
||||||
|
const existingUids = fileListTemp.value.map(f => f.uid)
|
||||||
|
|
||||||
|
// 处理新选择的文件
|
||||||
|
fileList.forEach((file) => {
|
||||||
|
// 检查文件是否已存在(去重)
|
||||||
|
if (existingUids.includes(file.uid)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保文件对象有正确的属性
|
||||||
|
const fileObj = {
|
||||||
|
uid: file.uid,
|
||||||
|
name: file.name,
|
||||||
|
size: file.size || file.file?.size || 0,
|
||||||
|
type: file.type || file.file?.type || '',
|
||||||
|
status: 'ready',
|
||||||
|
error: '',
|
||||||
|
originFileObj: file.file || file
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件
|
||||||
|
const isValid = validateFile(fileObj)
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
fileListTemp.value.push(fileObj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件验证
|
||||||
|
const validateFile = (file) => {
|
||||||
|
file.error = ''
|
||||||
|
|
||||||
|
// 验证文件类型
|
||||||
|
const ext = getFileExtension(file.name).toLowerCase()
|
||||||
|
const allowedExts = allowedFileTypes
|
||||||
|
.split(',')
|
||||||
|
.map(type => type.toLowerCase().replace(/^\./, ''))
|
||||||
|
|
||||||
|
if (!allowedExts.includes(ext)) {
|
||||||
|
file.error = `不支持的文件类型,支持: ${allowedFileTypesText}`
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件大小
|
||||||
|
if (file.size > maxFileSize) {
|
||||||
|
file.error = `文件过大,最大支持 ${maxFileSizeText}`
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件扩展名
|
||||||
|
const getFileExtension = (fileName) => {
|
||||||
|
const lastDotIndex = fileName.lastIndexOf('.')
|
||||||
|
return lastDotIndex > 0 ? fileName.slice(lastDotIndex + 1) : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件图标颜色
|
||||||
|
const fileColor = (extension) => {
|
||||||
|
const colorMap = {
|
||||||
|
pdf: '#ff4d4f',
|
||||||
|
doc: '#1890ff',
|
||||||
|
docx: '#1890ff',
|
||||||
|
xls: '#52c41a',
|
||||||
|
xlsx: '#52c41a',
|
||||||
|
ppt: '#faad14',
|
||||||
|
pptx: '#faad14',
|
||||||
|
zip: '#722ed1',
|
||||||
|
txt: '#8c8c8c',
|
||||||
|
jpg: '#52c41a',
|
||||||
|
jpeg: '#52c41a',
|
||||||
|
png: '#1890ff',
|
||||||
|
gif: '#faad14',
|
||||||
|
bmp: '#722ed1',
|
||||||
|
webp: '#13c2c2'
|
||||||
|
}
|
||||||
|
return colorMap[extension.toLowerCase()] || 'var(--color-text-3)'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化文件大小
|
||||||
|
const formatFileSize = (fileSize) => {
|
||||||
|
if (fileSize === 0) return '0 B'
|
||||||
|
|
||||||
|
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||||
|
let size = fileSize
|
||||||
|
let unitIndex = 0
|
||||||
|
|
||||||
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||||
|
size /= 1024
|
||||||
|
unitIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${size.toFixed(2)} ${units[unitIndex]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除文件
|
||||||
|
const removeFile = (file) => {
|
||||||
|
fileListTemp.value = fileListTemp.value.filter(f => f.uid !== file.uid)
|
||||||
|
|
||||||
|
// 如果是正在上传的文件,取消请求
|
||||||
|
if (file.status === 'uploading' && cancelTokens.value[file.uid]) {
|
||||||
|
cancelTokens.value[file.uid].cancel('上传已取消')
|
||||||
|
delete cancelTokens.value[file.uid]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消上传
|
||||||
|
const cancelUpload = (file) => {
|
||||||
|
if (cancelTokens.value[file.uid]) {
|
||||||
|
cancelTokens.value[file.uid].cancel('上传已取消')
|
||||||
|
file.status = 'canceled'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交上传
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
// 过滤有效文件
|
||||||
|
const validFiles = fileListTemp.value.filter(file =>
|
||||||
|
!file.error && file.status !== 'removed' && file.status !== 'canceled'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (validFiles.length === 0) {
|
||||||
|
Message.warning('请选择有效的文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件夹ID
|
||||||
|
if (!uploadForm.folderId) {
|
||||||
|
Message.warning('请选择目标文件夹')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uploading.value = true
|
||||||
|
let hasError = false
|
||||||
|
let hasFileExists = false
|
||||||
|
|
||||||
|
for (const fileItem of validFiles) {
|
||||||
|
// 获取原始File对象
|
||||||
|
const realFile = fileItem.originFileObj || fileItem
|
||||||
|
|
||||||
|
if (!realFile) {
|
||||||
|
hasError = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fileItem.status = 'uploading'
|
||||||
|
fileItem.percent = 0
|
||||||
|
|
||||||
|
// 创建取消令牌
|
||||||
|
const source = axios.CancelToken.source()
|
||||||
|
cancelTokens.value[fileItem.uid] = source
|
||||||
|
|
||||||
|
// 调用API
|
||||||
|
const result = await uploadFileApi(
|
||||||
|
realFile,
|
||||||
|
Number(uploadForm.folderId),
|
||||||
|
(progressEvent) => {
|
||||||
|
if (progressEvent.lengthComputable) {
|
||||||
|
fileItem.percent = Math.round((progressEvent.loaded / progressEvent.total) * 100)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
source.token
|
||||||
|
)
|
||||||
|
|
||||||
|
// 检查上传结果
|
||||||
|
if (result.code === 200) {
|
||||||
|
fileItem.status = 'success'
|
||||||
|
fileItem.percent = 100
|
||||||
|
} else if (result.code === 400 && result.msg && result.msg.includes('已存在')) {
|
||||||
|
// 文件已存在的情况
|
||||||
|
fileItem.status = 'error'
|
||||||
|
fileItem.error = '文件已存在'
|
||||||
|
hasFileExists = true
|
||||||
|
} else {
|
||||||
|
fileItem.status = 'error'
|
||||||
|
fileItem.error = result.msg || '上传失败'
|
||||||
|
hasError = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据结果显示相应的消息
|
||||||
|
if (hasFileExists && !hasError) {
|
||||||
|
Message.warning('文件已存在')
|
||||||
|
} else if (hasError) {
|
||||||
|
Message.error('上传失败')
|
||||||
|
} else {
|
||||||
|
Message.success('上传成功')
|
||||||
|
emit('upload-success')
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消上传
|
||||||
|
const handleCancel = () => {
|
||||||
|
// 取消所有正在进行的上传
|
||||||
|
Object.values(cancelTokens.value).forEach(source => {
|
||||||
|
source.cancel('上传已取消')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 重置所有状态
|
||||||
|
emit('update:visible', false)
|
||||||
|
uploadForm.folderId = props.currentFolderId || ''
|
||||||
|
fileListTemp.value = []
|
||||||
|
cancelTokens.value = {}
|
||||||
|
uploading.value = false
|
||||||
|
|
||||||
|
// 清空上传组件
|
||||||
|
if (uploadRef.value) {
|
||||||
|
uploadRef.value.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 上传文件相关样式 */
|
||||||
|
.upload-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-btn {
|
||||||
|
align-self: flex-start;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-hint {
|
||||||
|
color: var(--color-text-3);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--color-fill-2);
|
||||||
|
border-radius: 6px;
|
||||||
|
border-left: 3px solid var(--color-primary-light-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-file-list {
|
||||||
|
margin-top: 16px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--color-bg-1);
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-fill-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.file-error {
|
||||||
|
background: rgba(255, 77, 79, 0.05);
|
||||||
|
border-left: 3px solid #ff4d4f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
gap: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-details {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-meta {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
color: #ff4d4f;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-progress {
|
||||||
|
margin: 0 16px;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-btn {
|
||||||
|
color: var(--color-text-3);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #ff4d4f;
|
||||||
|
background: rgba(255, 77, 79, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
color: var(--color-text-3);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #faad14;
|
||||||
|
background: rgba(250, 173, 20, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,19 +2,21 @@
|
||||||
项目管理页面
|
项目管理页面
|
||||||
已完成接口对接:
|
已完成接口对接:
|
||||||
1. 项目列表查询 (listProject) - 支持分页和条件查询
|
1. 项目列表查询 (listProject) - 支持分页和条件查询
|
||||||
2. 项目新增 (addProject)
|
2. 项目新增 (addProject)
|
||||||
3. 项目修改 (updateProject)
|
3. 项目修改 (updateProject)
|
||||||
4. 项目删除 (deleteProject)
|
4. 项目删除 (deleteProject)
|
||||||
5. 项目导出 (exportProject)
|
5. 项目导出 (exportProject)
|
||||||
6. 项目导入 (importProject)
|
6. 项目导入 (importProject)
|
||||||
|
|
||||||
所有API调用都已添加错误处理和类型安全检查
|
所有API调用都已添加错误处理和类型安全检查
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<GiPageLayout>
|
<GiPageLayout>
|
||||||
<GiTable row-key="id" :data="dataList" :columns="tableColumns" :loading="loading"
|
<GiTable
|
||||||
|
row-key="id" :data="dataList" :columns="tableColumns" :loading="loading"
|
||||||
:scroll="{ x: '100%', y: '100%', minWidth: 1500 }" :pagination="pagination" :disabled-tools="['size']"
|
:scroll="{ x: '100%', y: '100%', minWidth: 1500 }" :pagination="pagination" :disabled-tools="['size']"
|
||||||
@page-change="onPageChange" @page-size-change="onPageSizeChange" @refresh="search">
|
@page-change="onPageChange" @page-size-change="onPageSizeChange" @refresh="search"
|
||||||
|
>
|
||||||
<template #top>
|
<template #top>
|
||||||
<GiForm v-model="searchForm" search :columns="queryFormColumns" size="medium" @search="search" @reset="reset">
|
<GiForm v-model="searchForm" search :columns="queryFormColumns" size="medium" @search="search" @reset="reset">
|
||||||
</GiForm>
|
</GiForm>
|
||||||
|
@ -71,10 +73,14 @@
|
||||||
</GiTable>
|
</GiTable>
|
||||||
|
|
||||||
<!-- 新增/编辑项目弹窗 -->
|
<!-- 新增/编辑项目弹窗 -->
|
||||||
<a-modal v-model:visible="addModalVisible" :title="modalTitle" @cancel="resetForm"
|
<a-modal
|
||||||
:ok-button-props="{ loading: submitLoading }" @ok="handleSubmit" width="800px" modal-class="project-form-modal">
|
v-model:visible="addModalVisible" :title="modalTitle" :ok-button-props="{ loading: submitLoading }"
|
||||||
<a-form ref="formRef" :model="form" :rules="formRules" layout="vertical"
|
width="800px" modal-class="project-form-modal" @cancel="resetForm" @ok="handleSubmit"
|
||||||
:style="{ maxHeight: '70vh', overflow: 'auto', padding: '0 10px' }">
|
>
|
||||||
|
<a-form
|
||||||
|
ref="formRef" :model="form" :rules="formRules" layout="vertical"
|
||||||
|
:style="{ maxHeight: '70vh', overflow: 'auto', padding: '0 10px' }"
|
||||||
|
>
|
||||||
<!-- 基本信息 -->
|
<!-- 基本信息 -->
|
||||||
<a-divider orientation="left">基本信息</a-divider>
|
<a-divider orientation="left">基本信息</a-divider>
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
|
@ -88,10 +94,12 @@
|
||||||
<a-input v-model="form.farmAddress" placeholder="请输入地址" />
|
<a-input v-model="form.farmAddress" placeholder="请输入地址" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col><a-button size="mini" @click="() => { Message.info(`待开发`) }">
|
<a-col>
|
||||||
|
<a-button size="mini" @click="() => { Message.info(`待开发`) }">
|
||||||
<template #icon><icon-location /></template>
|
<template #icon><icon-location /></template>
|
||||||
地图选点
|
地图选点
|
||||||
</a-button></a-col>
|
</a-button>
|
||||||
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
|
@ -108,7 +116,7 @@
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item field="inspectionUnit" label="业主">
|
<a-form-item field="inspectionUnit" label="业主">
|
||||||
<a-input v-model="form.inspectionUnit" placeholder="请输入业主单位" @input="(val) => (form.farmName = val)" />
|
<a-input v-model="form.inspectionUnit" placeholder="请输入业主单位" @input="(val) => (form.farmName = val)" />
|
||||||
<!--风场名称同步业主 -->
|
<!-- 风场名称同步业主 -->
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
|
@ -145,10 +153,133 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-form-item field="projectContent" label="项目内容">
|
<a-col :span="12">
|
||||||
<a-textarea v-model="form.coverUrl" placeholder="请输入项目内容" :rows="4" />
|
<a-form-item field="projectOrigin" label="项目来源" :rules="[{ required: true, message: '请输入项目来源' }]">
|
||||||
</a-form-item>
|
<a-input v-model="form.projectOrigin" placeholder="请输入项目来源" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
<a-divider orientation="left">任务设置</a-divider>
|
||||||
|
<div class="mb-2">
|
||||||
|
<a-button type="dashed" size="small" @click="addTask">
|
||||||
|
<template #icon><icon-plus /></template>
|
||||||
|
新增任务
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div v-if="form.tasks.length === 0" class="text-gray-500 mb-2">暂无任务,请点击“新增任务”。</div>
|
||||||
|
<a-space direction="vertical" fill>
|
||||||
|
<a-card v-for="(task, tIndex) in form.tasks" :key="tIndex" size="small">
|
||||||
|
<template #title>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span>任务 {{ tIndex + 1 }}</span>
|
||||||
|
<a-space>
|
||||||
|
<a-button size="mini" @click="addSubtask(tIndex)">新增子任务</a-button>
|
||||||
|
<a-button size="mini" status="danger" @click="removeTask(tIndex)">删除</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-row :gutter="12">
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.taskName`" label="任务名称" required>
|
||||||
|
<a-input v-model="task.taskName" placeholder="请输入任务名称" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.taskCode`" label="任务编号">
|
||||||
|
<a-input v-model="task.taskCode" placeholder="编号" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.mainUserId`" label="负责人">
|
||||||
|
<a-select v-model="task.mainUserId" placeholder="选择负责人" :loading="userLoading">
|
||||||
|
<a-option v-for="u in userOptions" :key="u.value" :value="u.value">{{ u.label }}</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="4">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.scales`" label="工量">
|
||||||
|
<a-input-number v-model="task.scales" :min="0" :max="9999" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row :gutter="12">
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.planStartDate`" label="计划开始">
|
||||||
|
<a-date-picker v-model="task.planStartDate" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.planEndDate`" label="计划结束">
|
||||||
|
<a-date-picker v-model="task.planEndDate" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.taskGroupId`" label="任务组">
|
||||||
|
<a-input-number v-model="task.taskGroupId" :min="0" placeholder="可选" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<!-- 子任务 -->
|
||||||
|
<div v-if="task.children && task.children.length">
|
||||||
|
<a-divider orientation="left">子任务</a-divider>
|
||||||
|
<a-card
|
||||||
|
v-for="(sub, sIndex) in task.children"
|
||||||
|
:key="sIndex"
|
||||||
|
size="small"
|
||||||
|
class="mb-2"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span>子任务 {{ tIndex + 1 }}-{{ sIndex + 1 }}</span>
|
||||||
|
<a-button size="mini" status="danger" @click="removeSubtask(tIndex, sIndex)">删除</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-row :gutter="12">
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.taskName`" label="任务名称" required>
|
||||||
|
<a-input v-model="sub.taskName" placeholder="请输入任务名称" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.taskCode`" label="任务编号">
|
||||||
|
<a-input v-model="sub.taskCode" placeholder="编号" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.mainUserId`" label="负责人">
|
||||||
|
<a-select v-model="sub.mainUserId" placeholder="选择负责人" :loading="userLoading">
|
||||||
|
<a-option v-for="u in userOptions" :key="u.value" :value="u.value">{{ u.label }}</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="4">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.scales`" label="工量">
|
||||||
|
<a-input-number v-model="sub.scales" :min="0" :max="9999" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row :gutter="12">
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.planStartDate`" label="计划开始">
|
||||||
|
<a-date-picker v-model="sub.planStartDate" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.planEndDate`" label="计划结束">
|
||||||
|
<a-date-picker v-model="sub.planEndDate" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item :field="`tasks.${tIndex}.children.${sIndex}.taskGroupId`" label="任务组">
|
||||||
|
<a-input-number v-model="sub.taskGroupId" :min="0" placeholder="可选" style="width: 100%" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-space>
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item field="status" label="项目状态">
|
<a-form-item field="status" label="项目状态">
|
||||||
|
@ -198,7 +329,6 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-divider orientation="middle">地图</a-divider>
|
<a-divider orientation="middle">地图</a-divider>
|
||||||
|
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
|
@ -221,53 +351,53 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted } from 'vue'
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { Message, Modal } from '@arco-design/web-vue'
|
import { Message, Modal } from '@arco-design/web-vue'
|
||||||
import { addProject, deleteProject, listProject, updateProject, exportProject, importProject } from '@/apis/project'
|
import type { TableColumnData } from '@arco-design/web-vue'
|
||||||
|
import TurbineGrid from './TurbineGrid.vue'
|
||||||
|
import { addProject, deleteProject, exportProject, importProject, listProject, updateProject } from '@/apis/project'
|
||||||
import { isMobile } from '@/utils'
|
import { isMobile } from '@/utils'
|
||||||
import has from '@/utils/has'
|
|
||||||
import http from '@/utils/http'
|
import http from '@/utils/http'
|
||||||
import type { ColumnItem } from '@/components/GiForm'
|
import type { ColumnItem } from '@/components/GiForm'
|
||||||
import type { TableColumnData } from '@arco-design/web-vue'
|
import type { ProjectPageQuery } from '@/apis/project/type'
|
||||||
import type { ProjectResp, ProjectPageQuery } from '@/apis/project/type'
|
import type * as T from '@/apis/project/type'
|
||||||
import * as T from '@/apis/project/type'
|
|
||||||
import TurbineGrid from './TurbineGrid.vue'
|
|
||||||
defineOptions({ name: 'ProjectManagement' })
|
defineOptions({ name: 'ProjectManagement' })
|
||||||
|
|
||||||
// 项目状态常量定义 (API返回数字类型)
|
// 项目状态常量定义 (API返回数字类型)
|
||||||
const PROJECT_STATUS = {
|
const PROJECT_STATUS = {
|
||||||
NOT_STARTED: 0, // 未开始/待施工
|
NOT_STARTED: 0, // 未开始/待施工
|
||||||
IN_PROGRESS: 1, // 施工中
|
IN_PROGRESS: 1, // 施工中
|
||||||
COMPLETED: 2, // 已完成
|
COMPLETED: 2, // 已完成
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
// 项目状态映射
|
// 项目状态映射
|
||||||
const PROJECT_STATUS_MAP = {
|
const PROJECT_STATUS_MAP = {
|
||||||
0: '待施工',
|
0: '待施工',
|
||||||
1: '施工中',
|
1: '施工中',
|
||||||
2: '已完成'
|
2: '已完成',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
// 项目状态选项
|
// 项目状态选项
|
||||||
const PROJECT_STATUS_OPTIONS = [
|
const PROJECT_STATUS_OPTIONS = [
|
||||||
{ label: '待施工', value: 0 },
|
{ label: '待施工', value: 0 },
|
||||||
{ label: '施工中', value: 1 },
|
{ label: '施工中', value: 1 },
|
||||||
{ label: '已完成', value: 2 }
|
{ label: '已完成', value: 2 },
|
||||||
]
|
]
|
||||||
|
|
||||||
// 项目类别常量定义
|
// 项目类别常量定义
|
||||||
const PROJECT_CATEGORY = {
|
const PROJECT_CATEGORY = {
|
||||||
EXTERNAL_WORK: '外部工作',
|
EXTERNAL_WORK: '外部工作',
|
||||||
INTERNAL_PROJECT: '内部项目',
|
INTERNAL_PROJECT: '内部项目',
|
||||||
TECHNICAL_SERVICE: '技术服务'
|
TECHNICAL_SERVICE: '技术服务',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
// 项目类别选项
|
// 项目类别选项
|
||||||
const PROJECT_CATEGORY_OPTIONS = [
|
const PROJECT_CATEGORY_OPTIONS = [
|
||||||
{ label: PROJECT_CATEGORY.EXTERNAL_WORK, value: PROJECT_CATEGORY.EXTERNAL_WORK },
|
{ label: PROJECT_CATEGORY.EXTERNAL_WORK, value: PROJECT_CATEGORY.EXTERNAL_WORK },
|
||||||
{ label: PROJECT_CATEGORY.INTERNAL_PROJECT, value: PROJECT_CATEGORY.INTERNAL_PROJECT },
|
{ label: PROJECT_CATEGORY.INTERNAL_PROJECT, value: PROJECT_CATEGORY.INTERNAL_PROJECT },
|
||||||
{ label: PROJECT_CATEGORY.TECHNICAL_SERVICE, value: PROJECT_CATEGORY.TECHNICAL_SERVICE }
|
{ label: PROJECT_CATEGORY.TECHNICAL_SERVICE, value: PROJECT_CATEGORY.TECHNICAL_SERVICE },
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
@ -280,12 +410,12 @@ const currentId = ref<string | null>(null)
|
||||||
const fileList = ref([])
|
const fileList = ref([])
|
||||||
const dataList = ref<T.ProjectResp[]>([])
|
const dataList = ref<T.ProjectResp[]>([])
|
||||||
const userLoading = ref(false)
|
const userLoading = ref(false)
|
||||||
const userOptions = ref<{ label: string; value: string }[]>([])
|
const userOptions = ref<{ label: string, value: string }[]>([])
|
||||||
|
|
||||||
let searchForm = reactive<Partial<ProjectPageQuery>>({
|
const searchForm = reactive<Partial<ProjectPageQuery>>({
|
||||||
projectName: '',
|
projectName: '',
|
||||||
status: undefined,
|
status: undefined,
|
||||||
fieldName: '', // 保持使用fieldName,因为API类型定义中使用的是这个字段名
|
fieldName: '', // 保持使用fieldName,因为API类型定义中使用的是这个字段名
|
||||||
})
|
})
|
||||||
|
|
||||||
const queryFormColumns: ColumnItem[] = reactive([
|
const queryFormColumns: ColumnItem[] = reactive([
|
||||||
|
@ -320,28 +450,48 @@ const queryFormColumns: ColumnItem[] = reactive([
|
||||||
])
|
])
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
projectId: '', // 项目id
|
projectId: '', // 项目id
|
||||||
projectName: '', // 项目名称
|
projectName: '', // 项目名称
|
||||||
projectManagerId: '', // 项目经理id
|
projectManagerId: '', // 项目经理id
|
||||||
client: '', // 委托单位
|
client: '', // 委托单位
|
||||||
clientContact: '', // 委托单位联系人
|
clientContact: '', // 委托单位联系人
|
||||||
clientPhone: '', // 委托单位联系电话
|
clientPhone: '', // 委托单位联系电话
|
||||||
inspectionUnit: '', // 业主单位
|
inspectionUnit: '', // 业主单位
|
||||||
inspectionContact: '', // 业主单位联系人
|
inspectionContact: '', // 业主单位联系人
|
||||||
inspectionPhone: '', // 业主单位联系电话
|
inspectionPhone: '', // 业主单位联系电话
|
||||||
farmName: '', // 风场名称 现在等同业主单位
|
farmName: '', // 风场名称 现在等同业主单位
|
||||||
farmAddress: '', // 风场地址 项目地址
|
farmAddress: '', // 风场地址 项目地址
|
||||||
scale: '', // 项目规模 风机数量
|
projectOrigin: '', // 项目来源
|
||||||
turbineModel: '', // 风机型号
|
scale: '', // 项目规模 风机数量
|
||||||
status: '', // 状态:0待施工,1施工中,2已完工,3已审核,4已验收
|
turbineModel: '', // 风机型号
|
||||||
startDate: '', // 开始时间
|
status: '', // 状态:0待施工,1施工中,2已完工,3已审核,4已验收
|
||||||
endDate: '', // 结束时间
|
startDate: '', // 开始时间
|
||||||
coverUrl: '', // 项目封面 现在填的是项目内容
|
endDate: '', // 结束时间
|
||||||
|
// coverUrl: '', // 项目封面 现在改为任务,不再使用
|
||||||
constructionTeamLeaderId: '', // 施工组长id
|
constructionTeamLeaderId: '', // 施工组长id
|
||||||
constructorIds: '', // 施工人员id
|
constructorIds: '', // 施工人员id
|
||||||
qualityOfficerId: '', // 质量员id
|
qualityOfficerId: '', // 质量员id
|
||||||
auditorId: '', // 安全员id
|
auditorId: '', // 安全员id
|
||||||
turbineList: [] as { id: number; turbineNo: string; lat?: number; lng?: number; status: 0 | 1 | 2 }[], //风机组
|
// 任务集合(支持子任务)
|
||||||
|
tasks: [] as Array<{
|
||||||
|
taskName: string
|
||||||
|
taskCode?: string
|
||||||
|
mainUserId?: string | number
|
||||||
|
planStartDate?: string
|
||||||
|
planEndDate?: string
|
||||||
|
scales?: number
|
||||||
|
taskGroupId?: number | string
|
||||||
|
children?: Array<{
|
||||||
|
taskName: string
|
||||||
|
taskCode?: string
|
||||||
|
mainUserId?: string | number
|
||||||
|
planStartDate?: string
|
||||||
|
planEndDate?: string
|
||||||
|
scales?: number
|
||||||
|
taskGroupId?: number | string
|
||||||
|
}>
|
||||||
|
}>,
|
||||||
|
turbineList: [] as { id: number, turbineNo: string, lat?: number, lng?: number, status: 0 | 1 | 2 }[], // 风机组
|
||||||
})
|
})
|
||||||
|
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
|
@ -350,7 +500,7 @@ const pagination = reactive({
|
||||||
total: 0,
|
total: 0,
|
||||||
showTotal: true,
|
showTotal: true,
|
||||||
showJumper: true,
|
showJumper: true,
|
||||||
showPageSize: true
|
showPageSize: true,
|
||||||
})
|
})
|
||||||
const openMapModal = (item: any) => {
|
const openMapModal = (item: any) => {
|
||||||
Message.info(`地图选点功能待开发,当前机组编号:${item.turbineNo}`)
|
Message.info(`地图选点功能待开发,当前机组编号:${item.turbineNo}`)
|
||||||
|
@ -384,70 +534,70 @@ const tableColumns = ref<TableColumnData[]>([
|
||||||
slotName: 'fieldInfo',
|
slotName: 'fieldInfo',
|
||||||
minWidth: 180,
|
minWidth: 180,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
slotName: 'status',
|
slotName: 'status',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 100
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '委托单位',
|
title: '委托单位',
|
||||||
dataIndex: 'commissionUnit',
|
dataIndex: 'commissionUnit',
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '委托单位联系人/电话',
|
title: '委托单位联系人/电话',
|
||||||
slotName: 'commissionInfo',
|
slotName: 'commissionInfo',
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '业主',
|
title: '业主',
|
||||||
dataIndex: 'inspectionUnit',
|
dataIndex: 'inspectionUnit',
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '业主联系人/电话',
|
title: '业主联系人/电话',
|
||||||
slotName: 'inspectionInfo',
|
slotName: 'inspectionInfo',
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '项目规模',
|
title: '项目规模',
|
||||||
dataIndex: 'projectScale',
|
dataIndex: 'projectScale',
|
||||||
width: 100,
|
width: 100,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '机组型号',
|
title: '机组型号',
|
||||||
dataIndex: 'orgNumber',
|
dataIndex: 'orgNumber',
|
||||||
width: 100,
|
width: 100,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '项目经理/施工人员',
|
title: '项目经理/施工人员',
|
||||||
slotName: 'projectManager',
|
slotName: 'projectManager',
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '项目周期',
|
title: '项目周期',
|
||||||
slotName: 'projectPeriod',
|
slotName: 'projectPeriod',
|
||||||
minWidth: 180,
|
minWidth: 180,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
|
@ -492,7 +642,7 @@ const fetchData = async () => {
|
||||||
const params: ProjectPageQuery = {
|
const params: ProjectPageQuery = {
|
||||||
...searchForm,
|
...searchForm,
|
||||||
page: pagination.current,
|
page: pagination.current,
|
||||||
size: pagination.pageSize
|
size: pagination.pageSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await listProject(params)
|
const res = await listProject(params)
|
||||||
|
@ -516,9 +666,9 @@ const fetchData = async () => {
|
||||||
projectManager: item.projectManagerName,
|
projectManager: item.projectManagerName,
|
||||||
projectScale: item.scale,
|
projectScale: item.scale,
|
||||||
// 处理项目周期
|
// 处理项目周期
|
||||||
projectPeriod: item.startDate && item.endDate ? [item.startDate, item.endDate] : []
|
projectPeriod: item.startDate && item.endDate ? [item.startDate, item.endDate] : [],
|
||||||
};
|
}
|
||||||
return mappedItem;
|
return mappedItem
|
||||||
})
|
})
|
||||||
|
|
||||||
// 由于API没有返回total,使用当前数据长度
|
// 由于API没有返回total,使用当前数据长度
|
||||||
|
@ -553,7 +703,7 @@ const reset = () => {
|
||||||
// 重置搜索表单
|
// 重置搜索表单
|
||||||
Object.assign(searchForm, {
|
Object.assign(searchForm, {
|
||||||
projectName: '',
|
projectName: '',
|
||||||
fieldName: '', // 保持使用fieldName,因为API类型定义中使用的是这个字段名
|
fieldName: '', // 保持使用fieldName,因为API类型定义中使用的是这个字段名
|
||||||
status: undefined,
|
status: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -576,27 +726,29 @@ const onPageSizeChange = (pageSize: number) => {
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
// 重置表单字段
|
// 重置表单字段
|
||||||
Object.assign(form, {
|
Object.assign(form, {
|
||||||
projectId: '', // 项目id
|
projectId: '', // 项目id
|
||||||
projectName: '', // 项目名称
|
projectName: '', // 项目名称
|
||||||
projectManagerId: '', // 项目经理id
|
projectManagerId: '', // 项目经理id
|
||||||
client: '', // 委托单位
|
client: '', // 委托单位
|
||||||
clientContact: '', // 委托单位联系人
|
clientContact: '', // 委托单位联系人
|
||||||
clientPhone: '', // 委托单位联系电话
|
clientPhone: '', // 委托单位联系电话
|
||||||
inspectionUnit: '', // 检查单位
|
inspectionUnit: '', // 检查单位
|
||||||
inspectionContact: '', // 检查单位联系人
|
inspectionContact: '', // 检查单位联系人
|
||||||
inspectionPhone: '', // 检查单位联系电话
|
inspectionPhone: '', // 检查单位联系电话
|
||||||
farmName: '', // 风场名称
|
farmName: '', // 风场名称
|
||||||
farmAddress: '', // 风场地址
|
farmAddress: '', // 风场地址
|
||||||
scale: '', // 项目规模
|
projectOrigin: '', // 项目来源(必填)
|
||||||
turbineModel: '', // 风机型号
|
scale: '', // 项目规模
|
||||||
status: 0, // 状态:0待施工,1施工中,2已完工,3已审核,4已验收
|
turbineModel: '', // 风机型号
|
||||||
startDate: '', // 开始时间
|
status: 0, // 状态:0待施工,1施工中,2已完工,3已审核,4已验收
|
||||||
endDate: '', // 结束时间
|
startDate: '', // 开始时间
|
||||||
coverUrl: '', // 项目封面
|
endDate: '', // 结束时间
|
||||||
|
// coverUrl: '', // 项目封面(已废弃)
|
||||||
constructionTeamLeaderId: '', // 施工组长id
|
constructionTeamLeaderId: '', // 施工组长id
|
||||||
constructorIds: '', // 施工人员id
|
constructorIds: '', // 施工人员id
|
||||||
qualityOfficerId: '', // 质量员id
|
qualityOfficerId: '', // 质量员id
|
||||||
auditorId: '' // 安全员id
|
auditorId: '', // 安全员id
|
||||||
|
tasks: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
isEdit.value = false
|
isEdit.value = false
|
||||||
|
@ -607,21 +759,35 @@ const openAddModal = () => {
|
||||||
resetForm()
|
resetForm()
|
||||||
addModalVisible.value = true
|
addModalVisible.value = true
|
||||||
}
|
}
|
||||||
|
// 任务增删改(仅在新增/编辑弹窗内部使用)
|
||||||
|
const addTask = () => {
|
||||||
|
;(form.tasks as any[]).push({ taskName: '', taskCode: '', mainUserId: undefined, planStartDate: '', planEndDate: '', scales: undefined, taskGroupId: undefined, children: [] })
|
||||||
|
}
|
||||||
|
const removeTask = (index: number) => {
|
||||||
|
;(form.tasks as any[]).splice(index, 1)
|
||||||
|
}
|
||||||
|
const addSubtask = (parentIndex: number) => {
|
||||||
|
const list = (form.tasks as any[])
|
||||||
|
if (!list[parentIndex].children) list[parentIndex].children = []
|
||||||
|
list[parentIndex].children!.push({ taskName: '', taskCode: '', mainUserId: undefined, planStartDate: '', planEndDate: '', scales: undefined, taskGroupId: undefined })
|
||||||
|
}
|
||||||
|
const removeSubtask = (parentIndex: number, index: number) => {
|
||||||
|
const list = (form.tasks as any[])
|
||||||
|
list[parentIndex].children!.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const openEditModal = (record: T.ProjectResp) => {
|
const openEditModal = (record: T.ProjectResp) => {
|
||||||
isEdit.value = true
|
isEdit.value = true
|
||||||
currentId.value = record.id || record.projectId || null
|
currentId.value = record.id || record.projectId || null
|
||||||
|
|
||||||
// 重置表单
|
// 重置表单为默认(确保 tasks、turbineList 等为数组初始值)
|
||||||
Object.keys(form).forEach(key => {
|
resetForm()
|
||||||
// @ts-ignore
|
|
||||||
form[key] = ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// 填充表单数据
|
// 填充表单数据
|
||||||
Object.keys(form).forEach(key => {
|
Object.keys(form).forEach((key) => {
|
||||||
if (key in record && record[key as keyof T.ProjectResp] !== undefined) {
|
if (key in record && record[key as keyof T.ProjectResp] !== undefined) {
|
||||||
// @ts-ignore - 这里需要处理类型转换
|
// @ts-expect-error - 这里需要处理类型转换
|
||||||
form[key] = record[key as keyof T.ProjectResp]
|
form[key] = record[key as keyof T.ProjectResp]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -645,6 +811,7 @@ const openEditModal = (record: T.ProjectResp) => {
|
||||||
// 添加表单验证规则
|
// 添加表单验证规则
|
||||||
const formRules = {
|
const formRules = {
|
||||||
projectName: [{ required: true, message: '请输入项目名称' }],
|
projectName: [{ required: true, message: '请输入项目名称' }],
|
||||||
|
projectOrigin: [{ required: true, message: '请输入项目来源' }],
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加提交加载状态
|
// 添加提交加载状态
|
||||||
|
@ -660,15 +827,32 @@ const handleSubmit = async () => {
|
||||||
await formRef.value.validate()
|
await formRef.value.validate()
|
||||||
|
|
||||||
// 准备提交的数据
|
// 准备提交的数据
|
||||||
|
const normalizeDate = (d: any) => (d ? (typeof d === 'string' ? d : new Date(d).toISOString().split('T')[0]) : '')
|
||||||
|
|
||||||
|
// 任务结构转换(只转换日期,其余保持)
|
||||||
|
const mapTasks = (tasks: any[]) =>
|
||||||
|
(tasks || []).map(t => ({
|
||||||
|
...t,
|
||||||
|
planStartDate: normalizeDate(t.planStartDate),
|
||||||
|
planEndDate: normalizeDate(t.planEndDate),
|
||||||
|
children: (t.children || []).map((c: any) => ({
|
||||||
|
...c,
|
||||||
|
planStartDate: normalizeDate(c.planStartDate),
|
||||||
|
planEndDate: normalizeDate(c.planEndDate),
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
const submitData = {
|
const submitData = {
|
||||||
...form,
|
...form,
|
||||||
// 确保projectId字段正确
|
// 确保projectId字段正确
|
||||||
projectId: isEdit.value && currentId.value ? currentId.value : form.projectId,
|
projectId: isEdit.value && currentId.value ? currentId.value : form.projectId,
|
||||||
// 处理日期格式 - 确保是字符串格式 YYYY-MM-DD
|
// 处理日期格式 - 确保是字符串格式 YYYY-MM-DD
|
||||||
startDate: form.startDate ? (typeof form.startDate === 'string' ? form.startDate : new Date(form.startDate).toISOString().split('T')[0]) : '',
|
startDate: normalizeDate(form.startDate),
|
||||||
endDate: form.endDate ? (typeof form.endDate === 'string' ? form.endDate : new Date(form.endDate).toISOString().split('T')[0]) : '',
|
endDate: normalizeDate(form.endDate),
|
||||||
// 处理施工人员ID - 如果是数组,转换为逗号分隔的字符串
|
// 处理施工人员ID - 如果是数组,转换为逗号分隔的字符串
|
||||||
constructorIds: Array.isArray(form.constructorIds) ? form.constructorIds.join(',') : form.constructorIds
|
constructorIds: Array.isArray(form.constructorIds) ? form.constructorIds.join(',') : form.constructorIds,
|
||||||
|
// 新字段:任务
|
||||||
|
tasks: mapTasks(form.tasks as any[]),
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('提交数据:', submitData)
|
console.log('提交数据:', submitData)
|
||||||
|
@ -753,8 +937,8 @@ const viewDetail = (record: T.ProjectResp) => {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'ProjectDetail',
|
name: 'ProjectDetail',
|
||||||
params: {
|
params: {
|
||||||
id: projectId.toString()
|
id: projectId.toString(),
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -811,7 +995,7 @@ const exportData = async () => {
|
||||||
const params = {
|
const params = {
|
||||||
projectName: searchForm.projectName,
|
projectName: searchForm.projectName,
|
||||||
status: searchForm.status,
|
status: searchForm.status,
|
||||||
fieldName: searchForm.fieldName, // 保持使用fieldName,因为API类型定义中使用的是这个字段名
|
fieldName: searchForm.fieldName, // 保持使用fieldName,因为API类型定义中使用的是这个字段名
|
||||||
}
|
}
|
||||||
|
|
||||||
await exportProject(params)
|
await exportProject(params)
|
||||||
|
@ -829,9 +1013,9 @@ const fetchUserList = async () => {
|
||||||
// 调用用户列表接口
|
// 调用用户列表接口
|
||||||
const res = await http.get('/user/list')
|
const res = await http.get('/user/list')
|
||||||
if (res.data && Array.isArray(res.data)) {
|
if (res.data && Array.isArray(res.data)) {
|
||||||
userOptions.value = res.data.map(item => ({
|
userOptions.value = res.data.map((item) => ({
|
||||||
label: item.userName || item.username || item.name || item.nickName || item.account || '未命名用户',
|
label: item.userName || item.username || item.name || item.nickName || item.account || '未命名用户',
|
||||||
value: item.userId || item.id || ''
|
value: item.userId || item.id || '',
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
userOptions.value = []
|
userOptions.value = []
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
v-model="formData.receiptTime"
|
v-model="formData.receiptTime"
|
||||||
show-time
|
show-time
|
||||||
format="YYYY-MM-DD HH:mm:ss"
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
placeholder="请选择收货时间"
|
placeholder="请选择收货时间"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
|
@ -233,8 +234,9 @@ const emit = defineEmits<{
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据 - 使用正确的字段映射
|
||||||
const formData = reactive<ReceiptRequest>({
|
const formData = reactive<ReceiptRequest>({
|
||||||
|
// 收货特有信息
|
||||||
receiptTime: '',
|
receiptTime: '',
|
||||||
receiptPerson: '',
|
receiptPerson: '',
|
||||||
receiptQuantity: 1,
|
receiptQuantity: 1,
|
||||||
|
@ -247,6 +249,47 @@ const formData = reactive<ReceiptRequest>({
|
||||||
checkRemark: '',
|
checkRemark: '',
|
||||||
storageLocation: '',
|
storageLocation: '',
|
||||||
storageManager: '',
|
storageManager: '',
|
||||||
|
|
||||||
|
// 设备基本信息(从采购数据继承)
|
||||||
|
equipmentName: '',
|
||||||
|
equipmentModel: '',
|
||||||
|
equipmentType: '',
|
||||||
|
equipmentSn: '',
|
||||||
|
brand: '',
|
||||||
|
specification: '',
|
||||||
|
assetCode: '',
|
||||||
|
|
||||||
|
// 采购信息(从采购数据继承)
|
||||||
|
purchaseOrder: '',
|
||||||
|
supplierName: '',
|
||||||
|
purchasePrice: 0,
|
||||||
|
purchaseTime: '',
|
||||||
|
quantity: 1,
|
||||||
|
unitPrice: 0,
|
||||||
|
totalPrice: 0,
|
||||||
|
|
||||||
|
// 入库信息
|
||||||
|
inStockTime: '',
|
||||||
|
physicalLocation: '',
|
||||||
|
locationStatus: '',
|
||||||
|
responsiblePerson: '',
|
||||||
|
inventoryBarcode: '',
|
||||||
|
|
||||||
|
// 状态信息
|
||||||
|
equipmentStatus: '',
|
||||||
|
useStatus: '',
|
||||||
|
healthStatus: '',
|
||||||
|
receiptStatus: '',
|
||||||
|
|
||||||
|
// 其他管理信息
|
||||||
|
depreciationMethod: '',
|
||||||
|
depreciationYears: 5,
|
||||||
|
salvageValue: 0,
|
||||||
|
currentNetValue: 0,
|
||||||
|
|
||||||
|
// 系统字段
|
||||||
|
createTime: '',
|
||||||
|
updateTime: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
|
@ -287,28 +330,46 @@ const rules = {
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化表单数据
|
||||||
|
const initFormData = () => {
|
||||||
|
if (props.equipmentData) {
|
||||||
|
// 从设备数据中复制相关字段
|
||||||
|
Object.keys(formData).forEach((key) => {
|
||||||
|
const formKey = key as keyof ReceiptRequest
|
||||||
|
const equipmentKey = key as keyof EquipmentResp
|
||||||
|
if (formKey in formData && equipmentKey in props.equipmentData!) {
|
||||||
|
const value = props.equipmentData![equipmentKey]
|
||||||
|
if (value !== undefined) {
|
||||||
|
(formData[formKey] as any) = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置默认值
|
||||||
|
formData.receiptQuantity = props.equipmentData.quantity || 1
|
||||||
|
formData.storageLocation = props.equipmentData.physicalLocation || ''
|
||||||
|
formData.storageManager = props.equipmentData.responsiblePerson || ''
|
||||||
|
|
||||||
|
// 设置收货时间默认为当前时间
|
||||||
|
formData.receiptTime = formatDateTime(new Date())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 监听弹窗显示状态
|
// 监听弹窗显示状态
|
||||||
watch(() => props.visible, (visible) => {
|
watch(() => props.visible, (visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
// 重置表单
|
initFormData()
|
||||||
Object.assign(formData, {
|
|
||||||
receiptTime: '',
|
|
||||||
receiptPerson: '',
|
|
||||||
receiptQuantity: props.equipmentData?.quantity || 1,
|
|
||||||
receiptRemark: '',
|
|
||||||
appearanceCheck: '',
|
|
||||||
functionTest: '',
|
|
||||||
packageIntegrity: '',
|
|
||||||
accessoryIntegrity: '',
|
|
||||||
checkResult: 'PASS',
|
|
||||||
checkRemark: '',
|
|
||||||
storageLocation: '',
|
|
||||||
storageManager: '',
|
|
||||||
})
|
|
||||||
formRef.value?.clearValidate()
|
formRef.value?.clearValidate()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听设备数据变化
|
||||||
|
watch(() => props.equipmentData, () => {
|
||||||
|
if (props.visible && props.equipmentData) {
|
||||||
|
initFormData()
|
||||||
|
}
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -319,17 +380,77 @@ const handleSubmit = async () => {
|
||||||
throw new Error('设备ID不能为空')
|
throw new Error('设备ID不能为空')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化时间
|
console.log('📦 开始提交收货数据...')
|
||||||
const receiptTime = formData.receiptTime ? new Date(formData.receiptTime).toISOString() : new Date().toISOString()
|
console.log('📦 设备数据:', props.equipmentData)
|
||||||
|
console.log('📦 表单数据:', formData)
|
||||||
const requestData: ReceiptRequest = {
|
|
||||||
...formData,
|
// 构建收货请求数据
|
||||||
receiptTime,
|
const receiptData: ReceiptRequest = {
|
||||||
|
// 收货特有信息
|
||||||
|
receiptTime: formData.receiptTime ? formatDateTime(formData.receiptTime) : formatDateTime(new Date()),
|
||||||
|
receiptPerson: formData.receiptPerson,
|
||||||
|
receiptQuantity: formData.receiptQuantity,
|
||||||
|
receiptRemark: formData.receiptRemark,
|
||||||
|
appearanceCheck: formData.appearanceCheck,
|
||||||
|
functionTest: formData.functionTest,
|
||||||
|
packageIntegrity: formData.packageIntegrity,
|
||||||
|
accessoryIntegrity: formData.accessoryIntegrity,
|
||||||
|
checkResult: formData.checkResult,
|
||||||
|
checkRemark: formData.checkRemark,
|
||||||
|
storageLocation: formData.storageLocation,
|
||||||
|
storageManager: formData.storageManager,
|
||||||
|
|
||||||
|
// 设备基本信息(从采购数据继承)
|
||||||
|
equipmentName: props.equipmentData.equipmentName,
|
||||||
|
equipmentModel: props.equipmentData.equipmentModel,
|
||||||
|
equipmentType: props.equipmentData.equipmentType,
|
||||||
|
equipmentSn: props.equipmentData.equipmentSn,
|
||||||
|
brand: props.equipmentData.brand,
|
||||||
|
specification: props.equipmentData.specification,
|
||||||
|
assetCode: props.equipmentData.assetCode,
|
||||||
|
|
||||||
|
// 采购信息(从采购数据继承)
|
||||||
|
purchaseOrder: props.equipmentData.purchaseOrder,
|
||||||
|
supplierName: props.equipmentData.supplierName,
|
||||||
|
purchasePrice: props.equipmentData.purchasePrice,
|
||||||
|
purchaseTime: props.equipmentData.purchaseTime,
|
||||||
|
quantity: props.equipmentData.quantity,
|
||||||
|
unitPrice: props.equipmentData.unitPrice,
|
||||||
|
totalPrice: props.equipmentData.totalPrice,
|
||||||
|
|
||||||
|
// 入库信息
|
||||||
|
inStockTime: formData.receiptTime ? formatDateTime(formData.receiptTime) : formatDateTime(new Date()),
|
||||||
|
physicalLocation: formData.storageLocation,
|
||||||
|
locationStatus: 'in_stock',
|
||||||
|
responsiblePerson: formData.storageManager,
|
||||||
|
inventoryBarcode: props.equipmentData.inventoryBarcode || generateInventoryBarcode(),
|
||||||
|
|
||||||
|
// 状态信息
|
||||||
|
equipmentStatus: 'normal',
|
||||||
|
useStatus: '0',
|
||||||
|
healthStatus: 'good',
|
||||||
|
receiptStatus: 'RECEIVED',
|
||||||
|
|
||||||
|
// 其他管理信息
|
||||||
|
depreciationMethod: props.equipmentData.depreciationMethod || 'straight_line',
|
||||||
|
depreciationYears: props.equipmentData.depreciationYears || 5,
|
||||||
|
salvageValue: props.equipmentData.salvageValue || 0,
|
||||||
|
currentNetValue: props.equipmentData.purchasePrice || 0,
|
||||||
|
|
||||||
|
// 系统字段
|
||||||
|
createTime: formatDateTime(new Date()),
|
||||||
|
updateTime: formatDateTime(new Date())
|
||||||
}
|
}
|
||||||
|
|
||||||
await equipmentProcurementApi.receiveGoods(props.equipmentData.equipmentId, requestData)
|
console.log('📦 构建的收货数据:', receiptData)
|
||||||
|
|
||||||
|
// 调用收货API
|
||||||
|
await equipmentProcurementApi.receiveGoods(
|
||||||
|
props.equipmentData.equipmentId,
|
||||||
|
receiptData
|
||||||
|
)
|
||||||
|
|
||||||
Message.success('收货成功')
|
Message.success('收货成功,设备已自动入库')
|
||||||
emit('success')
|
emit('success')
|
||||||
emit('update:visible', false)
|
emit('update:visible', false)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
@ -340,6 +461,26 @@ const handleSubmit = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成库存条码的辅助函数
|
||||||
|
const generateInventoryBarcode = () => {
|
||||||
|
const timestamp = Date.now().toString(36)
|
||||||
|
const random = Math.random().toString(36).substr(2, 5)
|
||||||
|
return `INV-${timestamp}-${random}`.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期时间
|
||||||
|
const formatDateTime = (date: string | Date) => {
|
||||||
|
const d = new Date(date);
|
||||||
|
let month = '' + (d.getMonth() + 1);
|
||||||
|
let day = '' + d.getDate();
|
||||||
|
const year = d.getFullYear();
|
||||||
|
if (month.length < 2)
|
||||||
|
month = '0' + month;
|
||||||
|
if (day.length < 2)
|
||||||
|
day = '0' + day;
|
||||||
|
return [year, month, day].join('-') + ' ' + [d.getHours(), d.getMinutes(), d.getSeconds()].join(':');
|
||||||
|
}
|
||||||
|
|
||||||
// 取消
|
// 取消
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
emit('update:visible', false)
|
emit('update:visible', false)
|
||||||
|
|
Loading…
Reference in New Issue