Compare commits

...

2 Commits

Author SHA1 Message Date
wuxueyu 48f4020fc8 Merge branch 'main' of http://pms.dtyx.net:3000/wuxueyu/Industrial-image-management-system---web
# Conflicts:
#	.env.development
#	src/views/project-management/bidding/information-retrieval/index.vue
2025-08-06 00:05:20 +08:00
wuxueyu 0eb12b4df9 信息检索模块新增爬虫设置、检索信息列表展示、信息详情查看 2025-08-06 00:03:43 +08:00
2 changed files with 415 additions and 1 deletions

View File

@ -5,7 +5,6 @@ VITE_API_PREFIX = '/dev-api'
# 接口地址
# VITE_API_BASE_URL = 'http://pms.dtyx.net:9158/'
VITE_API_BASE_URL = 'http://localhost:8888/'
# VITE_API_BASE_URL = 'http://10.18.34.213:8888/'
# 接口地址 (WebSocket)
VITE_API_WS_URL = 'ws://localhost:8000'

View File

@ -0,0 +1,415 @@
<template>
<GiPageLayout>
<div class="project-list-container">
<!-- 顶部搜索和操作区域 -->
<div class="header-section">
<div class="search-area">
<a-input-search
v-model="searchKeyword"
placeholder="搜索项目名称"
allow-clear
@search="handleSearch"
@clear="handleClear"
style="width: 300px"
/>
</div>
<div class="action-area">
<a-space>
<a-button type="primary" @click="startCrawler">
<template #icon><icon-play-arrow /></template>
开始爬虫
</a-button>
<a-button @click="refreshData">
<template #icon><icon-refresh /></template>
刷新数据
</a-button>
<a-button @click="exportData">
<template #icon><icon-download /></template>
导出数据
</a-button>
<a-button @click="openCrawlerSettings">
<template #icon><icon-settings /></template>
爬虫设置
</a-button>
</a-space>
</div>
</div>
<!-- 选项卡区域 -->
<div class="tabs-section">
<a-tabs
v-model:active-key="activeTab"
type="line"
size="large"
@change="handleTabChange"
>
<a-tab-pane key="all" tab="信息检索" title="信息检索">
<ProjectTable
:data="pagedData"
:loading="loading"
@view="handleView"
@enter="handleEnter"
/>
<div class="pagination-wrapper">
<a-pagination
v-model:current="currentPage"
v-model:page-size="pageSize"
:total="totalItems"
show-total
show-jumper
show-page-size
:page-size-options="[10, 20, 50, 100]"
@change="handlePageChange"
/>
</div>
</a-tab-pane>
<a-tab-pane key="my" tab="投标响应" title="投标响应">
<ProjectTable
:data="myPagedData"
:loading="loading"
@view="handleView"
@enter="handleEnter"
/>
<div class="pagination-wrapper">
<a-pagination
v-model:current="myCurrentPage"
v-model:page-size="pageSize"
:total="myTotalItems"
show-total
show-jumper
show-page-size
:page-size-options="[10, 20, 50, 100]"
@change="handleMyPageChange"
/>
</div>
</a-tab-pane>
</a-tabs>
</div>
<CrawlerSettings
v-model:visible="crawlerSettingsVisible"
@save="handleSaveSettings"
/>
</div>
</GiPageLayout>
<BiddingDetailModal
v-model:visible="detailVisible"
:detail="currentDetail"
:uploading="uploading"
@upload="handleUploadDocument"
/>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue'
import ProjectTable from './components/InformationTable.vue'
import CrawlerSettings from './components/CrawlerSettings.vue'
import BiddingDetailModal from './components/BiddingDetailModal.vue'
import type { ProjectData, TabKey,BiddingDetail } from './types'
import { ProjectStatus } from './types'
defineOptions({ name: 'ProjectList' })
//
const loading = ref(false)
const activeTab = ref<TabKey>('all')
const searchKeyword = ref('')
const crawlerSettingsVisible = ref(false)
//
const currentPage = ref(1)
const myCurrentPage = ref(1)
const pageSize = ref(10)
//
const detailVisible = ref(false)
const uploading = ref(false)
const currentDetail = ref<BiddingDetail>({
id: 0,
projectName: '',
biddingUnit: '',
budgetAmount: 0,
deadline: '',
crawlingTime: '',
projectLocation: '',
projectDuration: '',
biddingScope: '',
qualificationRequirements: '',
sourcePlatform: '',
biddingDocuments: '',
biddingContent: '',
contentItems: []
})
//
const projectList = ref<ProjectData[]>([
{
id: 1,
projectName: 'A风场2023年检查',
biddingUnit: 15,
budgetAmount:111,
deadline: '2023-10-01',
crawlingTime: '2023-12-31',
sourcePlatform:"中国招标投标网",
biddingDocuments:"333.pdf",
status: ProjectStatus.IN_PROGRESS,
manager: '张三',
isMyProject: true
},
//
...Array.from({ length: 25 }, (_, i) => ({
id: i + 2,
projectName: `项目${i + 2}`,
biddingUnit: Math.floor(Math.random() * 20) + 1,
budgetAmount: Math.floor(Math.random() * 1000) + 100,
deadline: `2023-${(Math.floor(Math.random() * 12) + 1).toString().padStart(2, '0')}-${(Math.floor(Math.random() * 28) + 1).toString().padStart(2, '0')}`,
crawlingTime: `2023-${(Math.floor(Math.random() * 12) + 1).toString().padStart(2, '0')}-${(Math.floor(Math.random() * 28) + 1).toString().padStart(2, '0')}`,
sourcePlatform: "中国招标投标网",
biddingDocuments: `文档${i + 2}.pdf`,
status: Math.random() > 0.5 ? ProjectStatus.IN_PROGRESS : ProjectStatus.COMPLETED,
manager: ['张三', '李四', '王五'][Math.floor(Math.random() * 3)],
isMyProject: Math.random() > 0.7
}))
])
const handleSaveSettings = (settings: any) => {
console.log('Saved settings:', settings)
}
//
const handleView = (project: ProjectData) => {
// ID
currentDetail.value = {
id: project.id,
projectName: project.projectName,
biddingUnit: project.biddingUnit.toString(),
budgetAmount: project.budgetAmount,
deadline: project.deadline,
crawlingTime: project.crawlingTime,
projectLocation: '内蒙古自治区', //
projectDuration: '6个月', //
biddingScope: '风电场50台机组叶片检查服务', //
qualificationRequirements: '具备风电叶片检查资质', //
sourcePlatform: project.sourcePlatform,
biddingDocuments: project.biddingDocuments,
biddingContent: '本项目为某风电场2023年叶片检查服务采购包括但不限于',
contentItems: [
'叶片外观检查',
'无损检测',
'缺陷记录与评估',
'检查报告编制'
]
}
detailVisible.value = true
}
// -
const filteredProjects = computed(() => {
if (!searchKeyword.value) {
return projectList.value
}
return projectList.value.filter(project =>
project.projectName.toLowerCase().includes(searchKeyword.value.toLowerCase())
)
})
// -
const myProjects = computed(() => {
return filteredProjects.value.filter(project => project.isMyProject)
})
//
const totalItems = computed(() => filteredProjects.value.length)
const myTotalItems = computed(() => myProjects.value.length)
const pagedData = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return filteredProjects.value.slice(start, end)
})
const myPagedData = computed(() => {
const start = (myCurrentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return myProjects.value.slice(start, end)
})
//
const handleSearch = (value: string) => {
searchKeyword.value = value
currentPage.value = 1 //
myCurrentPage.value = 1
}
const handleClear = () => {
searchKeyword.value = ''
currentPage.value = 1 //
myCurrentPage.value = 1
}
//
const handleTabChange = (key: string) => {
activeTab.value = key as TabKey
}
//
const handlePageChange = (page: number) => {
currentPage.value = page
}
const handleMyPageChange = (page: number) => {
myCurrentPage.value = page
}
//
const refreshData = async () => {
loading.value = true
try {
// API
await new Promise(resolve => setTimeout(resolve, 800))
Message.success('数据已刷新')
} catch (error) {
Message.error('刷新失败')
} finally {
loading.value = false
}
}
//
const startCrawler = () => {
Message.info('开始爬虫任务')
//
}
//
const exportData = () => {
Message.info('导出数据')
//
}
//
const openCrawlerSettings = () => {
crawlerSettingsVisible.value = true
}
const handleUploadDocument = async (file: File) => {
uploading.value = true
try {
const formData = new FormData()
formData.append('file', file)
formData.append('projectId', currentDetail.value.id.toString())
// API
const response = await uploadBiddingDocument(formData)
Message.success('文件上传成功')
currentDetail.value.biddingDocuments = response.data.fileUrl
} catch (error) {
Message.error(`文件上传失败: ${error.message}`)
} finally {
uploading.value = false
}
}
// API
const uploadBiddingDocument = (formData: FormData) => {
return new Promise((resolve) => {
setTimeout(() => {
const file = formData.get('file') as File
const projectId = formData.get('projectId')
const extension = file.name.split('.').pop()
resolve({
data: {
fileUrl: `https://your-api-domain.com/uploads/${projectId}.${extension}`
}
})
}, 1500)
})
}
onMounted(() => {
//
})
</script>
<style scoped lang="less">
.project-list-container {
height: 100%;
display: flex;
flex-direction: column;
}
.header-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 0 4px;
}
.search-area {
flex: 1;
}
.action-area {
display: flex;
gap: 12px;
align-items: center;
}
.tabs-section {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.pagination-wrapper {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
:deep(.arco-tabs-content) {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
:deep(.arco-tabs-pane) {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
:deep(.arco-tabs-nav-tab) {
font-weight: 500;
font-size: 16px;
}
:deep(.arco-tabs-nav-tab-list) {
padding: 0 4px;
}
//
@media (max-width: 768px) {
.header-section {
flex-direction: column;
gap: 16px;
align-items: stretch;
}
.search-area {
flex: none;
}
.action-area {
justify-content: center;
}
}
</style>