first commit

This commit is contained in:
2025-12-26 23:19:09 +08:00
commit b29d128e41
788 changed files with 100922 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
<template>
<el-dialog v-model="visibleInner" :title="t('common.viewBtn') + ' ' + t('expertLibrary.title')" width="900px"
append-to-body>
<!-- 顶部三列摘要 -->
<el-descriptions :column="3" border class="mb10">
<el-descriptions-item :label="t('expertApply.reporter')">
{{ meta.reporter }}
</el-descriptions-item>
<el-descriptions-item :label="t('expertApply.date')">
{{ meta.date }}
</el-descriptions-item>
<el-descriptions-item :label="t('expertApply.reportingUnit')">
{{ meta.unit }}
</el-descriptions-item>
</el-descriptions>
<!-- 专家信息 -->
<el-descriptions :title="t('expertLibrary.title')" :column="2" border>
<el-descriptions-item :label="t('expertApply.table.name')">
{{ detail.expertName || '-' }}
</el-descriptions-item>
<el-descriptions-item :label="t('expertApply.table.contact')">
{{ detail.contactInformation || '-' }}
</el-descriptions-item>
<el-descriptions-item :label="t('expertApply.table.field')">
{{ detail.technologyField || '-' }}
</el-descriptions-item>
<el-descriptions-item :label="t('expertApply.table.title')">
{{ detail.professionalTitle || '-' }}
</el-descriptions-item>
<el-descriptions-item :label="t('expertApply.table.company')" :span="2">
{{ detail.workUnit || '-' }}
</el-descriptions-item>
<el-descriptions-item :label="t('expertApply.table.level')">
{{ levelLabel(detail.level) }}
</el-descriptions-item>
<el-descriptions-item :label="t('expertLibrary.table.status')">
<el-tag :type="['info', 'primary', 'success', 'danger'][detail.status]">{{ statusLabel(detail.status) }}</el-tag>
</el-descriptions-item>
</el-descriptions>
<!-- 附件与佐证材料 -->
<el-descriptions :column="1" border class="mt10">
<el-descriptions-item :label="t('expertApply.table.attachments')">
<template v-if="(detail.attachments || []).length">
<span v-for="(f, idx) in detail.attachments" :key="'a-' + idx">
<a :href="`${baseURL}${f.url}`" target="_blank">
<el-button link type="primary">
{{ f.name }}
</el-button>
</a>
<span v-if="f.size">{{ f.size }}</span>
<span v-if="idx !== detail.attachments.length - 1" class="mx8">|</span>
</span>
</template>
<template v-else>-</template>
</el-descriptions-item>
<el-descriptions-item :label="t('expertApply.table.evidences')">
<template v-if="(detail.evidences || []).length">
<span v-for="(f, idx) in detail.evidences" :key="'e-' + idx">
<a :href="`${baseURL}${f.url}`" target="_blank">
<el-button link type="primary">
{{ f.name }}
</el-button>
</a>
<span v-if="f.size">{{ f.size }}</span>
<span v-if="idx !== detail.evidences.length - 1" class="mx8">|</span>
</span>
</template>
<template v-else>-</template>
</el-descriptions-item>
</el-descriptions>
<template #footer>
<span class="dialog-footer">
<el-button @click="visibleInner = false">{{ t('tagsView.close') || '关闭' }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
const baseURL = import.meta.env.VITE_API_URL
const props = defineProps<{
visible: boolean
detail: any
meta: { reporter: string; unit: string; date: string }
}>();
const emit = defineEmits<{
(e: 'update:visible', v: boolean): void
}>();
const { t } = useI18n();
const visibleInner = ref(false);
watch(
() => props.visible,
(v) => (visibleInner.value = v),
{ immediate: true },
);
watch(visibleInner, (v) => emit('update:visible', v));
const levelLabel = (val: string) => {
if (val === 'national') return t('expertApply.level.national');
if (val === 'provincial') return t('expertApply.level.provincial');
if (val === 'city') return t('expertApply.level.city');
return '-';
};
const statusLabel = (val: string) => {
return {
'0': t('expertLibrary.status.pending'),
'1': t('expertLibrary.status.reviewing'),
'2': t('expertLibrary.status.approved'),
'3': t('expertLibrary.status.rejected'),
}[val] || val
};
</script>
<style scoped>
.mb10 {
margin-bottom: 10px;
}
</style>

View File

@@ -0,0 +1,167 @@
<template>
<el-dialog v-model="innerVisible" :title="t('cooperationUnit.businessCompany')" width="90%" top="5vh"
:close-on-click-modal="false">
<div class="dialog-toolbar">
<el-form :inline="true" :model="queryForm" @keyup.enter="getDataList" ref="queryRef">
<el-form-item :label="t('cooperationUnit.cooperationName')">
<el-input v-model="queryForm.cooperationName"
:placeholder="t('cooperationUnit.inputCooperationNameTip')" style="width: 220px" />
</el-form-item>
<el-form-item :label="t('cooperationUnit.industryField')">
<el-input v-model="queryForm.industryField"
:placeholder="t('cooperationUnit.inputIndustryFieldTip')" style="width: 160px" />
</el-form-item>
<el-form-item :label="t('cooperationUnit.cooperationNature')">
<el-select v-model="queryForm.cooperationNature" clearable style="width: 160px">
<el-option label="企业" value="enterprise" />
<el-option label="事业单位" value="institution" />
<el-option label="政府机构" value="government" />
<el-option label="其他" value="other" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">{{ $t('common.queryBtn') }}</el-button>
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="dataList" @selection-change="onCurrentChange" max-height="560" style="width: 100%"
v-loading="loading" border>
<el-table-column width="60">
<template #default="scope">
<el-radio v-model="selectedIndex" :label="scope.$index">&nbsp;</el-radio>
</template>
</el-table-column>
<el-table-column :label="t('cooperationUnit.cooperationName')" prop="cooperationName" min-width="150"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.provinceCity')" prop="provinceCityLabel" min-width="120"
show-overflow-tooltip />
<el-table-column prop="cooperationNature" header-class-name="red-header" min-width="140"
show-overflow-tooltip>
<template #header>
<span class="red-text">{{ t('cooperationUnit.cooperationNature') }}</span>
</template>
</el-table-column>
<el-table-column :label="t('cooperationUnit.industryField')" prop="cooperationIndustry" min-width="150"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.cooperationBusiness')" prop="proposedBusinessCooperation"
min-width="160" show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.cooperationAdvantage')" prop="cooperationAdvantage"
min-width="160" show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.cooperationStatus')" prop="cooperationStatus" min-width="120"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.cooperationStartTime')" prop="startTime"
min-width="140" show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.cooperationEndTime')" prop="endTime" min-width="140"
show-overflow-tooltip />
<!-- <el-table-column :label="t('cooperationUnit.unitAddress')" prop="unitAddress" min-width="200"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.unitContact')" prop="unitContact" min-width="120"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.departmentPosition')" prop="departmentPosition" min-width="150"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.phone')" prop="phone" min-width="120" show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.potentialOpportunity')" prop="potentialOpportunity"
min-width="160" show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.businessCompany')" prop="businessCompany" min-width="150"
show-overflow-tooltip /> -->
<el-table-column :label="t('cooperationUnit.actualController')" prop="cooperationController"
header-class-name="red-header" min-width="150" show-overflow-tooltip>
<template #header>
<span class="red-text">{{ t('cooperationUnit.actualController') }}</span>
</template>
</el-table-column>
<el-table-column :label="t('cooperationUnit.shareholdingRatio')" prop="shareholdingRatio"
header-class-name="red-header" min-width="120" show-overflow-tooltip>
<template #header>
<span class="red-text">{{ t('cooperationUnit.shareholdingRatio') }}</span>
</template>
</el-table-column>
<el-table-column :label="t('cooperationUnit.createTime')" prop="createTime" min-width="170"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.updateTime')" prop="updateTime" min-width="170"
show-overflow-tooltip />
</el-table>
<template #footer>
<el-button @click="innerVisible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button type="primary" @click="confirm" :disabled="selectedIndex === -1">{{
$t('common.confirmButtonText') }}</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { getExternalCooperationUnitsPageAPI } from '/@/api/investment/cooperationUnit';
import type { FormInstance } from 'element-plus';
const props = defineProps<{
visible: boolean;
}>();
const emit = defineEmits<{
(e: 'update:visible', v: boolean): void;
(e: 'confirm', value: string): void;
}>();
const { t } = useI18n();
const innerVisible = ref(false);
watch(() => props.visible, v => (innerVisible.value = v));
watch(innerVisible, v => emit('update:visible', v));
const loading = ref(false);
const dataList = ref<any[]>([]);
const selectedIndex = ref(-1);
const queryRef = ref<FormInstance|null>(null);
const queryForm = reactive({
cooperationName: '',
industryField: '',
cooperationNature: '',
page: 1,
size: 10,
});
const getDataList = async () => {
loading.value = true;
try {
const res = await getExternalCooperationUnitsPageAPI(queryForm as any);
const apiList = res.data?.records || [];
dataList.value = apiList
} finally {
loading.value = false;
}
};
const resetQuery = () => {
Object.assign(queryForm, {
cooperationName: '',
industryField: '',
cooperationNature: ''
})
getDataList();
};
const onCurrentChange = (row: any) => {
if (!row) return;
selectedIndex.value = dataList.value.indexOf(row);
};
const confirm = () => {
if (selectedIndex.value === -1) return;
const row = dataList.value[selectedIndex.value];
emit('confirm', row);
innerVisible.value = false;
};
watch(innerVisible, v => {
if (v) {
getDataList();
selectedIndex.value = -1;
}
});
</script>
<style scoped>
.dialog-toolbar {
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@@ -0,0 +1,189 @@
<template>
<el-dialog v-model="visibleInner" :title="t('expertLibrary.title')" width="900px" append-to-body>
<!-- 搜索区 -->
<el-form :inline="true" :model="queryForm" class="mb10">
<el-form-item :label="t('expertLibrary.form.name')" prop="name">
<el-input v-model="queryForm.expertName" :placeholder="t('expertLibrary.placeholder.input')"
style="width: 180px" />
</el-form-item>
<el-form-item :label="t('expertLibrary.form.field')" prop="techField">
<el-input v-model="queryForm.technologyField" :placeholder="t('expertLibrary.placeholder.input')"
style="width: 180px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">{{ t('common.queryBtn') }}</el-button>
<el-button icon="Refresh" @click="resetQuery">{{ t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
<!-- 单选表格 -->
<el-table :data="tableData" border @current-change="onCurrentChange" v-loading="loading">
<el-table-column width="60">
<template #default="scope">
<el-radio v-model="selectedRowIndex" :label="scope.$index"
@change="handleRowSelect(scope.$index)">&nbsp;</el-radio>
</template>
</el-table-column>
<el-table-column type="index" :label="t('expertLibrary.table.index')" width="60" />
<el-table-column prop="expertName" :label="t('expertLibrary.table.name')" min-width="140" />
<el-table-column prop="contactInformation" :label="t('expertLibrary.table.contact')" min-width="140" />
<el-table-column prop="technologyField" :label="t('expertLibrary.table.field')" min-width="140" />
<el-table-column prop="professionalTitle" :label="t('expertLibrary.table.title')" min-width="120" />
<el-table-column prop="workUnit" :label="t('expertLibrary.table.company')" min-width="160" />
<el-table-column prop="level" :label="t('expertLibrary.table.level')" min-width="100">
<template #default="{ row }">
{{ levelLabel(row.level) }}
</template>
</el-table-column>
<el-table-column prop="externalStatus" :label="t('expertLibrary.table.status')" min-width="100">
<template #default="{ row }">
{{ externalStatusLabel(row.externalStatus) }}
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="mt15 flex justify-end">
<el-pagination background layout="sizes, prev, pager, next, jumper, total" :total="pagination.total"
:page-sizes="[10, 20, 50, 100]" v-model:page-size="pagination.size"
v-model:current-page="pagination.page" @size-change="handleSizeChange"
@current-change="handlePageChange" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCancel">{{ t('common.cancelButtonText') || '取消' }}</el-button>
<el-button type="primary" @click="handleConfirm">{{ t('user.logOutConfirm') || '确定' }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, watch, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import { getExpertInformationPageAPI } from '/@/api/investment/cooperationUnit';
const props = defineProps<{
visible: boolean
}>();
const emit = defineEmits<{
(e: 'update:visible', v: boolean): void
(e: 'confirm', row: any): void
}>();
const { t } = useI18n();
const visibleInner = ref(false);
watch(
() => props.visible,
(v) => (visibleInner.value = v),
{ immediate: true },
);
watch(visibleInner, (v) => emit('update:visible', v));
const queryForm = reactive({
expertName: '',
technologyField: '',
externalStatus: '',
});
const loading = ref(false);
const tableData = ref<any[]>([]);
const pagination = reactive({
page: 1,
size: 10,
total: 0,
});
const getDataList = async () => {
loading.value = true;
try {
const params = {
page: pagination.page,
size: pagination.size,
...queryForm,
};
const res = await getExpertInformationPageAPI(params);
const records = res?.records ?? res?.data?.records ?? [];
const total = res?.total ?? res?.data?.total ?? 0;
tableData.value = records;
pagination.total = total;
} finally {
loading.value = false;
}
};
const resetQuery = () => {
queryForm.expertName = '';
queryForm.technologyField = '';
queryForm.externalStatus = '';
pagination.page = 1;
getDataList();
};
const handleSizeChange = (val: number) => {
pagination.size = val;
pagination.page = 1;
getDataList();
};
const handlePageChange = (val: number) => {
pagination.page = val;
getDataList();
};
const selectedRowIndex = ref(-1)
const currentRow = ref<any | null>(null);
const onCurrentChange = (row: any) => {
selectedRowIndex.value = tableData.value.findIndex(item => item === row)
currentRow.value = row;
};
const handleRowSelect = (index: number) => {
selectedRowIndex.value = index
currentRow.value = tableData.value[index];
}
const handleCancel = () => {
visibleInner.value = false;
};
const handleConfirm = () => {
if (currentRow.value) emit('confirm', currentRow.value);
visibleInner.value = false;
};
const levelLabel = (val: string) => {
if (val === 'national') return t('expertApply.level.national');
if (val === 'provincial') return t('expertApply.level.provincial');
if (val === 'city') return t('expertApply.level.city');
return '-';
};
const externalStatusLabel = (val: string) => {
if (val === 'active') return t('expertLibrary.status.active');
if (val === 'inactive') return t('expertLibrary.status.inactive');
return '-';
};
// 监听对话框显示状态,当打开时加载数据
watch(visibleInner, (newVal) => {
if (newVal) {
pagination.page = 1;
getDataList();
}
});
</script>
<style scoped>
.mb10 {
margin-bottom: 10px;
}
.mt15 {
margin-top: 15px;
}
.flex {
display: flex;
}
.justify-end {
justify-content: flex-end;
}
</style>

View File

@@ -0,0 +1,185 @@
<template>
<el-dialog v-model="innerVisible" :title="t('cooperationUnit.cooperationName')" width="90%" top="5vh"
:close-on-click-modal="false">
<div class="dialog-toolbar">
<el-form :inline="true" :model="queryForm" @keyup.enter="getDataList" ref="queryRef">
<el-form-item :label="t('cooperationUnit.cooperationName')">
<el-input v-model="queryForm.cooperationName"
:placeholder="t('cooperationUnit.inputCooperationNameTip')" style="width: 220px" />
</el-form-item>
<el-form-item :label="t('cooperationUnit.industryField')">
<el-input v-model="queryForm.industryField"
:placeholder="t('cooperationUnit.inputIndustryFieldTip')" style="width: 160px" />
</el-form-item>
<el-form-item :label="t('cooperationUnit.cooperationNature')">
<el-select v-model="queryForm.cooperationNature" clearable style="width: 160px">
<el-option label="企业" value="enterprise" />
<el-option label="事业单位" value="institution" />
<el-option label="政府机构" value="government" />
<el-option label="其他" value="other" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">{{ $t('common.queryBtn') }}</el-button>
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="dataList" v-loading="loading" @current-change="onCurrentChange" highlight-current-row
height="55vh" border>
<el-table-column width="60">
<template #default="scope">
<el-radio v-model="selectedIndex" :label="scope.$index">&nbsp;</el-radio>
</template>
</el-table-column>
<el-table-column :label="t('cooperationUnit.cooperationName')" prop="cooperationName" min-width="200"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.provinceCity')" prop="provinceCity" min-width="120"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.industryField')" prop="industryField" min-width="160"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.cooperationBusiness')" prop="cooperationBusiness"
min-width="160" show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.cooperationAdvantage')" prop="cooperationAdvantage"
min-width="160" show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.cooperationStatus')" prop="cooperationStatus" min-width="140"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.cooperationStartTime')" prop="cooperationStartTime"
min-width="160" show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.cooperationEndTime')" prop="cooperationEndTime" min-width="160"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.reportingUnit')" prop="reportingUnit" min-width="160"
show-overflow-tooltip />
<el-table-column :label="t('cooperationUnit.date')" prop="date" min-width="140" show-overflow-tooltip />
</el-table>
<template #footer>
<el-button @click="handleCancel">{{ $t('common.cancelButtonText') }}</el-button>
<el-button type="primary" @click="handleConfirm" :disabled="selectedIndex === -1">{{
$t('common.confirmButtonText') }}</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
// import { getCooperationUnitPageAPI } from '/@/api/investment/cooperationUnit';
const props = defineProps<{
visible: boolean;
}>();
const emit = defineEmits<{
(e: 'update:visible', v: boolean): void;
(e: 'confirm', row: any): void;
}>();
const { t } = useI18n();
const innerVisible = ref(false);
watch(
() => props.visible,
v => (innerVisible.value = v)
);
watch(innerVisible, v => emit('update:visible', v));
const loading = ref(false);
const dataList = ref<any[]>([]);
const selectedIndex = ref(-1);
const queryRef = ref();
const queryForm = reactive({
cooperationName: '',
industryField: '',
cooperationNature: '',
current: 1,
size: 10,
});
const getDataList = async () => {
loading.value = true;
try {
// const res = await getCooperationUnitPageAPI(queryForm as any);
// const apiList = res.records || res.data?.records || [];
const apiList: any[] = [];
// 模拟数据(用于演示/联调前占位)
const mockList = [
{
cooperationName: '成都公交集团',
provinceCity: '成都市',
unitType: '国有企业',
industryField: '道路运输',
cooperationBusiness: '公交客运服务',
cooperationAdvantage: '线路资源丰富、品牌影响力强',
cooperationStatus: '已开展合作',
cooperationStartTime: '2025-08-01',
cooperationEndTime: '2025-12-31',
reportingUnit: '成都市交通运输局',
date: '2025-08-01',
unitContact: '张三',
phone: '13800000001',
},
{
cooperationName: '四川能投集团',
provinceCity: '成都市',
unitType: '国有企业',
industryField: '专业技术与商务服务业',
cooperationBusiness: '资产评估',
cooperationAdvantage: '行业资源、资金实力雄厚',
cooperationStatus: '已开展合作',
cooperationStartTime: '2025-08-05',
cooperationEndTime: '2025-12-31',
reportingUnit: '成都市国资委',
date: '2025-08-05',
unitContact: '李四',
phone: '13800000002',
},
{
cooperationName: '成都地铁集团',
provinceCity: '成都市',
unitType: '事业单位',
industryField: '道路运输',
cooperationBusiness: '公共交通协同',
cooperationAdvantage: '轨道交通网络完善',
cooperationStatus: '拟合作',
cooperationStartTime: '2025-09-01',
cooperationEndTime: '2025-12-31',
reportingUnit: '成都市交委',
date: '2025-09-01',
unitContact: '王五',
phone: '13800000003',
},
];
dataList.value = apiList && apiList.length ? apiList : mockList;
} finally {
loading.value = false;
}
};
const resetQuery = () => {
queryRef.value?.resetFields();
getDataList();
};
const onCurrentChange = (row: any) => {
if (!row) return;
selectedIndex.value = dataList.value.indexOf(row);
};
const handleCancel = () => (innerVisible.value = false);
const handleConfirm = () => {
if (selectedIndex.value === -1) return;
emit('confirm', dataList.value[selectedIndex.value]);
innerVisible.value = false;
};
watch(innerVisible, v => {
if (v) {
getDataList();
selectedIndex.value = -1;
}
});
</script>
<style scoped>
.dialog-toolbar {
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@@ -0,0 +1,109 @@
<template>
<div>
<el-row class="mb10 header-meta">
<div class="meta-item">{{ t('cooperationUnit.reporterLabel') + displayReporter }}</div>
<div class="meta-item">{{ t('cooperationUnit.reportingUnitLabel') + displayUnit }}</div>
<div class="meta-item">{{ t('cooperationUnit.dateLabel') + displayDate }}</div>
<div class="meta-item">{{ t('cooperationUnit.serialNumber') + '--' }}</div>
</el-row>
<!-- 动态明细表按图示 -->
<el-table :data="data" border style="width: 100%;">
<el-table-column type="index" :label="t('cooperationUnit.index')" width="60" />
<el-table-column prop="cooperationName" :label="t('cooperationUnit.cooperationName')" min-width="200" />
<el-table-column prop="areaCode" :label="t('cooperationUnit.provinceCity')" min-width="240">
<template #default="scope">
{{ scope.row.areaCode }}
</template>
</el-table-column>
<el-table-column prop="cooperationNature" :label="t('cooperationUnit.cooperationNature')" min-width="140">
<template #default="{ row }">
{{ cooperationPartnerNatureOptions.find(item => item.value === row.cooperationNature)?.label || row.cooperationNature || '--' }}
</template>
</el-table-column>
<el-table-column prop="cooperationIndustry" :label="t('cooperationUnit.cooperationIndustry')" min-width="160" />
<el-table-column prop="proposedBusinessCooperation" :label="t('cooperationUnit.proposedBusinessCooperation')" min-width="180" />
<el-table-column prop="cooperationAdvantage" :label="t('cooperationUnit.cooperationAdvantage')" min-width="160" />
<el-table-column prop="cooperationStatus" :label="t('cooperationUnit.cooperationStatus')" min-width="140" />
<el-table-column prop="startTime" :label="t('cooperationUnit.startTime')" min-width="170" />
<el-table-column prop="endTime" :label="t('cooperationUnit.endTime')" min-width="170" />
<el-table-column prop="unitAddress" :label="t('cooperationUnit.unitAddress')" min-width="200" />
<el-table-column prop="unitContactPerson" :label="t('cooperationUnit.unitContactPerson')" min-width="150" />
<el-table-column prop="contactPersonDept" :label="t('cooperationUnit.contactPersonDept')" min-width="160" />
<el-table-column prop="contactPersonPhone" :label="t('cooperationUnit.contactPersonPhone')" min-width="160" />
<el-table-column prop="potentialCooperation" :label="t('cooperationUnit.potentialCooperation')" min-width="180" />
<el-table-column prop="businessConnectionCompany" :label="t('cooperationUnit.businessConnectionCompany')" min-width="200" />
<el-table-column prop="cooperationController" :label="t('cooperationUnit.actualController')" min-width="160" />
<el-table-column prop="shareholdingRatio" :label="t('cooperationUnit.shareholdingRatio')" min-width="140" />
</el-table>
</div>
</template>
<script lang="ts" name="bizInvestmentCooperationUnit" setup>
import { useI18n } from 'vue-i18n';
import { useUserInfo } from '/@/stores/userInfo';
import { formatDate } from '/@/utils/formatTime';
import { cooperationPartnerNatureOptions } from '/@/hooks/enums'
const props = defineProps({
isUpdate: {
type: Boolean,
default: false
},
data: {
type: Array,
default: () => ([])
}
})
const { t } = useI18n();
// 头部摘要显示
const userInfoStore = useUserInfo();
const displayReporter = (userInfoStore.userInfos.user?.username || '');
const displayUnit = (userInfoStore.userInfos.user?.deptName || '');
const displayDate = formatDate(new Date(), 'YYYY-mm-dd');
</script>
<style scoped>
.form-header-actions {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-summary .title {
margin: 0 auto;
font-size: 18px;
font-weight: 600;
}
.header-meta {
display: flex;
justify-content: space-between;
align-items: center;
width: 50%;
margin: 0 auto;
}
.header-meta .meta-item {
color: var(--el-text-color-regular);
}
.required-star {
color: var(--el-color-danger);
margin-right: 2px;
}
.red-label {
color: var(--el-color-danger);
}
</style>
<style scoped>
.form-header-actions {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,617 @@
<template>
<div class="project-basic-info-form">
<div class="form-section-title">{{ t('reserveRegistration.basicInfo.title') }}</div>
<el-form
label-width="220px"
class="project-form"
ref="formRef"
>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.applicant')">
{{ formData.applicant || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.applicationDate')">
{{ formData.applicationDate || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('reserveRegistration.basicInfo.applicationUnit')">
{{ formData.applicationUnit || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('reserveRegistration.basicInfo.projectName')">
{{ formData.projectName || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectMainUnit')">
{{ formData.projectMainUnit || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectOwnerUnit')">
{{ formData.projectOwnerUnit || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectStartDate')">
{{ formData.projectStartDate || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectEndDate')">
{{ formData.projectEndDate || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.investmentCategory')">
{{ investmentCategoryOptions.find(item => item.value === formData.investmentCategory)?.label || formData.investmentCategory || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectNature')">
{{ projectNatureOptions.find(item => item.value === formData.projectNature)?.label || formData.projectNature || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectManagerName')">
{{ formData.projectManagerName || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.contactNumber')">
{{ formData.contactNumber || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectSource')">
{{ projectSourceOptions.find(item => item.value === formData.projectSource)?.label || formData.projectSource || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.constructionNature')">
{{ constructionNatureOptions.find(item => item.value === formData.constructionNature)?.label || formData.constructionNature || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.keyProject')">
{{ formData.keyProject === 'yes' ? '是' : '否' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.majorInvestmentProject')">
{{ formData.majorInvestmentProject || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('reserveRegistration.basicInfo.withinMainBusiness')">
{{ formData.withinMainBusiness || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.mainBusinessType')">
{{ formData.mainBusinessType || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.mainBusinessCode')">
{{ formData.mainBusinessCode || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectDirection')">
{{ formData.projectDirection || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.directionSubdivision')">
{{ formData.directionSubdivision || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.strategicEmergingIndustry')">
{{ formData.strategicEmergingIndustry || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.urbanStrategy')">
{{ formData.urbanStrategy || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.isManufacturing')">
{{ formData.isManufacturing || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.investmentRegion')">
{{ investmentAreaOptions.find(item => item.value === formData.investmentRegion)?.label || formData.investmentRegion || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectAddress')">
{{ formData.projectAddress || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectDetailedAddress')">
{{ formData.projectDetailedAddress || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.isControllingShareholder')">
{{ formData.isControllingShareholder || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.ourShareholdingRatio')">
{{ formData.ourShareholdingRatio || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.totalInvestmentAmount')">
{{ formData.totalInvestmentAmount || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.cumulativeInvestmentLastYear')">
{{ formData.cumulativeInvestmentLastYear || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.ourTotalInvestmentAmount')">
{{ formData.ourTotalInvestmentAmount || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.ourCumulativeInvestmentLastYear')">
{{ formData.ourCumulativeInvestmentLastYear || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('reserveRegistration.basicInfo.projectOverview')">
{{ formData.projectDesc || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('reserveRegistration.basicInfo.progressDescription')">
{{ formData.progressDesc || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('reserveRegistration.basicInfo.projectProposal')">
<div v-for="file of formData.projectProposal" :key="file.url" class="mr-4">
<a :href="`${baseURL}${file.url}`" target="_blank">
<el-button link type="primary">
{{ file.name }}
</el-button>
</a>
</div>
</el-form-item>
</el-col>
</el-row>
<div class="sub-section-title">{{ t('reserveRegistration.investmentEstimate.title') }}</div>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.investmentEstimate.enterpriseSelfFund')">
{{ formData.enterpriseSelfFund || '0' }}
{{ t('reserveRegistration.investmentEstimate.unitWanYuan') }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.investmentEstimate.governmentInvestmentFund')">
{{ formData.governmentInvestmentFund || '0' }}
{{ t('reserveRegistration.investmentEstimate.unitWanYuan') }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.investmentEstimate.externalRaisedFund')">
{{ formData.externalRaisedFund || '0' }}
{{ t('reserveRegistration.investmentEstimate.unitWanYuan') }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.investmentEstimate.otherFunds')">
{{ formData.otherFunds || '0' }}
{{ t('reserveRegistration.investmentEstimate.unitWanYuan') }}
</el-form-item>
</el-col>
</el-row>
<div class="sub-section-title">{{ t('reserveRegistration.partnerInfo.title') }}</div>
<el-form-item prop="partnerInfos" label-width="0">
<el-table :data="formData.cooperationUnits" border style="width: 100%;" highlight-current-row>
<el-table-column type="index" :label="t('reserveRegistration.partnerInfo.table.index')" width="60" />
<el-table-column prop="cooperationName" :label="t('reserveRegistration.partnerInfo.table.name')" />
<el-table-column prop="cooperationNature" :label="t('reserveRegistration.partnerInfo.table.nature')" />
<el-table-column prop="cooperationController" :label="t('reserveRegistration.partnerInfo.table.controller')" />
<el-table-column readonly prop="shareholdingRatio" :label="t('reserveRegistration.partnerInfo.table.share')" width="100">
<template #default="scope">
{{ scope.row.shareholdingRatio }}%
</template>
</el-table-column>
</el-table>
</el-form-item>
<div class="sub-section-title">{{ t('reserveRegistration.decisionInfo.title') }}</div>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.decisionInfo.decisionType')">
{{ formData.decisionType || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.decisionInfo.isCompleteEstablishmentProcedures')">
{{ formData.isCompleteEstablishmentProcedures === 'yes' ? '是' : '否' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.decisionInfo.establishmentDocumentNumber')">
{{ formData.establishmentDocumentNumber || '--' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.decisionInfo.establishmentDocumentInfo')">
{{ formData.establishmentDocumentInfo || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.decisionInfo.isCompleteDecisionProcedures')">
{{ formData.isCompleteDecisionProcedures === 'yes' ? '是' : '否' }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.decisionInfo.decisionProgramDocumentNumber')">
{{ formData.decisionProgramDocumentNumber || '--' }}
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('reserveRegistration.decisionInfo.decisionDocumentInfo')">
{{ formData.decisionDocumentInfo || '--' }}
</el-form-item>
</el-col>
</el-row>
<!-- <div class="sub-section-title">{{ t('reserveRegistration.reviewOpinions.title') }}</div>
<el-form-item :label="t('reserveRegistration.reviewOpinions.submitUnitOpinion')">
{{ formData.reviewOpinions.submitUnitOpinion || '--' }}
</el-form-item>
<el-form-item :label="t('reserveRegistration.reviewOpinions.groupInvestmentDeptOpinion')">
{{ formData.reviewOpinions.groupInvestmentDeptOpinion || '--' }}
</el-form-item>
<el-form-item :label="t('reserveRegistration.reviewOpinions.submitUnitLeadershipOpinion')">
{{ formData.reviewOpinions.submitUnitLeadershipOpinion || '--' }}
</el-form-item>
<el-form-item :label="t('reserveRegistration.reviewOpinions.submitUnitMainLeadershipOpinion')">
{{ formData.reviewOpinions.submitUnitMainLeadershipOpinion || '--' }}
</el-form-item> -->
</el-form>
</div>
</template>
<script lang="ts" setup>
import { reactive, watch, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import {
projectNatureOptions,
investmentCategoryOptions,
projectSourceOptions,
constructionNatureOptions,
investmentAreaOptions
} from '/@/hooks/enums'
import type { ExternalCooperationUnitItemT } from '/@/api/investment/cooperationUnit'
const { t } = useI18n();
export interface ProjectBasicInfoFormData {
applicant: string;
applicationUnit: string;
projectName: string;
projectMainUnit: string;
projectStartDate: string;
investmentCategory: string;
projectManagerName: string;
projectSource: string;
keyProject: string;
withinMainBusiness: string;
mainBusinessType: string;
projectDirection: string;
strategicEmergingIndustry: string;
isManufacturing: string;
projectAddress: string;
isControllingShareholder: string;
totalInvestmentAmount: string;
ourTotalInvestmentAmount: string;
projectDesc: string;
progressDesc: string;
projectProposal: any[];
applicationDate: string;
projectOwnerUnit: string;
projectEndDate: string;
projectNature: string;
contactNumber: string;
constructionNature: string;
majorInvestmentProject: string;
mainBusinessCode: string;
directionSubdivision: string;
urbanStrategy: string;
investmentRegion: string;
projectDetailedAddress: string;
ourShareholdingRatio: string;
cumulativeInvestmentLastYear: string;
ourCumulativeInvestmentLastYear: string;
enterpriseSelfFund: string;
externalRaisedFund: string;
governmentInvestmentFund: string;
otherFunds: string;
partnerInfos: PartnerInfo[];
decisionType: string;
isCompleteEstablishmentProcedures: string;
establishmentDocumentNumber: string;
establishmentDocumentInfo: string;
decisionProgramDocumentNumber: string;
decisionDocumentInfo: string;
isCompleteDecisionProcedures: string;
reviewOpinions: ReviewOpinions;
processInstanceId: string;
cooperationUnits?: ExternalCooperationUnitItemT[]
}
export interface PartnerInfo {
id?: string | number | null;
name: string;
nature: string;
controller: string;
share: string;
}
export interface ReviewOpinions {
submitUnitOpinion: string;
groupInvestmentDeptOpinion: string;
submitUnitLeadershipOpinion: string;
submitUnitMainLeadershipOpinion: string;
}
const props = defineProps<{
modelValue?: ProjectBasicInfoFormData;
}>();
const defaultPartnerRow: PartnerInfo = {
id: '',
name: '',
nature: '',
controller: '',
share: '',
};
const defaultFormData: ProjectBasicInfoFormData = {
applicant: '',
applicationUnit: '',
projectName: '',
projectMainUnit: '',
projectStartDate: '',
investmentCategory: '',
projectManagerName: '',
projectSource: '',
keyProject: '',
withinMainBusiness: '',
mainBusinessType: '',
projectDirection: '',
strategicEmergingIndustry: '',
isManufacturing: '',
projectAddress: '',
isControllingShareholder: '',
totalInvestmentAmount: '',
ourTotalInvestmentAmount: '',
projectDesc: '',
progressDesc: '',
projectProposal: [],
applicationDate: '',
projectOwnerUnit: '',
projectEndDate: '',
projectNature: '',
contactNumber: '',
constructionNature: '',
majorInvestmentProject: '',
mainBusinessCode: '',
directionSubdivision: '',
urbanStrategy: '',
investmentRegion: '',
projectDetailedAddress: '',
ourShareholdingRatio: '',
cumulativeInvestmentLastYear: '',
ourCumulativeInvestmentLastYear: '',
enterpriseSelfFund: '',
externalRaisedFund: '',
governmentInvestmentFund: '',
otherFunds: '',
partnerInfos: [],
decisionType: '',
isCompleteEstablishmentProcedures: '',
establishmentDocumentNumber: '',
establishmentDocumentInfo: '',
decisionProgramDocumentNumber: '',
decisionDocumentInfo: '',
isCompleteDecisionProcedures: '',
processInstanceId: '',
reviewOpinions: {
submitUnitOpinion: '',
groupInvestmentDeptOpinion: '',
submitUnitLeadershipOpinion: '',
submitUnitMainLeadershipOpinion: '',
},
};
const formData = reactive<ProjectBasicInfoFormData>({ ...defaultFormData });
watch(
() => props.modelValue,
newVal => {
Object.assign(
formData,
newVal
);
},
{ immediate: true, deep: true },
);
</script>
<style scoped>
.project-basic-info-form {
background: #fff;
border-radius: 4px;
padding: 20px;
}
.form-main-title {
font-size: 18px;
font-weight: 600;
text-align: center;
margin-bottom: 20px;
color: var(--el-text-color-primary);
}
.form-section-title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
margin-bottom: 20px;
padding-left: 10px;
border-left: 4px solid #409eff;
}
.project-form {
width: 100%;
}
.form-row {
margin-bottom: 16px;
}
.form-row:last-of-type {
margin-bottom: 0;
}
:deep(.el-form-item__label) {
font-weight: 500;
}
:deep(.is-required .el-form-item__label) {
font-weight: 500;
/* color: var(--el-color-danger); */
}
:deep(.is-required .el-form-item__label)::before {
content: '*';
/* color: var(--el-color-danger); */
margin-right: 4px;
}
.sub-section-title {
font-size: 15px;
font-weight: 600;
color: var(--el-text-color-primary);
margin: 24px 0 12px;
padding-left: 10px;
border-left: 4px solid #409eff;
}
.partner-toolbar {
display: flex;
gap: 12px;
margin-bottom: 12px;
}
.partner-name-cell {
display: flex;
align-items: center;
gap: 8px;
}
.partner-name-cell :deep(.el-input) {
flex: 1;
}
:deep(.el-table th .cell) {
font-weight: 500;
}
</style>

View File

@@ -0,0 +1,137 @@
<template>
<div class="project-exit-feedback-form">
<div class="form-heading">{{ t('projectExitFeedback.form.title') }}</div>
<div class="feedback-table">
<div class="table-row">
<div class="table-cell label required">{{ t('projectExitFeedback.form.projectName') }}</div>
<div class="table-cell value project-name-cell" >
{{ formData.projectName }}
</div>
</div>
<div class="table-row">
<div class="table-cell label">{{ t('projectExitFeedback.form.exitTime') }}</div>
<div class="table-cell value">
{{ formData.exitTime }}
</div>
</div>
<div class="table-row">
<div class="table-cell label">{{ t('projectExitFeedback.form.exitExecutor') }}</div>
<div class="table-cell value">
{{ formData.exitExecutor }}
</div>
</div>
<div class="table-row">
<div class="table-cell label">{{ t('projectExitFeedback.form.exitDescription') }}</div>
<div class="table-cell value">
{{ formData.exitRemark }}
</div>
</div>
<div class="table-row">
<div class="table-cell label">{{ t('projectExitFeedback.form.exitFiles') }}</div>
<div class="table-cell value">
<UploadFile :modelValue="formData.exitAttachment ? JSON.parse(formData.exitAttachment as string) : []" :fileSize="20" disabled type="simple" :limit="10" :isShowTip="false" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { ProjectExitFeedback } from '/@/views/invMid/projectExitFeedback/interface/type';
const { t } = useI18n();
const props = defineProps<{
modelValue?: ProjectExitFeedback;
}>();
const formData = reactive<ProjectExitFeedback>({ ...props.modelValue });
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
Object.assign(formData, newVal);
}
},
{ immediate: true, deep: true }
);
</script>
<style scoped>
.project-exit-feedback-form {
background-color: #fff;
border-radius: 4px;
border: 1px solid var(--el-border-color-lighter);
padding: 24px;
}
.form-heading {
text-align: center;
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
margin-bottom: 16px;
}
.feedback-table {
border: 1px solid var(--el-border-color);
}
.table-row {
display: flex;
border-top: 1px solid var(--el-border-color);
}
.table-row:first-of-type {
border-top: none;
}
.table-cell {
border-right: 1px solid var(--el-border-color);
padding: 12px;
display: flex;
align-items: center;
}
.table-cell:last-of-type {
border-right: none;
}
.table-cell.label {
width: 160px;
background-color: var(--el-fill-color-light);
font-weight: 500;
}
.table-cell.label.required::after {
content: '*';
color: var(--el-color-danger);
margin-left: 4px;
}
.table-cell.value {
flex: 1;
}
.interactive-icon {
color: var(--el-color-primary);
}
.feedback-table :deep(.el-input),
.feedback-table :deep(.el-select),
.feedback-table :deep(.el-date-editor),
.feedback-table :deep(.el-textarea) {
width: 100%;
}
.options {
width: 20px;
height: 20px;
cursor: pointer;
border: 1px solid var(--el-border-color);
border-radius: 50%;
}
.active {
background-color: var(--el-color-primary);
color: white;
}
</style>

View File

@@ -0,0 +1,290 @@
<template>
<div class="project-exit-feedback-form" :model="formData" :rules="rules" ref="formRef">
<div class="form-heading">{{ t('projectExitFeedback.form.title') }}</div>
<div class="feedback-table">
<div class="table-row">
<div class="table-cell label required">{{ t('projectExitFeedback.form.projectName') }}</div>
<div class="table-cell value project-name-cell" >
<el-input v-model="formData.projectName" :placeholder="t('projectExitFeedback.placeholder.projectName')" readonly>
<template #append>
<el-icon class="cursor-pointer interactive-icon" @click="handleSelectProject">
<FolderOpened />
</el-icon>
</template>
</el-input>
</div>
</div>
<div class="table-row">
<div class="table-cell label">{{ t('projectExitFeedback.form.exitTime') }}</div>
<div class="table-cell value">
<el-date-picker
v-model="formData.exitTime"
type="date"
value-format="YYYY-MM-DD"
:placeholder="t('projectExitFeedback.placeholder.select')"
style="width: 100%"
/>
</div>
</div>
<div class="table-row">
<div class="table-cell label">{{ t('projectExitFeedback.form.exitExecutor') }}</div>
<div class="table-cell value">
<el-input v-model="formData.exitExecutor" :placeholder="t('projectExitFeedback.placeholder.projectName')" readonly>
<template #append>
<el-icon class="cursor-pointer interactive-icon" @click="handleSelectUser">
<FolderOpened />
</el-icon>
</template>
</el-input>
</div>
</div>
<div class="table-row">
<div class="table-cell label">{{ t('projectExitFeedback.form.exitDescription') }}</div>
<div class="table-cell value">
<el-input v-model="formData.exitRemark" type="textarea" :rows="3" :placeholder="t('projectExitFeedback.placeholder.input')" />
</div>
</div>
<div class="table-row">
<div class="table-cell label">{{ t('projectExitFeedback.form.exitFiles') }}</div>
<div class="table-cell value">
<el-input :placeholder="t('projectExitFeedback.placeholder.selectAttachment')" readonly>
<template #append>
<UploadFile :modelValue="formData.exitAttachment" @change="handleChange" :fileSize="20" type="simple" :limit="10" :isShowTip="false" />
</template>
</el-input>
</div>
</div>
</div>
</div>
<el-dialog width="80%" title="关联列表" v-model="dialogShow" :close-on-click-modal="false">
<template #header> </template>
<el-table border :data="tableData" height="50vh" @current-change="handleSelectionChange">
<el-table-column label="选择" width="80">
<template #default="scope">
<div class="options" :class="active === scope.$index ? 'active' : ''"></div>
</template>
</el-table-column>
<el-table-column type="index" :label="t('projectExitPlanLibrary.table.index')" width="60" />
<el-table-column prop="projectName" :label="t('projectExitPlanLibrary.table.projectName')" min-width="180" />
<el-table-column prop="exitRecommendation" :label="t('projectExitPlanLibrary.table.exitSuggestion')" min-width="160" />
<el-table-column prop="exitMethod" :label="t('projectExitPlanLibrary.table.exitMethod')" min-width="140" />
<el-table-column prop="executionTask" :label="t('projectExitPlanLibrary.table.executionTask')" min-width="160" />
<el-table-column prop="taskDescription" :label="t('projectExitPlanLibrary.table.taskDescription')" min-width="200" show-overflow-tooltip />
<el-table-column prop="executor" :label="t('projectExitPlanLibrary.table.executor')" min-width="120" />
<el-table-column prop="taskStartDate" :label="t('projectExitPlanLibrary.table.taskStartTime')" min-width="140" />
<el-table-column prop="taskEndDate" :label="t('projectExitPlanLibrary.table.taskEndTime')" min-width="140" />
<el-table-column prop="applicationDept" :label="t('projectExitPlanLibrary.table.applyDepartment')" min-width="160" />
<el-table-column prop="reviewNotes" :label="t('projectExitPlanLibrary.table.submitMatter')" min-width="160" />
</el-table>
<div class="mt15 flex justify-end pagination-wrapper">
<el-pagination
background
layout="sizes, prev, pager, next, jumper, total"
:total="total"
:page-sizes="[10, 20, 50, 100]"
v-model:current-page="queryForm.page"
v-model:page-size="queryForm.size"
@size-change="handlePageSizeChange"
@current-change="handlePageChange"
/>
</div>
<template #footer>
<el-button type="primary" @click="confirmBtn">确定</el-button>
<el-button>取消</el-button>
</template>
</el-dialog>
<user-select :visible="userDialog" @ok="confirmUserBtn"/>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { FolderOpened } from '@element-plus/icons-vue';
import { ProjectExitFeedback } from '/@/views/invMid/projectExitFeedback/interface/type';
import userSelect from "/@/components/UserSelect/index.vue"
import { FormRules } from 'element-plus';
import { paramsDataProjectExitPlan, projectExitPlanItem } from '/@/views/invMid/projectExitPlan/interface/type';
import { getProjectExitPlanPage } from '/@/api/investment/projectExitPlan';
const { t } = useI18n();
const formRef = ref<HTMLFormElement | null>();
const rules = ref<FormRules<ProjectExitFeedback>>({});
const props = defineProps<{
modelValue?: ProjectExitFeedback;
}>();
const active = ref<number>(-1);
const queryForm = ref<paramsDataProjectExitPlan>({
projectName: '',
reviewNotes: '',
applicationDept: '',
page: 1,
size: 10,
});
const userDialog = ref(false)
const emit = defineEmits<{
(e: 'update:modelValue', value: ProjectExitFeedback): void;
}>();
const projectName = ref('');
const dialogShow = ref(false);
const formData = reactive<ProjectExitFeedback>({ ...props.modelValue });
const handleSelectProject = () => {
dialogShow.value = true;
};
const handlePageSizeChange = (size:number) =>{
queryForm.value.size = size;
getDataList();
}
const handlePageChange = (page:number) =>{
queryForm.value.page = page;
getDataList();
}
const tableData = ref<projectExitPlanItem[]>([]);
watch(()=>formData, () => {
emit('update:modelValue', { ...formData })
}, { deep: true,immediate: true});
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
Object.assign(formData, newVal);
}
},
{ immediate: true, deep: true }
);
interface ProjectExitPlanLibraryItem {
id: number;
projectName: string;
exitSuggestion: string;
exitMethod: string;
executionTask: string;
taskDescription: string;
executor: string;
taskStartTime: string;
taskEndTime: string;
applyDepartment: string;
submitMatter: string;
projectId?: number;
}
const total = ref<number>(0);
const selectedRows = ref<ProjectExitPlanLibraryItem>();
const handleSelectionChange = (rows: ProjectExitPlanLibraryItem) => {
selectedRows.value = rows;
projectName.value = selectedRows.value?.projectName;
active.value = tableData.value.findIndex((item) => item.id === rows.id);
};
const confirmBtn = () => {
emit('update:modelValue', {
...formData,
projectName:projectName.value,
projectId: selectedRows.value?.projectId,
});
dialogShow.value = false;
};
const getDataList = () => {
getProjectExitPlanPage(queryForm.value).then((res) => {
tableData.value = res.data.records;
total.value = res.data.total;
});
};
getDataList();
const handleSelectUser = () =>{
userDialog.value = true
}
const confirmUserBtn = (value:any) =>{
formData.exitExecutor = value?.name
}
const handleChange = (_:any, data:any[]) => {
if (!data || Object.prototype.toString.call(data) !== '[object Array]' || data.length === 0){
formData.exitAttachment = [];
return;
}
formData.exitAttachment = data.map((item:any) => {
return {
name: item.name,
url: item.url
}
})
}
</script>
<style scoped>
.project-exit-feedback-form {
background-color: #fff;
border-radius: 4px;
border: 1px solid var(--el-border-color-lighter);
padding: 24px;
}
.form-heading {
text-align: center;
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
margin-bottom: 16px;
}
.feedback-table {
border: 1px solid var(--el-border-color);
}
.table-row {
display: flex;
border-top: 1px solid var(--el-border-color);
}
.table-row:first-of-type {
border-top: none;
}
.table-cell {
border-right: 1px solid var(--el-border-color);
padding: 12px;
display: flex;
align-items: center;
}
.table-cell:last-of-type {
border-right: none;
}
.table-cell.label {
width: 160px;
background-color: var(--el-fill-color-light);
font-weight: 500;
}
.table-cell.label.required::after {
content: '*';
color: var(--el-color-danger);
margin-left: 4px;
}
.table-cell.value {
flex: 1;
}
.interactive-icon {
color: var(--el-color-primary);
}
.feedback-table :deep(.el-input),
.feedback-table :deep(.el-select),
.feedback-table :deep(.el-date-editor),
.feedback-table :deep(.el-textarea) {
width: 100%;
}
.options {
width: 20px;
height: 20px;
cursor: pointer;
border: 1px solid var(--el-border-color);
border-radius: 50%;
}
.active {
background-color: var(--el-color-primary);
color: white;
}
</style>

View File

@@ -0,0 +1,428 @@
<template>
<div class="project-exit-plan-form">
<div class="form-heading">
<div class="form-title">{{ t('projectExitPlan.form.title') }}</div>
<div class="form-meta">
<div class="meta-item">
<span>{{ t('projectExitPlan.form.applyDepartmentLabel') }}</span>
<el-input v-model="formData.applicationDept" size="small" :placeholder="t('projectExitPlan.placeholder.input')" />
</div>
<div class="meta-item">
<span>{{ t('projectExitPlan.form.applyDateLabel') }}</span>
<span>{{new Date().toLocaleDateString('zh-CN')}}</span>
</div>
</div>
</div>
<el-form :model="formData" :rules="rules" ref="useForm" class="exit-plan-table">
<el-form-item class="table-row" prop="projectName" required>
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.projectName') }}</div>
</template>
<div class="table-cell value project-name-cell">
<el-input v-model="formData.projectName" :placeholder="t('projectExitPlan.form.projectNamePlaceholder')" readonly>
<template #suffix>
<el-icon class="cursor-pointer interactive-icon" @click="handleSelectProject">
<FolderOpened />
</el-icon>
</template>
</el-input>
</div>
<div class="table-cell action">
<el-button text circle :icon="OfficeBuilding" class="action-btn" @click="handleSelectProject" />
</div>
</el-form-item>
<el-form-item class="table-row" prop="exitRecommendation" required>
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.exitSuggestion') }}</div>
</template>
<div class="table-cell value">
<el-input v-model="formData.exitRecommendation" :placeholder="t('projectExitPlan.placeholder.input')" />
</div>
</el-form-item>
<el-form-item class="table-row">
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.exitMethod') }}</div>
</template>
<div class="table-cell value">
<el-input v-model="formData.exitMethod" :placeholder="t('projectExitPlan.placeholder.input')" />
</div>
</el-form-item>
<el-form-item class="table-row">
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.executionTask') }}</div>
</template>
<div class="table-cell value">
<el-input v-model="formData.executionTask" :placeholder="t('projectExitPlan.placeholder.input')" />
</div>
</el-form-item>
<el-form-item class="table-row">
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.taskDescription') }}</div>
</template>
<div class="table-cell value">
<el-input v-model="formData.taskDescription" type="textarea" :rows="1" :placeholder="t('projectExitPlan.placeholder.input')" />
</div>
</el-form-item>
<el-form-item class="table-row">
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.executor') }}</div>
</template>
<div class="table-cell value">
<el-select v-model="formData.executor" clearable :placeholder="t('projectExitPlan.form.executorPlaceholder')">
<el-option :label="t('projectExitPlan.form.executorPlaceholder')" value="executor-placeholder" />
</el-select>
</div>
</el-form-item>
<el-form-item class="table-row two-columns">
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.taskStartTime') }}</div>
</template>
<div class="table-cell value">
<el-date-picker
v-model="formData.taskStartDate"
type="date"
value-format="YYYY-MM-DD"
:placeholder="t('projectExitPlan.placeholder.select')"
style="width: 100%"
/>
</div>
<div class="table-cell label">{{ t('projectExitPlan.form.taskEndTime') }}</div>
<div class="table-cell value">
<el-date-picker
v-model="formData.taskEndDate"
type="date"
value-format="YYYY-MM-DD"
:placeholder="t('projectExitPlan.placeholder.select')"
style="width: 100%"
/>
</div>
</el-form-item>
</el-form>
<el-dialog width="80%" title="关联列表" v-model="visible" :close-on-click-modal="false">
<template #header>
<el-form inline>
<el-form-item label="项目名称">
<el-input v-model="queryForm.projectName" placeholder="请输入项目名称" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getListSearch">筛选</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</template>
<el-table :data="tableData" style="width: 100%" height="50vh" highlight-current-row border @current-change="handleSelectionChange">
<el-table-column label="选择" width="80">
<template #default="scope">
<div class="options" :class="active === scope.$index ? 'active' : ''"></div>
</template>
</el-table-column>
<el-table-column prop="projectName" :label="t('projectLibrary.table.projectName')" min-width="200" />
<el-table-column prop="projectStartTime" :label="t('projectLibrary.table.projectImplementationStart')" min-width="160" />
<el-table-column prop="projectNature" :label="t('projectLibrary.table.projectNature')" min-width="140">
<template #default="{ row }">
{{ projectNatureOptions.find((item: Enums) => item.value === row.projectNature)?.label }}
</template>
</el-table-column>
<el-table-column prop="projectOwnerUnit" :label="t('projectLibrary.table.projectOwnerUnit')" min-width="200" />
<el-table-column prop="" label="项目实施单位" />
<el-table-column prop="projectInvestmentDirection" :label="t('projectLibrary.table.projectDirection')" min-width="160" />
<el-table-column prop="mainBusinessTypes" :label="t('planApply.applySection.mainBusinessType')" min-width="160">
<template #default="{ row }">
{{ mainBusinessOptions.find((item: Enums) => item.value === row.mainBusinessTypes)?.label }}
</template>
</el-table-column>
<el-table-column prop="constructionStage" :label="t('projectLibrary.table.constructionStage')" min-width="120" />
<el-table-column prop="investmentCategory" :label="t('projectLibrary.table.investmentCategory')" min-width="140">
<template #default="{ row }">
{{ investmentCategoryOptions.find((item: Enums) => item.value === row.investmentCategory)?.label }}
</template>
</el-table-column>
<el-table-column prop="" label="操作" fixed="right" width="100">
<template #default="{ row }">
<el-button type="primary" link @click="handleDetail(row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt15 flex justify-end pagination-wrapper">
<el-pagination
background
layout="sizes, prev, pager, next, jumper, total"
:total="total"
:page-sizes="[10, 20, 50, 100]"
v-model:page-size="queryForm.size"
v-model:current-page="queryForm.page"
@size-change="handlePageSizeChange"
@current-change="handlePageChange"
/>
</div>
<template #footer>
<el-button type="primary" @click="tableSubmit">确定</el-button>
<el-button @click="visible = false">取消</el-button>
</template>
</el-dialog>
<el-dialog v-model="visible2" width="80%" title="项目详情">
<ProjectPlanApplyFormDetails v-model="detailFormData" :isEdit="false" />
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { FolderOpened, OfficeBuilding } from '@element-plus/icons-vue';
import { investmentProjectsPlanGetById, investmentProjectsPlanPage } from '/@/api/investment/investmentManagement';
import { investmentProjectsPlanList } from '/@/views/invMid/projectLibrary/interface/types';
import { ProjectPlanApplyFormItem } from '/@/components/investment/interface/types';
import { type Enums, investmentCategoryOptions, mainBusinessOptions, projectNatureOptions } from '/@/hooks/enums';
import type { ProjectTask } from '/@/views/invMid/projectExitPlan/interface/type';
import ProjectPlanApplyFormDetails from '/@/components/investment/common/ProjectPlanApplyFormDetails.vue';
import { type FormInstance, FormRules } from 'element-plus';
const { t } = useI18n();
const visible = ref<boolean>(false);
const visible2 = ref<boolean>(false);
const props = defineProps<{
modelValue?: ProjectTask;
rules:FormRules;
}>();
const useForm = ref<FormInstance | undefined>();
const emit = defineEmits<{
(e: 'update:modelValue', value: ProjectTask): void;
}>();
const defaultForm: ProjectTask = {
id: undefined,
projectName: '',
projectId: undefined,
exitRecommendation: '',
executionTask:'',
taskDescription:'',
executor:'',
taskStartDate:new Date().toISOString().substring(0,10),
taskEndDate:new Date().toISOString().substring(0,10),
};
const formData = reactive<ProjectTask>({ ...defaultForm, ...props.modelValue });
const total = ref<number>(0);
const queryForm = ref<investmentProjectsPlanList>({
page: 1,
size: 10,
projectName: '',
});
const tableData = ref<ProjectPlanApplyFormItem[]>([]);
const handleSelectProject = () => {
visible.value = true;
};
const getListOfProjects = async () => {
try {
const res = await investmentProjectsPlanPage(queryForm.value);
total.value = res.data.total as number;
tableData.value = res.data.records;
} catch (error: any) {
console.log(error);
}
};
const handlePageSizeChange = (size: number) => {
queryForm.value.size = size;
getListOfProjects();
};
const handlePageChange = (page: number) => {
queryForm.value.page = page;
getListOfProjects();
};
getListOfProjects();
const getListSearch = () =>{
queryForm.value.page = 1;
getListOfProjects();
}
const handleReset = () =>{
queryForm.value.projectName = '';
}
watch(
formData,
(newVal) => {
console.log(newVal,'formData');
emit('update:modelValue', { ...newVal });
},
{ deep: true }
);
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
console.log(newVal,'asdas');
Object.assign(formData, newVal);
}
},
{ immediate: true, deep: true }
);
const active = ref<number>(-1);
const selectItem = ref<string>('');
const selectId = ref<number>();
const handleSelectionChange = (val: any) => {
active.value = tableData.value.findIndex((item) => item.id === val.id);
selectItem.value = val.projectName;
selectId.value = val.id;
formData.deptId = val.deptId;
};
const tableSubmit = () => {
visible.value = false;
formData.projectName = selectItem.value;
formData.projectId = selectId.value;
};
const detailFormData = ref<any>({});
const handleDetail = (row: any) => {
visible2.value = true;
investmentProjectsPlanGetById(row.id).then((res) => {
detailFormData.value.entity = res.data;
detailFormData.value.investmentProjects = res.data.projectInvestmentEntities;
});
};
defineExpose({
validateRef: () => {
return useForm.value?.validate();
},
})
</script>
<style scoped>
.project-exit-plan-form {
background-color: #fff;
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
padding: 24px;
}
.form-heading {
text-align: center;
margin-bottom: 20px;
}
.form-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 8px;
}
.form-meta {
display: flex;
justify-content: center;
gap: 40px;
flex-wrap: wrap;
}
.meta-item {
display: flex;
align-items: center;
gap: 8px;
}
.meta-item :deep(.el-input),
.meta-item :deep(.el-date-editor) {
width: 180px;
}
.exit-plan-table {
border: 1px solid var(--el-border-color);
border-collapse: collapse;
}
.table-row {
display: flex;
border-top: 1px solid var(--el-border-color);
}
.table-row:first-of-type {
border-top: none;
}
.table-cell {
border-right: 1px solid var(--el-border-color);
padding: 12px;
display: flex;
align-items: center;
}
.table-cell:last-of-type {
border-right: none;
}
.table-cell.label {
width: 180px;
height: 100%;
background-color: var(--el-fill-color-light);
font-weight: 500;
}
.table-cell.label.required::after {
content: '*';
margin-left: 4px;
color: var(--el-color-danger);
}
.table-cell.value {
flex: 1;
}
.table-row {
margin: 0;
}
.table-row.two-columns .table-cell.label {
width: 160px;
}
.table-row.two-columns .table-cell.value {
flex: 1;
}
.project-name-cell {
flex: 1;
}
.table-cell.action {
width: 80px;
justify-content: center;
}
.interactive-icon {
color: var(--el-color-primary);
}
.action-btn {
font-size: 18px;
}
.exit-plan-table :deep(.el-input),
.exit-plan-table :deep(.el-select),
.exit-plan-table :deep(.el-textarea),
.exit-plan-table :deep(.el-date-editor) {
width: 100%;
}
.options {
width: 20px;
height: 20px;
cursor: pointer;
border: 1px solid var(--el-border-color);
border-radius: 50%;
}
.active {
background-color: var(--el-color-primary);
color: white;
}
:deep(.el-form-item--default .el-form-item__label) {
height: 100%;
position: relative;
}
:deep(.el-form-item__label:before) {
left: 4px;
position: absolute;
}
</style>

View File

@@ -0,0 +1,239 @@
<template>
<div class="project-exit-plan-form">
<div class="form-heading">
<div class="form-title">{{ title ? title : t('projectExitPlan.form.title') }}</div>
<div class="form-meta">
<div class="meta-item">
<span>{{ t('projectExitPlan.form.applyDepartmentLabel') }}</span>
<span>{{ formData.applicationDept }}</span>
</div>
<div class="meta-item">
<span>{{ t('projectExitPlan.form.applyDateLabel') }}</span>
<span>{{new Date().toLocaleDateString('zh-CN')}}</span>
</div>
</div>
</div>
<el-form :model="formData" class="exit-plan-table">
<el-form-item class="table-row" prop="projectName" required>
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.projectName') }}</div>
</template>
<div class="table-cell value project-name-cell">
<span>{{ formData.projectName }}</span>
</div>
<div class="table-cell action">
</div>
</el-form-item>
<el-form-item class="table-row" prop="exitRecommendation" required>
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.exitSuggestion') }}</div>
</template>
<div class="table-cell value">
<span>{{ formData.exitRecommendation }}</span>
</div>
</el-form-item>
<el-form-item class="table-row">
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.exitMethod') }}</div>
</template>
<div class="table-cell value">
<span>{{ formData.exitMethod }}</span>
</div>
</el-form-item>
<el-form-item class="table-row">
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.executionTask') }}</div>
</template>
<div class="table-cell value">
<span>{{ formData.executionTask }}</span>
</div>
</el-form-item>
<el-form-item class="table-row">
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.taskDescription') }}</div>
</template>
<div class="table-cell value">
<span>{{ formData.taskDescription }}</span>
</div>
</el-form-item>
<el-form-item class="table-row">
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.executor') }}</div>
</template>
<div class="table-cell value">
<span>{{ formData.executor }}</span>
</div>
</el-form-item>
<el-form-item class="table-row two-columns">
<template #label>
<div class="table-cell label">{{ t('projectExitPlan.form.taskStartTime') }}</div>
</template>
<div class="table-cell value">
<span>{{ formData.taskStartDate }}</span>
</div>
<div class="table-cell label">{{ t('projectExitPlan.form.taskEndTime') }}</div>
<div class="table-cell value">
<span>{{ formData.taskEndDate }}</span>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import type { ProjectTask } from '/@/views/invMid/projectExitPlan/interface/type';
const { t } = useI18n();
const props = defineProps<{
modelValue: ProjectTask;
title?: string;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: ProjectTask): void;
}>();
const defaultForm: ProjectTask = {
id: undefined,
projectName: '',
projectId: undefined,
exitRecommendation: '',
executionTask:'',
taskDescription:'',
executor:'',
taskStartDate:new Date().toISOString().substring(0,10),
taskEndDate:new Date().toISOString().substring(0,10),
};
const formData = reactive<ProjectTask>({ ...defaultForm, ...props.modelValue });
watch(
formData,
(newVal) => {
emit('update:modelValue', { ...newVal });
},
{ deep: true }
);
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
Object.assign(formData, newVal);
}
},
{ immediate: true, deep: true }
);
</script>
<style scoped>
.project-exit-plan-form {
background-color: #fff;
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
padding: 24px;
}
.form-heading {
text-align: center;
margin-bottom: 20px;
}
.form-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 8px;
}
.form-meta {
display: flex;
justify-content: center;
gap: 40px;
flex-wrap: wrap;
}
.meta-item {
display: flex;
align-items: center;
gap: 8px;
}
.exit-plan-table {
border: 1px solid var(--el-border-color);
border-collapse: collapse;
}
.table-row {
display: flex;
border-top: 1px solid var(--el-border-color);
}
.table-row:first-of-type {
border-top: none;
}
.table-cell {
border-right: 1px solid var(--el-border-color);
padding: 12px;
display: flex;
align-items: center;
}
.table-cell:last-of-type {
border-right: none;
}
.table-cell.label {
width: 180px;
height: 100%;
background-color: var(--el-fill-color-light);
font-weight: 500;
}
.table-cell.label.required::after {
content: '*';
margin-left: 4px;
color: var(--el-color-danger);
}
.table-cell.value {
flex: 1;
}
.table-row {
margin: 0;
}
.table-row.two-columns .table-cell.label {
width: 160px;
}
.table-row.two-columns .table-cell.value {
flex: 1;
}
.project-name-cell {
flex: 1;
}
.table-cell.action {
width: 80px;
justify-content: center;
}
:deep(.el-form-item--default .el-form-item__label) {
height: 100%;
position: relative;
}
:deep(.el-form-item__label:before) {
left: 4px;
position: absolute;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,710 @@
<template>
<div class="project-plan-apply-form border-r border-[#e5e7eb]">
<FlowFormView :process-instance-id="processInstanceId"/>
<div style="display: flex;justify-content: space-between;align-items: center">
<div class="form-section-title">
{{ t('planApply.applySection.title') }}
</div>
<div class="page-title">{{ title ? title : '' }}</div>
<div class="page-unit">
{{ t('planApply.detail.unitLabel') }}
</div>
</div>
<el-form :model="formData" label-width="165px" class="plan-form" :rules="rules" ref="useForm">
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('planApply.applySection.projectName')" required prop="entity.projectName">
<template #label>
<span style="color: var(--el-text-color-regular)">{{t('planApply.applySection.projectName')}}</span>
</template>
<span>{{formData.entity.projectName}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.groupName')" required prop="entity.groupCompany">
<template #label>
<span style="color: var(--el-text-color-regular)">{{t('planApply.applySection.groupName')}}</span>
</template>
<span>{{formData.entity.groupCompany}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.projectNature')">
<span>{{projectNatureOptions.find((item:Enums) => item.value === formData.entity.projectNature)?.label}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.projectMainUnit')" required prop="entity.projectMainEntity">
<span>{{formData.entity.projectMainEntity}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectOwnerUnit')">
<span>{{formData.entity.projectOwnerUnit}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.investmentCategory')" required prop="entity.investmentCategory">
<template #label>
<span style="color: var(--el-text-color-regular)">{{t('planApply.applySection.investmentCategory')}}</span>
</template>
<span>{{investmentCategoryOptions.find((item:Enums) => item.value === formData.entity.investmentCategory)?.label}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.investmentType')" required prop="entity.investmentArea">
<template #label>
<span style="color: var(--el-text-color-regular)">{{t('planApply.applySection.investmentType')}}</span>
</template>
<span>{{investmentAreaOptions.find((item:Enums) => item.value ===formData.entity.investmentArea)?.label}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.projectAddress')" required prop="entity.projectAddress">
<template #label>
<span style="color: var(--el-text-color-regular)">{{t('planApply.applySection.projectAddress')}}</span>
</template>
<span>{{formData.entity.projectAddress}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.projectDetailAddress')" required prop="entity.projectAddressDetail">
<template #label>
<span style="color: var(--el-text-color-regular)">{{t('planApply.applySection.projectDetailAddress')}}</span>
</template>
<span>{{formData.entity.projectAddressDetail}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectDirection')" required prop="entity.projectInvestmentDirection">
<span>{{formData.entity.projectInvestmentDirection}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.directionSubdivision')" required prop="entity.investmentDirectionSegmentation">
<template #label>
<span style="color: var(--el-text-color-regular)">{{t('reserveRegistration.basicInfo.directionSubdivision')}}</span>
</template>
<span>{{projectDirectionDetailsOptions.find((item:Enums) => item.value ===formData.entity.investmentDirectionSegmentation)?.label}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.projectSource')" required prop="entity.projectSource">
<template #label>
<span style="color: var(--el-text-color-regular)">{{t('reserveRegistration.basicInfo.projectSource')}}</span>
</template>
<span>{{projectSourceOptions.find((item:Enums) => item.value === formData.entity.projectSource)?.label}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.constructionNature')" required prop="entity.constructionNature">
<template #label>
<span style="color: var(--el-text-color-regular)">{{t('planApply.applySection.constructionNature')}}</span>
</template>
<span>{{constructionNatureOptions.find((item:Enums) => item.value === formData.entity.constructionNature)?.label}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.majorInvestmentProject')" required prop="entity.majorInvestmentProjects">
<span>{{formData.entity.majorInvestmentProjects}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.keyProject')" required prop="entity.keyProject">
<span>{{projectImportantOptions.find((item:Enums) => item.value === formData.entity.keyProject)?.label}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span=" 12">
<el-form-item :label="t('planApply.applySection.isWithinMainBusiness')" required prop="entity.isMainBusiness">
<span>{{formData.entity.isMainBusiness == t('planApply.common.yes') ? t('planApply.common.yes') : t('planApply.common.no')}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.mainBusinessType')" :required="formData.entity.isMainBusiness === t('planApply.common.yes')" prop="entity.mainBusinessTypes">
<span>{{mainBusinessOptions.find((item:Enums) => item.value === formData.entity.mainBusinessTypes)?.label}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.mainBusinessCode')" required prop="entity.mainBusinessCode">
<span>{{formData.entity.mainBusinessTypes}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.isManufacturing')" required prop="entity.isManufacturingIndustry">
<span>{{formData.entity.isManufacturingIndustry == t('planApply.common.yes') ? t('planApply.common.yes') : t('planApply.common.no')}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('reserveRegistration.basicInfo.strategicEmergingIndustry')" required prop="entity.isStrategicEmergingIndustries">
<span>{{strategicIndustryOptions.find((item:Enums) => item.value === formData.entity.isStrategicEmergingIndustries)?.label}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.cityStrategy')" required prop="entity.urbanStrategy">
<span>{{cityStrategyOptions.find((item:Enums) => item.value === formData.entity.urbanStrategy)?.label}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.projectImplementationStart')" required prop="entity.projectStartTime">
<span>{{formData.entity.projectStartTime}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.applySection.projectImplementationEnd')" required prop="entity.projectEndTime">
<span>{{formData.entity.projectEndTime}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="plan-investment-block">
<el-col :span="12">
<div class="plan-investment-panel left-panel">
<div class="panel-title">{{ t('planApply.planInvestment.currentYearTotalTitle') }}</div>
<div style="flex: 1;">
<el-form-item style="height: 82px;" :label="t('planApply.planInvestment.planYear')">
<span>{{formData.entity.planInvestmentYear}}</span>
</el-form-item>
<el-form-item style="height: 82px;" :label="t('planApply.planInvestment.plannedImageQuota')"
required prop="entity.planImageQuota">
<span>{{formData.entity.planImageQuota}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</el-form-item>
<el-form-item style="height: 82px;" :label="t('planApply.planInvestment.annualPlanPayment')"
required prop="entity.planPaymentLimit">
<span>{{formData.entity.planPaymentLimit}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</el-form-item>
</div>
</div>
</el-col>
<el-col :span="12">
<div class="plan-investment-panel right-panel">
<div class="panel-title">{{ t('planApply.planInvestment.currentYearFundingTitle') }}</div>
<div style="flex: 1;">
<el-form-item :label="t('planApply.investmentEstimate.capitalFunding')" required prop="entity.ownedFunds">
<span>{{formData.entity.ownedFunds}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</el-form-item>
<el-form-item :label="t('planApply.investmentEstimate.financialFunding')" required prop="entity.financialFunds">
<span>{{formData.entity.financialFunds}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</el-form-item>
<el-form-item :label="t('planApply.investmentEstimate.externalRaisedFunds')" required prop="entity.externalRaisedCapital">
<span>{{formData.entity.externalRaisedCapital}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</el-form-item>
<el-form-item :label="t('planApply.investmentEstimate.otherFunding')" required prop="entity.otherFunds">
<span>{{formData.entity.otherFunds}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</el-form-item>
<el-form-item :label="t('planApply.investmentEstimate.financialFundingDescription')">
<span>{{formData.entity.governmentFundSourceDesc}}</span>
</el-form-item>
<el-form-item :label="t('planApply.investmentEstimate.otherFundingDescription')">
<span>{{formData.entity.otherFundSourceDesc}}</span>
</el-form-item>
</div>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('planApply.applySection.projectOverview')" required prop="entity.projectDesc">
<span>{{formData.entity.projectDesc}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row class="form-row">
<el-col :span="24">
<el-form-item :label="t('planApply.applySection.projectInitialPlan')">
<span>{{formData.entity.projectPreliminaryPlan}}</span>
</el-form-item>
<el-form-item :label="t('planApply.headerForm.attachments')">
<UploadFile :modelValue="formData.entity.projectPreliminaryPlanAttachment" :fileSize="20" type="simple" :limit="10"/>
</el-form-item>
</el-col>
</el-row>
<el-row class="form-row">
<el-col :span="24">
<el-form-item :label="t('planApply.applySection.remark')">
<span>{{formData.entity.remark}}</span>
</el-form-item>
</el-col>
</el-row>
<div class="form-section-title">{{ t('planApply.investmentStatus.title') }}</div>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('planApply.investmentStatus.totalInvestmentAmount')" required prop="entity.projectTotalAmount">
<span>{{formData.entity.projectTotalAmount}}{{t('planApply.investmentEstimate.unitSuffix')}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.investmentStatus.cumulativeInvestmentLastYear')" required prop="entity.lastYearCompleted">
<span>{{formData.entity.lastYearCompleted}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('planApply.investmentStatus.ourTotalInvestmentAmount')" required prop="entity.ourInvestmentTotalAmount">
<span>{{formData.entity.ourInvestmentTotalAmount}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.investmentStatus.ourCumulativeInvestmentLastYear')" required prop="entity.ourLastYearCompleted">
<span>{{formData.entity.ourLastYearCompleted}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</el-form-item>
</el-col>
</el-row>
<div class="form-section-title">{{ t('planApply.annualInvestmentInfo.title') }}</div>
<div class="table-actions" v-if="isEdit">
<el-button type="primary" @click="handleAddAnnualInvestment">
{{ t('planApply.annualInvestmentInfo.add') }}
</el-button>
<el-button @click="handleCopyAnnualInvestment">
{{ t('planApply.annualInvestmentInfo.copy') }}
</el-button>
<el-button @click="handleRemoveAnnualInvestment">
{{ t('planApply.annualInvestmentInfo.remove') }}
</el-button>
<el-button @click="handleRemoveAllAnnualInvestment">
{{ t('planApply.annualInvestmentInfo.removeAll') }}
</el-button>
</div>
<el-table :data="formData.investmentProjects" border style="width: 100%" class="annual-investment-table">
<el-table-column width="60" align="center">
<template>
<el-radio v-model="selectedAnnualInvestmentIndex" />
</template>
</el-table-column>
<el-table-column :label="t('planApply.annualInvestmentInfo.table.index')" width="60" align="center">
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
<el-table-column :label="t('planApply.annualInvestmentInfo.table.planYear')" min-width="140">
<template #default="{ row }">
<span>{{row.plannedInvestmentYear}}</span>
</template>
</el-table-column>
<el-table-column :label="t('planApply.annualInvestmentInfo.table.plannedImageQuota')" min-width="160">
<template #default="{ row }">
<span>{{row.plannedImageAmount}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</template>
</el-table-column>
<el-table-column :label="t('planApply.annualInvestmentInfo.table.annualPlanPayment')" min-width="160">
<template #default="{ row }">
<span>{{row.plannedPaymentAmount}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</template>
</el-table-column>
<el-table-column :label="t('planApply.annualInvestmentInfo.table.fundingSources')" align="center">
<el-table-column :label="t('planApply.annualInvestmentInfo.table.capitalFunding')" min-width="140">
<template #default="{ row }">
<span>{{row.selfFunding}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</template>
</el-table-column>
<el-table-column :label="t('planApply.annualInvestmentInfo.table.externalRaisedFunds')"
min-width="150">
<template #default="{ row }">
<span>{{row.fiscalFunding}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</template>
</el-table-column>
<el-table-column :label="t('planApply.annualInvestmentInfo.table.financialFunding')"
min-width="140">
<template #default="{ row }">
<span>{{row.fiscalFunding}}{{ t('planApply.investmentEstimate.unitSuffix') }} </span>
</template>
</el-table-column>
<el-table-column :label="t('planApply.annualInvestmentInfo.table.otherFunding')" min-width="140">
<template #default="{ row }">
<span>{{row.otherFunding}}{{ t('planApply.investmentEstimate.unitSuffix') }} </span>
</template>
</el-table-column>
<el-table-column :label="t('planApply.annualInvestmentInfo.table.financialFundingDescription')"
min-width="200">
<template #default="{ row }">
<span>{{row.fiscalFundingSource}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</template>
</el-table-column>
<el-table-column :label="t('planApply.annualInvestmentInfo.table.otherFundingDescription')"
min-width="200">
<template #default="{ row }">
<span>{{row.otherFundingSource}}{{ t('planApply.investmentEstimate.unitSuffix') }}</span>
</template>
</el-table-column>
</el-table-column>
</el-table>
<div class="form-section-title">{{ t('planApply.decisionInfo.title') }}</div>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('planApply.decisionInfo.decisionType')">
<span>{{formData.entity.decisionType}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.decisionInfo.isCompleteEstablishmentProcedures')" required prop="entity.isProjectApprovalCompleted">
<span>{{formData.entity.isProjectApprovalCompleted}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('planApply.decisionInfo.establishmentDocumentNumber')">
<span>{{formData.entity.projectApprovalFileNo}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.decisionInfo.establishmentDocumentInfo')">
<span>{{formData.entity.projectApprovalFileInfo}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('planApply.decisionInfo.isCompleteDecisionProcedures')" required prop="entity.isDecisionProcedureCompleted">
<span>{{formData.entity.isDecisionProcedureCompleted}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('planApply.decisionInfo.decisionProgramDocumentNumber')">
<span>{{formData.entity.decisionProcedureFileNo}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('planApply.decisionInfo.decisionDocumentInfo')">
<span>{{formData.entity.decisionFileInfo}}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import type { InvestmentProjects,ProjectPlanApplyFormData } from "../interface/types";
import {projectNatureOptions,
investmentAreaOptions,
investmentCategoryOptions,
projectDirectionDetailsOptions,
projectSourceOptions,
constructionNatureOptions,
projectImportantOptions,
mainBusinessOptions,
strategicIndustryOptions,
cityStrategyOptions, Enums
} from "/@/hooks/enums"
import type { FormInstance, FormRules } from 'element-plus';
import FlowFormView from '/@/components/workbench/common/FlowFormView.vue'
const { t } = useI18n();
const useForm = ref<FormInstance | undefined>();
const props = withDefaults(defineProps<{
modelValue?: ProjectPlanApplyFormData;
rules?: FormRules;
isEdit: boolean;
title:string;
processInstanceId: string;
}>(), {
isEdit: true,
title:'',
processInstanceId:''
});
const emit = defineEmits<{
(e: 'update:modelValue', value: ProjectPlanApplyFormData): void;
}>();
const defaultForm: ProjectPlanApplyFormData = {
entity:{
id:null,
projectName: '',
projectNature: '',
groupCompany: '',
projectOwnerUnit: '',
projectMainEntity: '',
projectDepartment: '',
investmentCategory: '',
investmentArea: '',
projectAddress: '',
projectAddressDetail: '',
projectInvestmentDirection: '',
totalInvestment: '',
investmentDirectionSegmentation: '',
projectBackground: '',
constructionNature: '',
constructionStage: '',
keyProject: '',
isMainBusiness: '',
mainBusinessTypes: '',
mainBusinessCode: '',
isManufacturingIndustry: '',
isStuckIndustry: '',
urbanStrategy: '',
projectSource: '',
majorInvestmentProjects: '',
isStrategicEmergingIndustries: '',
projectStartTime: '',
projectEndTime: '',
projectConstructionContent: '',
planInvestmentYear: '',
annualPlanTotal: '',
annualPlanInvestment: '',
planPaymentLimit: '',
plannedImageAmount: '',
ownedFunds: '',
financialFunds: '',
externalFunding: '',
externalRaisedCapital: '',
otherFunds: '',
governmentFundSourceDesc: '',
otherFundSourceDesc: '',
projectTotalAmount: '',
lastYearCompleted: '',
ourInvestmentTotalAmount: '',
ourLastYearCompleted: '',
projectDesc: '',
promotionPlan: '',
projectPreliminaryPlan: '',
projectPreliminaryPlanAttachment: '',
attachments: [],
remark: '',
decisionType: '',
isProjectApprovalCompleted: '',
isDecisionProcedureCompleted: '',
projectApprovalFileNo: '',
projectApprovalFileInfo: '',
decisionProcedureFileNo: '',
decisionFileInfo: '',
submitUnitOpinion: '',
groupInvestmentDeptOpinion: '',
submitUnitLeadershipOpinion: '',
submitUnitMainLeadershipOpinion: '',
planImageQuota:'',
},
investmentProjects: [
{
plannedInvestmentYear: '',
plannedImageAmount: '',
plannedPaymentAmount: '',
selfFunding: '',
externalFunding: '',
fiscalFunding: '',
otherFunding: '',
fiscalFundingSource: '',
otherFundingSource: '',
},
],
};
const formData = ref<ProjectPlanApplyFormData>({ ...defaultForm });
const selectedAnnualInvestmentIndex = ref<number>(formData.value.investmentProjects.length > 0 ? 0 : -1);
const handleAddAnnualInvestment = () => {
const newItem: InvestmentProjects = {
plannedInvestmentYear: '',
plannedImageAmount: '',
plannedPaymentAmount: '',
selfFunding: '',
externalFunding: '',
fiscalFunding: '',
otherFunding: '',
fiscalFundingSource: '',
otherFundingSource: '',
};
formData.value.investmentProjects.push(newItem);
selectedAnnualInvestmentIndex.value = formData.value.investmentProjects.length - 1;
};
const handleCopyAnnualInvestment = () => {
if (selectedAnnualInvestmentIndex.value >= 0 && selectedAnnualInvestmentIndex.value < formData.value.investmentProjects.length) {
const selectedItem = formData.value.investmentProjects[selectedAnnualInvestmentIndex.value];
const copiedItem: InvestmentProjects = { ...selectedItem };
formData.value.investmentProjects.push(copiedItem);
selectedAnnualInvestmentIndex.value = formData.value.investmentProjects.length - 1;
}
};
const handleRemoveAnnualInvestment = () => {
if (selectedAnnualInvestmentIndex.value >= 0 && selectedAnnualInvestmentIndex.value < formData.value.investmentProjects.length) {
formData.value.investmentProjects.splice(selectedAnnualInvestmentIndex.value, 1);
if (selectedAnnualInvestmentIndex.value >= formData.value.investmentProjects.length) {
selectedAnnualInvestmentIndex.value = formData.value.investmentProjects.length - 1;
}
}
};
const handleRemoveAllAnnualInvestment = () => {
formData.value.investmentProjects = [];
selectedAnnualInvestmentIndex.value = -1;
};
watch(
() => props.modelValue,
newVal => {
Object.assign(formData.value, newVal || {});
formData.value.entity.planInvestmentYear = String(newVal?.entity?.planInvestmentYear);
// 如果有数据且没有选中,默认选中第一行
if (formData.value.investmentProjects.length > 0 && selectedAnnualInvestmentIndex.value === -1) {
selectedAnnualInvestmentIndex.value = 0;
}
},
{ immediate: true, deep: true },
);
watch(
()=> formData.value,
() => {
emit('update:modelValue', { ...formData.value });
},
{ deep: true },
);
const validateForm = () => {
return useForm.value?.validate();
}
defineExpose({
validateForm: validateForm
})
</script>
<style scoped>
.page-title-row {
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.page-title {
font-size: 20px;
font-weight: 600;
}
.page-unit {
font-size: 14px;
color: var(--el-text-color-secondary);
}
.project-plan-apply-form {
background: #fff;
border-radius: 4px;
padding: 20px;
}
.form-section-title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
margin: 24px 0 16px;
padding-left: 10px;
border-left: 4px solid var(--el-color-primary);
}
.plan-form {
width: 100%;
}
/* 表单 label 左对齐 */
.plan-form :deep(.el-form-item__label) {
text-align: left;
justify-content: flex-start;
}
/* 必填字段标签显示为红色 */
/*.plan-form :deep(.el-form-item.is-required .el-form-item__label) {
color: var(--el-color-danger);
}*/
.form-row {
margin-bottom: 16px;
margin-top: 16px;
}
.form-row:last-of-type {
margin-bottom: 0;
}
.sub-section-title {
font-size: 15px;
font-weight: 600;
color: var(--el-text-color-primary);
margin: 24px 0 16px;
}
.plan-investment-panel {
display: flex;
flex-direction: row;
align-items: center
}
.panel-title {
width: 200px;
padding-top: 0;
margin-top: 0;
}
.plan-investment-panel :deep(.el-form-item__label) {
padding-top: 0;
line-height: 32px;
}
.plan-investment-panel :deep(.el-form-item) {
align-items: center;
}
.table-actions {
margin-bottom: 16px;
display: flex;
gap: 12px;
}
.annual-investment-table {
width: 100%;
}
.annual-investment-table :deep(.el-table__body-wrapper) {
overflow-x: auto;
}
</style>

View File

@@ -0,0 +1,529 @@
<template>
<el-row class="page-title-row mb10">
<div class="page-title">{{ t('progressReport.form.title') }}</div>
<div class="page-report-time">{{ t('progressReport.form.reportTime') }}: {{formData.createTime || reportTime }}</div>
<div class="page-unit">{{ t('progressReport.form.unit') }}</div>
</el-row>
<div class="project-progress-report-form">
<div class="form-section-title">{{ t('progressReport.form.sectionTitle') }}</div>
<el-form :model="formData" label-width="200px" class="progress-form" :rules="rules" ref="useForm">
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.projectName')" required prop="projectName">
<el-select v-model="formData.projectId"
:placeholder="t('progressReport.form.selectPlaceholder')"
@change="handelSelectChange"
>
<el-option
v-for="item in projectNameData"
:key="item.id"
:label="item.projectName || ''"
:value="item.id"
/>
<template #footer>
<el-pagination
background
layout="sizes, prev, pager, next, jumper, total"
:total="total"
:page-sizes="[10, 20, 50, 100]"
v-model:page-size="queryForm.size"
v-model:current-page="queryForm.page"
@size-change="handlePageSizeChange"
@current-change="handlePageChange"
/>
</template>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('progressReport.form.projectStatus')" required prop="projectStatus">
<el-select v-model="formData.projectStatus"
:placeholder="t('progressReport.form.inputPlaceholder')"
clearable>
<el-option
v-for="item in projectStatusOptions" :key="item.value"
:value="item.value"
:label="item.label"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('progressReport.form.constructionStage')">
<el-select v-model="formData.constructionStage"
:placeholder="t('progressReport.form.selectPlaceholder')" clearable>
<el-option v-for="item in constructionStageOptions" :key="item.value"
:value="item.value"
:label="item.label"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('progressReport.form.projectImplementationUnit')">
<el-input v-model="formData.implementingBody"
:placeholder="t('progressReport.form.inputPlaceholder')" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('progressReport.form.totalInvestmentAmount')" required prop="projectTotalAmount">
<el-input v-model="formData.projectTotalAmount"
:placeholder="t('progressReport.form.inputPlaceholder')">
<template #append>{{ t('progressReport.form.unitSuffix') }}</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('progressReport.form.cumulativeInvestmentToMonthEnd')" required prop="cumulativeInvestmentToDate">
<el-input v-model="formData.cumulativeInvestmentToDate"
:placeholder="t('progressReport.form.inputPlaceholder')">
<template #append>{{ t('progressReport.form.unitSuffix') }}</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('progressReport.form.completionRate')" required prop="completionRate">
<el-input v-model="formData.completionRate"
:placeholder="t('progressReport.form.completionRatePlaceholder')">
<template #append>%</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('progressReport.form.cumulativePaymentToMonthEnd')">
<el-input v-model="formData.cumulativePaymentToDate"
:placeholder="t('progressReport.form.inputPlaceholder')">
<template #append>{{ t('progressReport.form.unitSuffix') }}</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('progressReport.form.paymentCompletionRate')">
<el-input v-model="formData.paymentCompletionRate"
:placeholder="t('progressReport.form.inputPlaceholder')">
<template #append>%</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="plan-investment-block">
<el-col :span="12">
<div class="plan-investment-panel left-panel">
<div class="panel-title">{{ t('progressReport.form.currentYearImageAmount') }}</div>
<div style="flex: 1;">
<el-form-item style="height: 82px;" :label="t('progressReport.form.reportYear')">
<el-date-picker v-model="formData.annualReport" type="year" value-format="YYYY"
:placeholder="t('progressReport.form.selectYearPlaceholder')" style="width: 100%" />
</el-form-item>
<el-form-item style="height: 82px;"
:label="t('progressReport.form.currentYearPlannedInvestment')" required prop="annualPlannedInvestment">
<el-input v-model="formData.annualPlannedInvestment"
:placeholder="t('progressReport.form.inputPlaceholder')">
<template #append>{{ t('progressReport.form.unitSuffix') }}</template>
</el-input>
</el-form-item>
<el-form-item style="height: 82px;"
:label="t('progressReport.form.enterpriseCumulativeInvestmentToMonthEnd')" required prop="ytdRemainingInvestment">
<el-input v-model="formData.ytdRemainingInvestment"
:placeholder="t('progressReport.form.inputPlaceholder')">
<template #append>{{ t('progressReport.form.unitSuffix') }}</template>
</el-input>
</el-form-item>
<el-form-item style="height: 82px;"
:label="t('progressReport.form.investmentCompletionRate')">
<el-input v-model="formData.investmentCompletionRate"
:placeholder="t('progressReport.form.inputPlaceholder')">
<template #append>%</template>
</el-input>
</el-form-item>
</div>
</div>
</el-col>
<el-col :span="12">
<div class="plan-investment-panel right-panel">
<div class="panel-title">{{ t('progressReport.form.currentYearPlannedAmount') }}</div>
<div style="flex: 1;">
<el-form-item style="height: 82px;" :label="t('progressReport.form.plannedPaymentAmount')">
<el-input v-model="formData.plannedPayment"
:placeholder="t('progressReport.form.inputPlaceholder')">
<template #append>{{ t('progressReport.form.unitSuffix') }}</template>
</el-input>
</el-form-item>
<el-form-item style="height: 82px;"
:label="t('progressReport.form.completedPaymentAmount')">
<el-input v-model="formData.actualPayment"
:placeholder="t('progressReport.form.inputPlaceholder')">
<template #append>{{ t('progressReport.form.unitSuffix') }}</template>
</el-input>
</el-form-item>
<el-form-item style="height: 82px;"
:label="t('progressReport.form.plannedAmountPaymentCompletionRate')">
<el-input v-model="formData.investmentPlanCompletionRate"
:placeholder="t('progressReport.form.inputPlaceholder')">
<template #append>%</template>
</el-input>
</el-form-item>
</div>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.effectiveness')">
<el-input v-model="formData.achievements"
:placeholder="t('progressReport.form.inputPlaceholder')" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.coordinationMatters')">
<el-input v-model="formData.coordinationIssues"
:placeholder="t('progressReport.form.inputPlaceholder')" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.nextWorkArrangements')">
<el-input v-model="formData.nextWorkPlan"
:placeholder="t('progressReport.form.inputPlaceholder')" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.currentStageEvidenceMaterials')">
<el-input v-model="formData.supportingDocuments"
:placeholder="t('progressReport.form.selectAttachmentPlaceholder')" readonly>
<template #append>
<UploadFile :modelValue="formData.supportingDocuments" @change="uploadChange" :fileSize="20"
type="simple" :limit="10" :isShowTip="false" />
</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.remark')">
<el-input v-model="formData.remarks" :placeholder="t('progressReport.form.inputPlaceholder')" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.isLastDeclaration')">
<el-select v-model="formData.isFinalApplication"
:placeholder="t('progressReport.form.selectPlaceholder')" clearable>
<el-option :label="t('progressReport.form.yes')" value="1" />
<el-option :label="t('progressReport.form.no')" value="0" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { reactive, computed, watch, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { InvestmentProjectProgress } from '/@/views/invMid/progressReport/interface/type';
import { investmentProjectsPlanList } from '/@/views/invMid/projectLibrary/interface/types';
import { investmentProjectsPlanPage } from '/@/api/investment/investmentManagement';
import { ProjectPlanApplyFormItem } from '/@/components/investment/interface/types';
import { constructionStageOptions, projectStatusOptions } from '/@/hooks/enums';
import UploadFile from "/@/components/Upload/index.vue";
import { type FormInstance, FormRules } from 'element-plus';
const { t } = useI18n();
export interface ProjectProgressReportFormData {
projectName: string;
projectStatus: string;
constructionStage: string;
projectImplementationUnit: string;
totalInvestmentAmount: string;
cumulativeInvestmentToMonthEnd: string;
completionRate: string;
cumulativePaymentToMonthEnd: string;
paymentCompletionRate: string;
// 本年投资形象额
reportYear: string;
currentYearPlannedInvestment: string;
enterpriseCumulativeInvestmentToMonthEnd: string;
investmentCompletionRate: string;
// 本年投资计划额
plannedPaymentAmount: string;
completedPaymentAmount: string;
plannedAmountPaymentCompletionRate: string;
// 其他字段
effectiveness: string;
coordinationMatters: string;
nextWorkArrangements: string;
currentStageEvidenceMaterials: any[];
remark: string;
isLastDeclaration: string;
}
const props = defineProps<{
modelValue?: InvestmentProjectProgress;
rules?:FormRules;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: InvestmentProjectProgress): void;
}>();
const useForm = ref<FormInstance | undefined>()
const defaultForm = reactive<InvestmentProjectProgress>({
id: undefined,
projectName: '',
projectId: undefined,
projectStatus: '',
constructionStage: '',
implementingBody: '',
projectTotalAmount: undefined,
cumulativeInvestmentToDate: undefined,
completionRate: undefined,
cumulativePaymentToDate: undefined,
paymentCompletionRate: undefined,
annualReport: undefined,
annualPlannedInvestment: undefined,
ytdRemainingInvestment: undefined,
investmentCompletionRate: undefined,
plannedPayment: undefined,
actualPayment: undefined,
investmentPlanCompletionRate: undefined,
achievements: '',
coordinationIssues: '',
nextWorkPlan: '',
supportingDocuments: '',
remarks: '',
isFinalApplication: undefined,
createBy: '',
createTime: '',
updateBy: '',
updateTime: '',
delFlag: '',
processInstanceId: '',
status: '',
})
const formData = reactive<InvestmentProjectProgress>({ ...defaultForm, ...props.modelValue });
const projectNameData = ref<ProjectPlanApplyFormItem[]>([]);
// 获取当前日期作为汇报时间
const reportTime = computed(() => {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
});
const total = ref(0)
const queryForm = reactive<investmentProjectsPlanList>({
page: 1,
size: 10,
});
const getProjectNameList = async () => {
try {
const res = await investmentProjectsPlanPage(queryForm);
projectNameData.value = res.data?.records || [];
total.value = res.data?.total || 0;
} catch (error) {
console.error(error);
projectNameData.value = []; // 出错时设置为空数组
}
}
getProjectNameList();
/**
* 项目选择改变
* @param {string | number} value - 选中的项目ID
*/
const handelSelectChange = (value: string | number) => {
// 根据选中的项目ID查找对应的项目信息
const selectedProject = projectNameData.value.find(item =>
item.id == value
);
// 如果找到了对应的项目,则更新实施单位字段
if (selectedProject) {
formData.implementingBody = selectedProject.projectMainEntity || selectedProject.projectOwnerUnit || '';
formData.projectName = selectedProject.projectName || '';
formData.deptId = selectedProject.deptId;
}
}
/**
* 页码大小改变
* @param {number} pageSize - 新的页码大小
* */
const handlePageSizeChange = (pageSize: number) => {
queryForm.size = pageSize;
getProjectNameList();
}
/**
* 页码改变
* @param {number} page - 新的页码
* */
const handlePageChange = (page: number) => {
queryForm.page = page;
getProjectNameList();
}
// 监听 formData 变化,同步到父组件
watch(
formData,
(newVal) => {
console.log(newVal);
emit('update:modelValue', { ...newVal });
},
{ deep: true }
);
// 监听 props.modelValue 变化,同步到 formData
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
if (typeof newVal.annualReport === 'number') newVal.annualReport = newVal.annualReport.toString()
Object.assign(formData, defaultForm, newVal);
}
},
{ immediate: true, deep: true }
);
const uploadChange = (_:any,data:any[]) =>{
if (!data || Object.prototype.toString.call(data) !== '[object Array]' || data.length === 0){
formData.supportingDocuments = [];
return;
}
formData.supportingDocuments = data.map((item:any) => {
return {
name: item.name,
url: item.url
}
})
}
defineExpose({
validate: async () => {
const isValid = await useForm.value?.validate();
if (!isValid) return false
return formData
},
})
</script>
<style scoped>
.page-title-row {
display: flex;
justify-content: center;
align-items: center;
position: relative;
margin-bottom: 20px;
}
.page-title {
font-size: 20px;
font-weight: 600;
text-align: center;
}
.page-report-time {
position: absolute;
left: 0;
font-size: 14px;
color: var(--el-text-color-secondary);
}
.page-unit {
position: absolute;
right: 0;
font-size: 14px;
color: var(--el-text-color-secondary);
}
.project-progress-report-form {
background: #fff;
border-radius: 4px;
padding: 20px;
border: 1px solid var(--el-border-color-light);
}
.form-section-title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
margin: 0 0 20px;
padding-left: 10px;
border-left: 4px solid var(--el-color-primary);
}
.progress-form {
width: 100%;
}
/* 表单 label 左对齐 */
.progress-form :deep(.el-form-item__label) {
text-align: left;
justify-content: flex-start;
}
.form-row {
margin-bottom: 16px;
margin-top: 16px;
}
.form-row:last-of-type {
margin-bottom: 0;
}
.cursor-pointer {
cursor: pointer;
}
.plan-investment-block {
margin-top: 24px;
}
.plan-investment-panel {
display: flex;
flex-direction: row;
align-items: center;
background: #f0f7ff;
padding: 16px;
border-radius: 4px;
min-height: 414px;
}
.panel-title {
width: 200px;
padding-top: 0;
margin-top: 0;
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
white-space: nowrap;
}
.plan-investment-panel :deep(.el-form-item__label) {
padding-top: 0;
line-height: 32px;
}
.plan-investment-panel :deep(.el-form-item) {
align-items: center;
}
</style>

View File

@@ -0,0 +1,415 @@
<template>
<el-row class="page-title-row mb10">
<div class="page-title">{{ title ? title : t('progressReport.form.title') }}</div>
<div class="page-report-time">{{ t('progressReport.form.reportTime') }}: {{formData.createTime || reportTime }}</div>
<div class="page-unit">{{ t('progressReport.form.unit') }}</div>
</el-row>
<div class="project-progress-report-form">
<div class="form-section-title">{{ t('progressReport.form.sectionTitle') }}</div>
<el-form :model="formData" label-width="200px" class="progress-form" :rules="rules" ref="useForm">
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.projectName')" required prop="projectName">
<span>{{formData.projectName }}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('progressReport.form.projectStatus')" required prop="projectStatus">
<span v-for="item in projectStatusOptions" :key="item.value">{{formData.projectStatus == item.value ? item.label : ''}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('progressReport.form.constructionStage')">
<span v-for="item in constructionStageOptions" :key="item.value">{{formData.constructionStage == item.value ? item.label : ''}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('progressReport.form.projectImplementationUnit')">
<span>{{formData.implementingBody}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('progressReport.form.totalInvestmentAmount')" required prop="projectTotalAmount">
<span>{{formData.projectTotalAmount }}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('progressReport.form.cumulativeInvestmentToMonthEnd')" required prop="cumulativeInvestmentToDate">
<span>{{formData.cumulativeInvestmentToDate}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('progressReport.form.completionRate')" required prop="completionRate">
<span>{{formData.completionRate}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="12">
<el-form-item :label="t('progressReport.form.cumulativePaymentToMonthEnd')">
<span>{{formData.cumulativePaymentToDate}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('progressReport.form.paymentCompletionRate')">
<span>{{formData.paymentCompletionRate}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="plan-investment-block">
<el-col :span="12">
<div class="plan-investment-panel left-panel">
<div class="panel-title">{{ t('progressReport.form.currentYearImageAmount') }}</div>
<div style="flex: 1;">
<el-form-item style="height: 82px;" :label="t('progressReport.form.reportYear')">
<span>{{formData.annualReport}}</span>
</el-form-item>
<el-form-item style="height: 82px;"
:label="t('progressReport.form.currentYearPlannedInvestment')" required prop="annualPlannedInvestment">
<span>{{formData.investmentTotalAmount ?? 0 + ' ' + t('progressReport.form.unitSuffix')}}</span>
</el-form-item>
<el-form-item style="height: 82px;"
:label="t('progressReport.form.enterpriseCumulativeInvestmentToMonthEnd')" required prop="ytdRemainingInvestment">
<span>{{ formData.ytdRemainingInvestment + ' ' + t('progressReport.form.unitSuffix')}}</span>
</el-form-item>
<el-form-item style="height: 82px;"
:label="t('progressReport.form.investmentCompletionRate')">
<span>{{formData.investmentCompletionRate}}%</span>
</el-form-item>
</div>
</div>
</el-col>
<el-col :span="12">
<div class="plan-investment-panel right-panel">
<div class="panel-title">{{ t('progressReport.form.currentYearPlannedAmount') }}</div>
<div style="flex: 1;">
<el-form-item style="height: 82px;" :label="t('progressReport.form.plannedPaymentAmount')">
<span>{{formData.plannedPayment + ' ' + t('progressReport.form.unitSuffix')}}</span>
</el-form-item>
<el-form-item style="height: 82px;"
:label="t('progressReport.form.completedPaymentAmount')">
<span>{{formData.actualPayment + ' ' + t('progressReport.form.unitSuffix')}}</span>
</el-form-item>
<el-form-item style="height: 82px;"
:label="t('progressReport.form.plannedAmountPaymentCompletionRate')">
<span>{{formData.investmentPlanCompletionRate}} %</span>
</el-form-item>
</div>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.effectiveness')">
<span>{{formData.achievements}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.coordinationMatters')">
<span>{{formData.coordinationIssues}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.nextWorkArrangements')">
<span>{{formData.nextWorkPlan}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.currentStageEvidenceMaterials')">
<uploadFile :modelValue="JSON.parse(formData.supportingDocuments || '[]') || []" @change="uploadChange" :fileSize="20" type="simple" :limit="10"
:isShowTip="false" disabled></uploadFile>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.remark')">
<span>{{formData.remarks}}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" class="form-row">
<el-col :span="24">
<el-form-item :label="t('progressReport.form.isLastDeclaration')">
<span>{{formData.isFinalApplication == '1' ? t('progressReport.form.yes') : t('progressReport.form.no')}}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { reactive, computed, watch, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { InvestmentProjectProgress } from '/@/views/invMid/progressReport/interface/type';
import { investmentProjectsPlanList } from '/@/views/invMid/projectLibrary/interface/types';
import { investmentProjectsPlanPage } from '/@/api/investment/investmentManagement';
import { ProjectPlanApplyFormItem } from '/@/components/investment/interface/types';
import { constructionStageOptions, projectStatusOptions } from '/@/hooks/enums';
import { type FormInstance, FormRules } from 'element-plus';
import uploadFile from '/@/components/Upload/index.vue';
const { t } = useI18n();
export interface ProjectProgressReportFormData {
projectName: string;
projectStatus: string;
constructionStage: string;
projectImplementationUnit: string;
totalInvestmentAmount: string;
cumulativeInvestmentToMonthEnd: string;
completionRate: string;
cumulativePaymentToMonthEnd: string;
paymentCompletionRate: string;
// 本年投资形象额
reportYear: string;
currentYearPlannedInvestment: string;
enterpriseCumulativeInvestmentToMonthEnd: string;
investmentCompletionRate: string;
// 本年投资计划额
plannedPaymentAmount: string;
completedPaymentAmount: string;
plannedAmountPaymentCompletionRate: string;
// 其他字段
effectiveness: string;
coordinationMatters: string;
nextWorkArrangements: string;
currentStageEvidenceMaterials: any[];
remark: string;
isLastDeclaration: string;
}
const props = defineProps<{
modelValue?: InvestmentProjectProgress;
rules?:FormRules;
title?: string;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: InvestmentProjectProgress): void;
}>();
const useForm = ref<FormInstance | undefined>()
const defaultForm = reactive<InvestmentProjectProgress>({
id: undefined,
projectName: '',
projectId: undefined,
projectStatus: '',
constructionStage: '',
implementingBody: '',
projectTotalAmount: undefined,
cumulativeInvestmentToDate: undefined,
completionRate: undefined,
cumulativePaymentToDate: undefined,
paymentCompletionRate: undefined,
annualReport: undefined,
annualPlannedInvestment: undefined,
ytdRemainingInvestment: undefined,
investmentCompletionRate: undefined,
plannedPayment: undefined,
actualPayment: undefined,
investmentPlanCompletionRate: undefined,
achievements: '',
coordinationIssues: '',
nextWorkPlan: '',
supportingDocuments: '',
remarks: '',
isFinalApplication: undefined,
createBy: '',
createTime: '',
updateBy: '',
updateTime: '',
delFlag: '',
processInstanceId: '',
status: '',
})
const formData = reactive<InvestmentProjectProgress>({ ...defaultForm, ...props.modelValue });
const projectNameData = ref<ProjectPlanApplyFormItem[]>([]);
// 获取当前日期作为汇报时间
const reportTime = computed(() => {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
});
const total = ref(0)
const queryForm = reactive<investmentProjectsPlanList>({
page: 1,
size: 10,
});
const getProjectNameList = async () => {
try {
const res = await investmentProjectsPlanPage(queryForm);
projectNameData.value = res.data?.records || [];
total.value = res.data?.total || 0;
} catch (error) {
console.error(error);
projectNameData.value = []; // 出错时设置为空数组
}
}
getProjectNameList();
/**
* 初始化图片
* */
const uploadChange = (_:any,data:any[]) =>{
if (!data || Object.prototype.toString.call(data) !== '[object Array]' || data.length === 0){
formData.supportingDocuments = [];
return;
}
formData.supportingDocuments = data.map((item:any) => {
return {
name: item.name,
url: item.url
}
})
}
// 监听 formData 变化,同步到父组件
watch(
formData,
(newVal) => {
emit('update:modelValue', { ...newVal });
},
{ deep: true }
);
// 监听 props.modelValue 变化,同步到 formData
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
if (typeof newVal.annualReport === 'number') newVal.annualReport = newVal.annualReport.toString()
Object.assign(formData, defaultForm, newVal);
}
},
{ immediate: true, deep: true }
);
defineExpose({
validate: async () => {
const isValid = await useForm.value?.validate();
if (!isValid) return false
return formData
},
})
</script>
<style scoped>
.page-title-row {
display: flex;
justify-content: center;
align-items: center;
position: relative;
margin-bottom: 20px;
}
.page-title {
font-size: 20px;
font-weight: 600;
text-align: center;
}
.page-report-time {
position: absolute;
left: 0;
font-size: 14px;
color: var(--el-text-color-secondary);
}
.page-unit {
position: absolute;
right: 0;
font-size: 14px;
color: var(--el-text-color-secondary);
}
.project-progress-report-form {
background: #fff;
border-radius: 4px;
padding: 20px;
border: 1px solid var(--el-border-color-light);
}
.form-section-title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
margin: 0 0 20px;
padding-left: 10px;
border-left: 4px solid var(--el-color-primary);
}
.progress-form {
width: 100%;
}
/* 表单 label 左对齐 */
.progress-form :deep(.el-form-item__label) {
text-align: left;
justify-content: flex-start;
}
.form-row {
margin-bottom: 16px;
margin-top: 16px;
}
.form-row:last-of-type {
margin-bottom: 0;
}
.cursor-pointer {
cursor: pointer;
}
.plan-investment-block {
margin-top: 24px;
}
.plan-investment-panel {
display: flex;
flex-direction: row;
align-items: center;
background: #f0f7ff;
padding: 16px;
border-radius: 4px;
min-height: 414px;
}
.panel-title {
margin-right: 20px;
padding-top: 0;
margin-top: 0;
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
white-space: nowrap;
}
.plan-investment-panel :deep(.el-form-item__label) {
padding-top: 0;
line-height: 32px;
}
.plan-investment-panel :deep(.el-form-item) {
align-items: center;
}
</style>

View File

@@ -0,0 +1,104 @@
export interface InvestmentProjects {
id?: number;
plannedInvestmentYear: string;
plannedImageAmount: string;
plannedPaymentAmount: string;
selfFunding: string;
externalFunding: string;
fiscalFunding: string;
otherFunding: string;
fiscalFundingSource: string;
otherFundingSource: string;
}
export interface EvaluationRecordEntitiesT {
id: number;
projectId: number;
actualTotalIncome: number;
actualInvestmentGain: number;
actualInvestmentReturnRate: number;
attachmentUrl: string;
investmentResults: string;
profitLossSituation: string;
existingProblems: string;
improvementSuggestions: string;
conclusion: string;
createBy: string;
createTime: string
}
export interface ProjectPlanApplyFormItem{
id?: any;
projectName: string;
projectNature: string;
groupCompany: string;
projectOwnerUnit: string;
projectMainEntity: string;
projectDepartment?: string;
investmentCategory: string;
investmentArea: string;
projectAddress: string;
projectAddressDetail: string;
projectInvestmentDirection: string;
totalInvestment?: string;
investmentDirectionSegmentation: string;
projectBackground?: string;
constructionNature: string;
constructionStage?: string;
keyProject: string;
isMainBusiness: string;
mainBusinessTypes: string;
mainBusinessCode: string;
isManufacturingIndustry: string;
isStuckIndustry: string;
urbanStrategy: string;
projectSource: string;
majorInvestmentProjects: string;
isStrategicEmergingIndustries: string;
projectStartTime: string;
projectEndTime: string;
projectConstructionContent: string;
planInvestmentYear: string;
annualPlanTotal: string;
annualPlanInvestment: string;
planPaymentLimit: string;
plannedImageAmount: string;
ownedFunds: string;
financialFunds: string;
externalFunding: string;
externalRaisedCapital: string;
otherFunds: string;
governmentFundSourceDesc: string;
otherFundSourceDesc: string;
projectTotalAmount: string;
lastYearCompleted: string;
ourInvestmentTotalAmount: string;
ourLastYearCompleted: string;
projectDesc: string;
promotionPlan: string;
projectPreliminaryPlan: string;
projectPreliminaryPlanAttachment:any[] | string;
attachments: any[] | string;
remark: string;
decisionType: string;
isProjectApprovalCompleted: string;
isDecisionProcedureCompleted: string;
projectApprovalFileNo: string;
projectApprovalFileInfo: string;
decisionProcedureFileNo: string;
decisionFileInfo: string;
submitUnitOpinion: string;
groupInvestmentDeptOpinion: string;
submitUnitLeadershipOpinion: string;
submitUnitMainLeadershipOpinion: string;
planImageQuota:string;
projectInvestmentEntities?:InvestmentProjects[];
evaluationRecordEntities?:EvaluationRecordEntitiesT[];
delFlag?: number;
projectId?: number;
processInstanceId:string;
status:number;
deptId:number
}
export interface ProjectPlanApplyFormData {
entity:ProjectPlanApplyFormItem;
investmentProjects: InvestmentProjects[];
}

View File

@@ -0,0 +1,116 @@
<template>
<el-drawer class="mixed-detail-drawer" :model-value="modelValue" @close="handleClose" direction="rtl" size="55%"
:close-on-click-modal="false" :destroy-on-close="true" :with-header="false" v-bind="$attrs">
<div class="drawer-header">
<div class="title">{{ title }}</div>
<el-button link icon="Close" @click="handleClose" />
</div>
<div class="drawer-body" v-loading="loading">
<template v-if="detail">
<el-descriptions :column="2" border>
<el-descriptions-item v-for="item in items" :key="item.key" :label="item.label">
<span v-if="item.key === 'mode'">
{{ mixedReformMethodOptions.find(item => item.value === detail?.mode)?.label || formatValue(detail[item.key]) }}
</span>
<span v-else-if="item.key === 'partnerNature'">
{{ cooperationPartnerNatureOptions.find(item => item.value === detail?.partnerNature)?.label || formatValue(detail[item.key]) }}
</span>
<span v-else-if="item.key === 'type'">
{{ mixedReformTypeOptions.find(item => item.value === detail?.type)?.label || formatValue(detail[item.key]) }}
</span>
<span v-else>{{ formatValue(detail[item.key]) }}</span>
</el-descriptions-item>
</el-descriptions>
</template>
<el-empty v-else :image-size="120" :description="emptyText" />
</div>
</el-drawer>
</template>
<script setup lang="ts">
import { toRef } from 'vue';
import {
cooperationPartnerNatureOptions,
industryChainCategoryOptions,
mixedCompanyCategoryOptions,
mixedCompanyFieldOptions,
mixedCompanyIndustryOptions,
mixedReformMethodOptions,
mixedReformTypeOptions
} from '/@/hooks/enums'
defineOptions({
inheritAttrs: false,
});
type DetailItem = {
key: string;
label: string;
};
const props = withDefaults(
defineProps<{
modelValue: boolean;
title?: string;
detail?: Record<string, any> | null;
items: DetailItem[];
loading?: boolean;
emptyText?: string;
}>(),
{
title: '详情',
loading: false,
emptyText: '暂无数据',
}
);
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void;
}>();
const handleClose = () => {
emit('update:modelValue', false);
};
const formatValue = (value: unknown) => {
if (value === null || value === undefined || value === '') return '--';
return value;
};
const modelValue = toRef(props, 'modelValue');
const title = toRef(props, 'title');
const detail = toRef(props, 'detail');
const items = toRef(props, 'items');
const loading = toRef(props, 'loading');
const emptyText = toRef(props, 'emptyText');
</script>
<style scoped>
.mixed-detail-drawer {
--el-drawer-border-radius: 0;
}
.drawer-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--el-border-color);
background-color: #fff;
}
.drawer-header .title {
font-size: 18px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.drawer-body {
padding: 16px;
height: calc(100% - 52px);
overflow: auto;
background-color: #f9fafc;
}
:deep(.el-descriptions__body) {
background-color: #fff;
}
</style>

View File

@@ -0,0 +1,153 @@
<template>
<el-card shadow="never" class="section-card">
<el-form ref="formRef" :model="basicInfoForm" label-width="180px" class="basic-info-form striped-form">
<div class="section-block">
<div class="section-header">
<span class="bar"></span>
<h3 class="section-title">{{ instructionTitle }}</h3>
</div>
<el-row :gutter="20" class="instruction-grid">
<el-col v-for="field in instructionFields" :key="field.key" :span="field.span || 8">
<el-form-item :label="field.label" label-width="80px">
<el-input maxlength="255" v-model="instructionForm[field.key]" :placeholder="t('mixedRegister.placeholder.input')" />
</el-form-item>
</el-col>
</el-row>
</div>
<div class="section-block">
<div class="section-header">
<span class="bar"></span>
<h3 class="section-title">{{ basicInfoTitle }}</h3>
</div>
<el-row :gutter="12">
<el-col v-for="field in basicInfoFields" :key="field.key" :span="field.span || 12" class="mb-4">
<el-form-item :label="field.label">
<!-- 日期时间选择器 -->
<el-date-picker
v-if="field.type && ['date', 'datetime'].includes(field.type)"
v-model="basicInfoForm[field.key]"
:type="field.type"
:placeholder="t('mixedRegister.placeholder.select')"
v-bind="field.fieldProps"
/>
<!-- 选择器 -->
<el-select
v-else-if="field.type === 'select'"
v-model="basicInfoForm[field.key]"
:placeholder="t('mixedRegister.placeholder.select')"
v-bind="field.fieldProps"
>
<el-option
v-for="item of field.fieldProps?.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<div class="partner-name-cell w-full" v-else-if="isUpdate && field.key === 'projectName'">
<el-input v-model="basicInfoForm.projectName" maxlength="255"
:placeholder="t('mixedRegister.placeholder.select')" readonly />
<el-button icon="OfficeBuilding" size="small" @click="librarySelectVisible = true" />
<LibrarySelect v-model="librarySelectVisible" @select="handleLibrarySelect" />
</div>
<!-- 输入框 -->
<template v-else>
<el-input-number v-if="field.type === 'number'" :length="field.length" v-model="basicInfoForm[field.key]"
:controls="false" :min="0"/>
<el-input v-else maxlength="255" v-model="basicInfoForm[field.key]" :placeholder="t('mixedRegister.placeholder.input')" />
</template>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
</el-card>
</template>
<script setup lang="ts">
import type { FormInstance } from 'element-plus';
import { useI18n } from 'vue-i18n';
import LibrarySelect from '/@/views/investment/mixedReformRegister/components/LibrarySelect.vue';
const { t } = useI18n();
type FieldConfig = {
key: string;
label: string;
span?: number;
type?: string;
fieldProps?: any;
};
const props = defineProps<{
instructionTitle: string;
basicInfoTitle: string;
instructionFields: FieldConfig[];
instructionForm: Record<string, any>;
basicInfoFields: FieldConfig[];
basicInfoForm: Record<string, any>;
isUpdate: boolean
}>();
const { instructionTitle, basicInfoTitle } = props;
// 处理专委会评审信息库选择事件
const librarySelectVisible = ref(false)
const handleLibrarySelect = (row: any) => {
for (const field of props.instructionFields) {
props.instructionForm[field.key] = row[field.key]
}
Object.assign(props.basicInfoForm, { ...row, id: '', parentId: row.id })
librarySelectVisible.value = false
}
const formRef = ref<FormInstance|null>(null)
const validate = async () => {
const isValid = await formRef.value?.validate()
if (isValid) {
return {
...props.instructionForm,
...props.basicInfoForm
}
}
throw new Error('表单校验不通过')
}
defineExpose({ validate })
</script>
<style scoped>
.section-block {
margin-bottom: 24px;
}
.section-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
}
.section-header .bar {
width: 4px;
height: 18px;
background-color: var(--el-color-primary);
border-radius: 2px;
}
.section-title {
font-size: 16px;
font-weight: 600;
margin: 0;
}
.partner-name-cell {
display: flex;
align-items: center;
gap: 8px;
}
.partner-name-cell :deep(.el-input) {
flex: 1;
}
</style>

View File

@@ -0,0 +1,108 @@
<template>
<el-form label-width="180px" class="p-8 basic-info-form striped-form">
<div class="section-block">
<div class="section-header">
<span class="bar"></span>
<h3 class="section-title">{{ t('mixedRegister.sections.instruction') }}</h3>
</div>
<el-row :gutter="20" class="instruction-grid">
<el-col v-for="field in instructionFields" :key="field.key" :span="field.span || 8">
<el-form-item :label="field.label" label-width="80px">
{{ instructionForm[field.key] || '--' }}
</el-form-item>
</el-col>
</el-row>
</div>
<div class="section-block">
<div class="section-header">
<span class="bar"></span>
<h3 class="section-title">{{ t('mixedRegister.sections.basic') }}</h3>
</div>
<el-row :gutter="12">
<el-col v-for="field in basicInfoFields" :key="field.key" :span="field.span || 12" class="mb-4">
<el-form-item :label="field.label">
<div v-if="field.type === 'select'">
{{ field.fieldProps?.options?.find((item: any) => item.value === basicInfoForm[field.key])?.label || basicInfoForm[field.key] || '--' }}
</div>
<div v-else>{{ basicInfoForm[field.key] || '--' }}</div>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import {
cooperationPartnerNatureOptions,
industryChainCategoryOptions,
mixedCompanyCategoryOptions,
mixedCompanyFieldOptions,
mixedCompanyIndustryOptions,
mixedReformMethodOptions,
mixedReformTypeOptions
} from '/@/hooks/enums';
const { t } = useI18n();
const props = defineProps<{
instructionForm: Record<string, any>;
basicInfoForm: Record<string, any>;
}>();
const instructionFields = computed<Array<{ key: string; label: string, [key: string]: any }>>(() => [
{ key: 'reportingUnit', label: t('mixedRegister.instructionFields.reportUnit') },
{ key: 'contactPerson', label: t('mixedRegister.instructionFields.contact') },
{ key: 'contactPhone', label: t('mixedRegister.instructionFields.contactMethod') },
]);
const basicInfoFields = computed<Array<{ key: string; label: string, [key: string]: any }>>(() => [
{ key: 'groupBelonging', label: t('mixedRegister.basicFields.groupName') },
{ key: 'projectYear', label: t('mixedRegister.basicFields.year') },
{ key: 'projectName', label: t('mixedRegister.basicFields.projectName') },
{ key: 'implementEnterprise', label: t('mixedRegister.basicFields.companyFullName') },
{ key: 'enterpriseCreditCode', label: t('mixedRegister.basicFields.creditCode') },
{ key: 'partnerNature', label: t('mixedRegister.basicFields.partnerNature'), type: 'select', fieldProps: { options: cooperationPartnerNatureOptions } },
{ key: 'partnerName', label: t('mixedRegister.basicFields.partnerName') },
{ key: 'progressStatus', label: t('mixedRegister.basicFields.progressStatus') },
{ key: 'estimatedCompletionTime', label: t('mixedRegister.basicFields.expectedFinishTime') },
{ key: 'mixedReformMethod', label: t('mixedRegister.basicFields.mixedMode'), type: 'select', fieldProps: { options: mixedReformMethodOptions } },
{ key: 'isIndustryLeader', label: t('mixedRegister.basicFields.isIndustryBenchmark') },
{ key: 'mixedReformType', label: t('mixedRegister.basicFields.mixedType'), type: 'select', fieldProps: { options: mixedReformTypeOptions } },
{ key: 'projectStartTime', label: t('mixedRegister.basicFields.projectStartTime') },
{ key: 'actualCompletionTime', label: t('mixedRegister.basicFields.completionTime') },
{ key: 'reformProgress', label: t('mixedRegister.basicFields.reformProgress'), span: 24 },
{ key: 'mixedCompanyCategory', label: t('mixedRegister.basicFields.mixedCompanyCategory'), type: 'select', fieldProps: { options: mixedCompanyCategoryOptions } },
{ key: 'mixedCompanyIndustry', label: t('mixedRegister.basicFields.mixedCompanyIndustry'), type: 'select', fieldProps: { options: mixedCompanyIndustryOptions } },
{ key: 'mixedCompanyField', label: t('mixedRegister.basicFields.mixedCompanyField'), type: 'select', fieldProps: { options: mixedCompanyFieldOptions } },
{ key: 'industryChainCategory', label: t('mixedRegister.basicFields.industryChainCategory'), type: 'select', fieldProps: { options: industryChainCategoryOptions } }
]);
</script>
<style scoped>
.section-block {
margin-bottom: 24px;
}
.section-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
}
.section-header .bar {
width: 4px;
height: 18px;
background-color: var(--el-color-primary);
border-radius: 2px;
}
.section-title {
font-size: 16px;
font-weight: 600;
margin: 0;
}
</style>