| | |
| | | <template> |
| | | <view class="container"> |
| | | <view v-for="(item, index) in list" :key="index" class="card"> |
| | | <view class="card-content"> |
| | | <view class="item-row"> |
| | | <text class="label">项目编码:</text> |
| | | <text class="value">{{ item.code }}</text> |
| | | <view class="container"> |
| | | <!-- 搜索区域 --> |
| | | <view class="search-container"> |
| | | <view class="search-box"> |
| | | <view class="search-item" @click="showProjectCodeSelector"> |
| | | <text class="search-label">{{ searchParams.projectCode || '项目编码' }}</text> |
| | | <text class="triangle-down">▼</text> |
| | | </view> |
| | | <view class="search-item" @click="showProjectNameSelector"> |
| | | <text class="search-label">{{ searchParams.projectName || '项目名称' }}</text> |
| | | <text class="triangle-down">▼</text> |
| | | </view> |
| | | <!-- 添加重置按钮 --> |
| | | <view class="reset-btn" @click="resetSearch" v-if="searchParams.projectCode || searchParams.projectName"> |
| | | <uni-icons type="clear" size="14"></uni-icons> |
| | | </view> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="label">项目名称:</text> |
| | | <text class="value">{{ item.name }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="label">工时:</text> |
| | | <text class="value">{{ item.hours }} 小时</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 项目编码选择弹出层 --> |
| | | <uni-popup |
| | | ref="projectCodePopup" |
| | | type="bottom" |
| | | @change="(e) => onPopupChange('projectCode', e.show)" |
| | | > |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">选择项目编码</text> |
| | | <text class="popup-close" @click="closeProjectCodePopup">×</text> |
| | | </view> |
| | | <!-- 添加搜索框 --> |
| | | <view class="popup-search"> |
| | | <uni-easyinput |
| | | v-model="projectCodeKeyword" |
| | | placeholder="搜索项目编码" |
| | | prefixIcon="search" |
| | | /> |
| | | </view> |
| | | <scroll-view scroll-y class="popup-list"> |
| | | <view v-if="!allProjectCodes.size" class="popup-loading"> |
| | | <uni-load-more status="loading" :content-text="loadingText"></uni-load-more> |
| | | </view> |
| | | <view |
| | | v-else |
| | | class="popup-item" |
| | | v-for="item in filteredProjectCodes" |
| | | :key="item" |
| | | @click="selectProjectCode(item)" |
| | | > |
| | | {{ item }} |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </uni-popup> |
| | | |
| | | <!-- 项目名称选择弹出层 --> |
| | | <uni-popup |
| | | ref="projectNamePopup" |
| | | type="bottom" |
| | | @change="(e) => onPopupChange('projectName', e.show)" |
| | | > |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">选择项目名称</text> |
| | | <text class="popup-close" @click="closeProjectNamePopup">×</text> |
| | | </view> |
| | | <!-- 添加搜索框 --> |
| | | <view class="popup-search"> |
| | | <uni-easyinput |
| | | v-model="projectNameKeyword" |
| | | placeholder="搜索项目名称" |
| | | prefixIcon="search" |
| | | /> |
| | | </view> |
| | | <scroll-view scroll-y class="popup-list"> |
| | | <view v-if="!allProjectNames.size" class="popup-loading"> |
| | | <uni-load-more status="loading" :content-text="loadingText"></uni-load-more> |
| | | </view> |
| | | <view |
| | | v-else |
| | | class="popup-item" |
| | | v-for="item in filteredProjectNames" |
| | | :key="item" |
| | | @click="selectProjectName(item)" |
| | | > |
| | | {{ item }} |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </uni-popup> |
| | | |
| | | <!-- 表格内容区域 --> |
| | | <view class="table-container"> |
| | | <view class="table-header"> |
| | | <text class="header-title">工时统计列表</text> |
| | | <text class="total-count">共 {{ total }} 条</text> |
| | | </view> |
| | | |
| | | <!-- 表格内容 --> |
| | | <view class="table-content" v-if="!loading"> |
| | | <view v-for="(item, index) in tableData" |
| | | :key="index" |
| | | class="card" |
| | | @click="handleCardClick(item)" |
| | | > |
| | | <view class="card-content"> |
| | | <view class="item-row"> |
| | | <text class="label">项目编码:</text> |
| | | <text class="value">{{ item.projectCode }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="label">项目名称:</text> |
| | | <text class="value">{{ item.projectName }}</text> |
| | | </view> |
| | | <!-- <view class="item-row"> |
| | | <text class="label">工时:</text> |
| | | <text class="value highlight">{{ item.workTime }} 小时</text> |
| | | </view> --> |
| | | <view class="item-row"> |
| | | <text class="label">工时:</text> |
| | | <text class="value highlight">{{ Number(item.workTime).toFixed(1) }} 小时</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="label">工种:</text> |
| | | <text class="value">{{ item.workType }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="label">项目阶段:</text> |
| | | <text class="value">{{ item.projectPhase }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="label">报工时间:</text> |
| | | <text class="value">{{ item.reportTime }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 加载状态 --> |
| | | <view class="loading-container" v-if="loading"> |
| | | <uni-load-more status="loading"></uni-load-more> |
| | | </view> |
| | | |
| | | <!-- 空状态 --> |
| | | <view class="empty-container" v-if="!loading && (!tableData || tableData.length === 0)"> |
| | | <image src="/static/empty.png" mode="aspectFit" class="empty-image"></image> |
| | | <text class="empty-text">暂无数据</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | data() { |
| | | return { |
| | | list: [ |
| | | { code: 'P001', name: '项目A', hours: 10 }, |
| | | { code: 'P002', name: '项目B', hours: 15 }, |
| | | { code: 'P003', name: '项目C', hours: 8 }, |
| | | { code: 'P004', name: '项目D', hours: 20 }, |
| | | { code: 'P005', name: '项目E', hours: 12 } |
| | | ] |
| | | }; |
| | | } |
| | | }; |
| | | import { listWorkHourInfoNoPage } from '@/api/dema/workHourInfo.js' |
| | | |
| | | export default { |
| | | data() { |
| | | return { |
| | | searchParams: { |
| | | projectCode: '', |
| | | projectName: '', |
| | | pageNum: 1, |
| | | pageSize: 10 |
| | | }, |
| | | loading: false, |
| | | tableData: [], |
| | | total: 0, |
| | | // 项目编码和名称列表 |
| | | projectCodes: [], |
| | | projectNames: [], |
| | | // 搜索关键词 |
| | | projectCodeKeyword: '', |
| | | projectNameKeyword: '', |
| | | // 用于存储所有不重复的项目编码和名称 |
| | | allProjectCodes: new Set(), |
| | | allProjectNames: new Set(), |
| | | loadingText: { |
| | | contentdown: '加载中...', |
| | | contentrefresh: '加载中...', |
| | | contentnomore: '没有更多数据' |
| | | }, |
| | | // 添加弹出层状态控制 |
| | | popupStatus: { |
| | | projectCode: false, |
| | | projectName: false |
| | | }, |
| | | // 添加工种和项目阶段的选项数据 |
| | | typeSelect: [ |
| | | { value: '软件', text: "机械设计" }, |
| | | { value: '电气设计', text: "电气设计" }, |
| | | { value: '软件', text: "软件" }, |
| | | { value: '软件', text: "视觉" }, |
| | | { value: '电气调试', text: "电气调试" }, |
| | | { value: '电工', text: "电工" }, |
| | | { value: '钳工', text: "钳工" }, |
| | | { value: '现场经理', text: "现场经理" }, |
| | | ], |
| | | phaseSelect: [ |
| | | { value: '场内设计', text: "场内设计" }, |
| | | { value: '场内装配', text: "场内装配" }, |
| | | { value: '场内调试', text: "场内调试" }, |
| | | { value: '场外装配', text: "场外装配" }, |
| | | { value: '场外调试', text: "场外调试" }, |
| | | { value: '试生产', text: "试生产" }, |
| | | { value: '陪产', text: "陪产" }, |
| | | { value: '终验收', text: "终验收" }, |
| | | ], |
| | | } |
| | | }, |
| | | computed: { |
| | | // 过滤后的项目编码列表 |
| | | filteredProjectCodes() { |
| | | if (!this.projectCodeKeyword) return Array.from(this.allProjectCodes); |
| | | return Array.from(this.allProjectCodes).filter(code => |
| | | code.toLowerCase().includes(this.projectCodeKeyword.toLowerCase()) |
| | | ); |
| | | }, |
| | | // 过滤后的项目名称列表 |
| | | filteredProjectNames() { |
| | | if (!this.projectNameKeyword) return Array.from(this.allProjectNames); |
| | | return Array.from(this.allProjectNames).filter(name => |
| | | name.toLowerCase().includes(this.projectNameKeyword.toLowerCase()) |
| | | ); |
| | | } |
| | | }, |
| | | methods: { |
| | | // 修改显示项目编码选择器的方法 |
| | | showProjectCodeSelector() { |
| | | if (this.popupStatus.projectCode) { |
| | | // 如果当前是打开状态,则关闭 |
| | | this.closeProjectCodePopup(); |
| | | } else { |
| | | // 如果当前是关闭状态,则打开(同时关闭另一个) |
| | | this.closeProjectNamePopup(); // 关闭项目名称选择器 |
| | | this.$refs.projectCodePopup.open(); |
| | | this.popupStatus.projectCode = true; |
| | | } |
| | | }, |
| | | |
| | | // 修改显示项目名称选择器的方法 |
| | | showProjectNameSelector() { |
| | | if (this.popupStatus.projectName) { |
| | | // 如果当前是打开状态,则关闭 |
| | | this.closeProjectNamePopup(); |
| | | } else { |
| | | // 如果当前是关闭状态,则打开(同时关闭另一个) |
| | | this.closeProjectCodePopup(); // 关闭项目编码选择器 |
| | | this.$refs.projectNamePopup.open(); |
| | | this.popupStatus.projectName = true; |
| | | } |
| | | }, |
| | | |
| | | // 修改关闭方法 |
| | | closeProjectCodePopup() { |
| | | this.$refs.projectCodePopup.close(); |
| | | this.projectCodeKeyword = ''; |
| | | this.popupStatus.projectCode = false; |
| | | }, |
| | | |
| | | closeProjectNamePopup() { |
| | | this.$refs.projectNamePopup.close(); |
| | | this.projectNameKeyword = ''; |
| | | this.popupStatus.projectName = false; |
| | | }, |
| | | |
| | | // 选择项目编码 |
| | | selectProjectCode(code) { |
| | | this.searchParams.projectCode = code |
| | | this.closeProjectCodePopup() |
| | | this.getList() |
| | | }, |
| | | // 选择项目名称 |
| | | selectProjectName(name) { |
| | | this.searchParams.projectName = name |
| | | this.closeProjectNamePopup() |
| | | this.getList() |
| | | }, |
| | | // 获取列表数据 |
| | | getList() { |
| | | this.loading = true |
| | | this.tableData = [] // 清空现有数据 |
| | | |
| | | listWorkHourInfoNoPage(this.searchParams).then(response => { |
| | | this.tableData = response.rows || [] |
| | | this.total = response.total || 0 |
| | | |
| | | // 更新项目编码和名称集合 |
| | | this.tableData.forEach(item => { |
| | | if (item.projectCode) this.allProjectCodes.add(item.projectCode); |
| | | if (item.projectName) this.allProjectNames.add(item.projectName); |
| | | }); |
| | | |
| | | this.loading = false |
| | | }).catch(() => { |
| | | this.loading = false |
| | | this.$modal.msgError('获取数据失败') |
| | | }) |
| | | }, |
| | | // 初始化 |
| | | async init() { |
| | | try { |
| | | // 先获取所有项目数据用于检索 |
| | | await this.getAllProjects(); |
| | | // 然后获取当前页的数据 |
| | | await this.getList(); |
| | | } catch (error) { |
| | | console.error('初始化失败:', error); |
| | | } |
| | | }, |
| | | // 重置搜索 |
| | | resetSearch() { |
| | | this.searchParams.projectCode = '' |
| | | this.searchParams.projectName = '' |
| | | this.projectCodeKeyword = '' |
| | | this.projectNameKeyword = '' |
| | | this.getList() |
| | | }, |
| | | // 获取所有项目数据 |
| | | async getAllProjects() { |
| | | try { |
| | | this.loading = true; |
| | | const response = await listWorkHourInfoNoPage({ |
| | | pageNum: 1, |
| | | pageSize: 999 // 获取足够多的数据以获取所有不重复的项目 |
| | | }); |
| | | |
| | | if (response && response.rows) { |
| | | // 清空现有集合 |
| | | this.allProjectCodes.clear(); |
| | | this.allProjectNames.clear(); |
| | | |
| | | // 添加新数据 |
| | | response.rows.forEach(item => { |
| | | if (item.projectCode) this.allProjectCodes.add(item.projectCode); |
| | | if (item.projectName) this.allProjectNames.add(item.projectName); |
| | | }); |
| | | } |
| | | this.loading = false; |
| | | } catch (error) { |
| | | console.error('获取项目列表失败:', error); |
| | | this.loading = false; |
| | | this.$modal.msgError('获取项目列表失败'); |
| | | } |
| | | }, |
| | | // 添加弹出层状态监听 |
| | | onPopupChange(type, show) { |
| | | this.popupStatus[type] = show; |
| | | }, |
| | | // 修改卡片点击处理方法 |
| | | handleCardClick(item) { |
| | | // 检查是否是当天的记录 |
| | | const today = new Date().toISOString().split('T')[0]; // 获取当前日期 YYYY-MM-DD |
| | | const reportDate = item.reportTime.split(' ')[0]; // 获取报工日期部分 YYYY-MM-DD |
| | | |
| | | if (reportDate === today) { |
| | | // 是当天的记录,允许编辑 |
| | | const params = { |
| | | workHourId: item.workHourId, |
| | | projectCode: item.projectCode, |
| | | projectName: item.projectName, |
| | | projectPhase: item.projectPhase, |
| | | workType: item.workType, |
| | | workTime: item.workTime, |
| | | details: item.details, |
| | | isEdit: true |
| | | }; |
| | | |
| | | uni.navigateTo({ |
| | | url: '/pages/dema/workHour/workHour?' + this.parseParams(params) |
| | | }); |
| | | } else { |
| | | // 不是当天的记录,显示警告 |
| | | this.$modal.msgError("只能修改当天的工时记录!"); |
| | | } |
| | | }, |
| | | |
| | | // 添加参数解析方法 |
| | | parseParams(params) { |
| | | return Object.keys(params) |
| | | .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`) |
| | | .join('&'); |
| | | }, |
| | | // 获取工种文本 |
| | | getWorkTypeText(value) { |
| | | const item = this.typeSelect.find(item => item.value === value); |
| | | return item ? item.text : value; |
| | | }, |
| | | // 获取项目阶段文本 |
| | | getPhaseText(value) { |
| | | const item = this.phaseSelect.find(item => item.value === value); |
| | | return item ? item.text : value; |
| | | }, |
| | | // 添加日期格式化辅助方法 |
| | | formatDate(date) { |
| | | const d = new Date(date); |
| | | const year = d.getFullYear(); |
| | | const month = String(d.getMonth() + 1).padStart(2, '0'); |
| | | const day = String(d.getDate()).padStart(2, '0'); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | }, |
| | | async created() { |
| | | // 在组件创建时就开始初始化 |
| | | await this.init(); |
| | | }, |
| | | mounted() { |
| | | // 如果需要其他初始化操作,放在这里 |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style> |
| | | .container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | padding: 10px; |
| | | } |
| | | .container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | width: 100%; |
| | | } |
| | | |
| | | .card { |
| | | width: 100%; |
| | | background-color: #f9f9f9; |
| | | border: 1px solid #ddd; |
| | | border-radius: 8px; |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | .search-container { |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 100; |
| | | background-color: #fff; |
| | | padding: 12px 0; |
| | | border-bottom: 1px solid #eee; |
| | | box-shadow: 0 2px 4px rgba(0,0,0,0.05); |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | .search-box { |
| | | position: relative; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .item-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | .search-item { |
| | | min-width: 120px; |
| | | padding: 8px 15px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .label { |
| | | font-size: 16px; |
| | | text-align: left; |
| | | } |
| | | .search-item:hover { |
| | | background-color: #e8f4ff; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 16px; |
| | | text-align: right; |
| | | } |
| | | .search-label { |
| | | color: #333; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .triangle-down { |
| | | margin-left: 5px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .reset-btn { |
| | | position: absolute; |
| | | right: 20px; |
| | | padding: 5px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .popup-content { |
| | | background-color: #fff; |
| | | border-radius: 16px 16px 0 0; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding-bottom: 10px; |
| | | border-bottom: 1px solid #eee; |
| | | } |
| | | |
| | | .popup-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .popup-close { |
| | | font-size: 20px; |
| | | color: #666; |
| | | padding: 0 10px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .popup-search { |
| | | padding: 10px 0; |
| | | border-bottom: 1px solid #ebeef5; |
| | | } |
| | | |
| | | .popup-list { |
| | | margin-top: 10px; |
| | | max-height: 40vh; |
| | | } |
| | | |
| | | .popup-item { |
| | | padding: 12px 15px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .popup-item:hover { |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .popup-item:active { |
| | | background-color: #e8f4ff; |
| | | } |
| | | |
| | | .table-container { |
| | | flex: 1; |
| | | padding: 15px; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .table-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | padding: 0 10px; |
| | | } |
| | | |
| | | .header-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .total-count { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .table-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .card { |
| | | background-color: #fff; |
| | | border-radius: 8px; |
| | | padding: 15px; |
| | | box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05); |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .card:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 12px 0 rgba(0,0,0,0.1); |
| | | } |
| | | |
| | | .item-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 8px 0; |
| | | border-bottom: 1px solid #ebeef5; |
| | | } |
| | | |
| | | .item-row:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .label { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | color: #333; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .highlight { |
| | | color: #409eff; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .loading-container { |
| | | padding: 20px 0; |
| | | display: flex; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .empty-container { |
| | | padding: 40px 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .empty-image { |
| | | width: 120px; |
| | | height: 120px; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .empty-text { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .popup-loading { |
| | | padding: 20px 0; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | } |
| | | </style> |