| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- <template>
- <uv-popup ref="popupRef" mode="bottom" round="20" :safeAreaInsetBottom="true">
- <view class="popup-container">
- <view class="popup-header">
- <text class="title">中检详情</text>
- <uv-icon name="close" size="36rpx" color="#999" @click="close"></uv-icon>
- </view>
- <scroll-view class="popup-content" scroll-y v-if="!projectStore.fetchInspListLoading">
-
- <uv-tabs :list="tabList" :current="currentTab" @click="handleTabClick" lineColor="#1c9bfd" activeColor="#1c9bfd" inactiveColor="#666"></uv-tabs>
- <view class="tab-pane" v-show="currentTab === 0">
- <!-- 基本信息 -->
- <CommonSection title="基本信息">
- <CommonInfoRow label="中检批次名称" :value="projectStore.inspDetailData.batchName" />
- <CommonInfoRow
- label="中检日期"
- :value="(projectStore.inspDetailData.batchStartDate ? formatDate(projectStore.inspDetailData.batchStartDate) : '--') + ' ~ ' + (projectStore.inspDetailData.batchEndDate ? formatDate(projectStore.inspDetailData.batchEndDate) : '--')"
- />
- <view v-if="batchInformList && batchInformList.length > 0" style="margin-top: 20rpx;">
- <CommonFileList :files="batchInformList.map((f: any) => ({ fileName: f.name, fileUrl: f.url }))" />
- </view>
- </CommonSection>
- </view>
- <view class="tab-pane" v-show="currentTab === 1">
- <!-- 中检信息 (普通项目展示方式) -->
- <template v-if="localProjectType !== 'horizontal'">
- <CommonSection title="中检信息">
- <CommonInfoRow label="项目实施周期" :value="projectStore.inspDetailData.implementCycle + ' 月'" />
- <CommonInfoRow label="项目进行时间" :value="projectStore.inspDetailData.implementExecute + ' 月'" />
- <CommonInfoRow label="项目完成占比" :value="projectStore.inspDetailData.implementCycleProp + ' %'" />
- <CommonInfoRow label="项目经费总额" :value="formatWithComma(projectStore.inspDetailData.fundsTotal) + ' 元'" isAmount noBorder />
- </CommonSection>
- </template>
- <!-- 中检信息 (横向项目展示方式) -->
- <template v-else>
- <CommonSection title="中检信息">
- <CommonInfoRow label="项目名称" :value="projectStore.inspDetailData.projectTitle" />
- <CommonInfoRow
- label="项目日期"
- :value="formatDate(projectStore.inspDetailData.projectStartDate) + ' ~ ' + formatDate(projectStore.inspDetailData.projectEndDate)"
- />
- <CommonInfoRow label="实施周期" :value="projectStore.inspDetailData.implCycle" />
- <CommonInfoRow label="项目完成占比" :value="projectStore.inspDetailData.implementCycleProp + '%'" />
- <CommonInfoRow
- :label="projectStore.inspDetailData.isClinicalTrial == '10' ? '合同经费' : '科研经费'"
- :value="formatWithComma(projectStore.inspDetailData.fundsTotal) + ' 元'"
- isAmount
- />
- <CommonInfoRow label="经费使用总额" :value="formatWithComma(projectStore.inspDetailData.fundsUsed) + ' 元'" />
- <CommonInfoRow label="费用使用占比" :value="(projectStore.inspDetailData.fundsProp || '0') + '%'" />
- <CommonInfoRow label="项目进展" :value="projectStore.inspDetailData.projectProgress" isColumn noBorder />
- </CommonSection>
- </template>
- <!-- 附件信息 -->
- <CommonSection title="附件信息" v-if="projectStore.inspDetailData.fileList && projectStore.inspDetailData.fileList.length > 0">
- <CommonFileList :files="projectStore.inspDetailData.fileList" />
- </CommonSection>
- </view>
-
- <view class="tab-pane" v-show="currentTab === 2">
- <!-- 审批信息 -->
- <CommonSection title="审批信息" v-if="projectStore.inspDetailData.approvalStatus > 10">
- <FlowTable
- :id="projectStore.inspDetailData.id"
- :businessCode="String(projectStore.inspDetailData.id)"
- defCode="sci_project_inspection"
- />
- </CommonSection>
- <uv-empty v-else mode="data" text="暂无审批信息"></uv-empty>
- </view>
- </scroll-view>
- <view class="loading-wrap" v-else>
- <uv-loading-icon text="数据加载中..." :vertical="true"></uv-loading-icon>
- </view>
- </view>
- </uv-popup>
- </template>
- <script setup lang="ts">
- import { ref, computed } from 'vue';
- import { useProjectStore } from '@/store/modules/project';
- import FlowTable from './FlowTable.vue';
- import { formatDate } from '@/utils/date';
- import { formatWithComma } from '@/utils/format';
- import { previewFile } from '@/utils/file';
- import CommonSection from '@/components/ui/CommonSection.vue';
- import CommonInfoRow from '@/components/ui/CommonInfoRow.vue';
- import CommonFileList from '@/components/ui/CommonFileList.vue';
- /**
- * 中检详情弹窗组件
- * 根据项目类型(纵向/横向/自发)展示不同的业务字段
- */
- const projectStore = useProjectStore();
- const popupRef = ref<any>(null);
- const localProjectType = ref('');
- /**
- * 计算属性:中检批次通知附件解析
- * 数据来源为 JSON 字符串,需解析为数组对象以便渲染
- */
- const batchInformList = computed(() => {
- const inform = projectStore.inspDetailData.batchInform;
- if (!inform) return [];
- try {
- return JSON.parse(inform);
- } catch (e) {
- return [];
- }
- });
- /**
- * 计算属性:Tab 页标题配置
- * 只有在已提交审批(审批状态 > 10)时才显示“审批信息”页签
- */
- const tabList = computed(() => {
- const tabs = [
- { name: '基本信息' },
- { name: '中检信息' }
- ];
-
- if (projectStore.inspDetailData.approvalStatus > 10) {
- tabs.push({ name: '审批信息' });
- }
- return tabs;
- });
- // 当前激活的 tab index
- const currentTab = ref(0);
- /**
- * 标签页点击切换处理
- */
- const handleTabClick = (item: any) => {
- currentTab.value = item.index;
- };
- /**
- * 外部调用开启详情弹窗
- * @param row 原始列表单条数据对象
- * @param type 项目类型标识 (vertical/horizontal/spontaneity)
- */
- const open = async (row: any, type: string = '') => {
- localProjectType.value = type;
- popupRef.value?.open();
-
- // 重置为第一个标签页
- currentTab.value = 0;
- // 调用 store 发起异步请求获取完整中检详情数据
- await projectStore.fetchInspDetail(row.id, '', type);
-
- /**
- * 业务逻辑补充:处理嵌套的文件字段反序列化
- * 针对支出明细等关联数据中的 fileUrl 字段进行二次解析
- */
- if (projectStore.inspDetailData.expenseList && projectStore.inspDetailData.expenseList.length > 0) {
- projectStore.inspDetailData.expenseList.forEach((item: any) => {
- if (item.fileUrl) {
- try {
- item.file = JSON.parse(item.fileUrl);
- } catch {
- item.file = null;
- }
- }
- });
- }
- };
- /**
- * 关闭弹窗逻辑
- */
- const close = () => {
- popupRef.value?.close();
- };
- /**
- * 统一附件预览处理
- * 提取文件链接并调用全局封装的预览工具
- */
- const handleDownload = (file: any) => {
- const url = file.url || file.fileUrl;
- if (!url) {
- uni.showToast({ title: '无法获取附件地址', icon: 'none' });
- return;
- }
- previewFile(url, file.name || file.fileName);
- };
- defineExpose({ open, close });
- </script>
- <style lang="scss" scoped>
- .popup-container {
- height: 85vh;
- display: flex;
- flex-direction: column;
- background-color: #f7f8fa;
- }
- .popup-header {
- flex-shrink: 0;
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 30rpx 40rpx;
- background-color: #fff;
- border-bottom: 2rpx solid #eee;
- .title {
- font-size: 34rpx;
- font-weight: bold;
- color: #343A3F;
- }
- }
- .loading-wrap {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .popup-content {
- flex: 1;
- overflow: hidden;
- background-color: #f7f8fa;
- }
- .tab-pane {
- padding: 24rpx;
- display: flex;
- flex-direction: column;
- gap: 24rpx;
- }
- .section {
- background-color: #fff;
- border-radius: 16rpx;
- padding: 30rpx;
- margin-bottom: 20rpx;
- .section-title {
- font-size: 30rpx;
- font-weight: bold;
- color: #343A3F;
- display: flex;
- align-items: center;
- margin-bottom: 24rpx;
- .icon {
- width: 8rpx;
- height: 30rpx;
- background-color: #1c9bfd;
- border-radius: 4rpx;
- margin-right: 16rpx;
- }
- }
- }
- .info-list {
- display: flex;
- flex-direction: column;
- .info-item {
- display: flex;
- padding: 16rpx 0;
- border-bottom: 2rpx dashed #f5f5f5;
- font-size: 28rpx;
- line-height: 1.5;
- &:last-child {
- border-bottom: none;
- }
- .label {
- color: #343A3F;
- width: 180rpx;
- flex-shrink: 0;
- }
- .value {
- flex: 1;
- color: #585858;
- word-break: break-all;
- text-align: right;
- &.text-left {
- text-align: left;
- margin-top: 10rpx;
- }
- }
- &.block-item {
- flex-direction: column;
- align-items: flex-start;
- .label {
- width: 100%;
- margin-bottom: 10rpx;
- }
- .value {
- text-align: left;
- width: 100%;
- }
- }
-
- .amount {
- color: #ff4d4f;
- font-weight: bold;
- }
- }
- }
- .links-col {
- display: flex;
- flex-direction: column;
- gap: 12rpx;
- }
- .file-list {
- display: flex;
- flex-direction: column;
- gap: 20rpx;
- .file-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 20rpx;
- background-color: #f7f9fa;
- border-radius: 8rpx;
- .file-type {
- font-size: 26rpx;
- color: #666;
- }
- .file-name {
- font-size: 26rpx;
- text-decoration: underline;
- max-width: 60%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- text-align: right;
- }
- }
- }
- .text-blue {
- color: #1c9bfd;
- }
- .text-red {
- color: #ff4d4f;
- }
- </style>
|