|
|
@@ -0,0 +1,696 @@
|
|
|
+<template>
|
|
|
+ <div class="review-page">
|
|
|
+ <div class="review-filter" style="border: none">
|
|
|
+ <el-form :model="queryParams" inline>
|
|
|
+ <el-form-item label="项目名称">
|
|
|
+ <el-input
|
|
|
+ v-model="queryParams.projectName"
|
|
|
+ clearable
|
|
|
+ placeholder="请输入项目名称"
|
|
|
+ class="!w-240px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="项目类型">
|
|
|
+ <el-select
|
|
|
+ v-model="queryParams.projectType"
|
|
|
+ clearable
|
|
|
+ placeholder="请选择项目类型"
|
|
|
+ class="!w-210px"
|
|
|
+ >
|
|
|
+ <el-option v-for="item in projectTypes" :key="item" :label="item" :value="item" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="提交时间">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="queryParams.submitDate"
|
|
|
+ type="date"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ clearable
|
|
|
+ placeholder="请选择日期"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="handleQuery"
|
|
|
+ ><Icon icon="ep:search" class="mr-5px" />查询</el-button
|
|
|
+ >
|
|
|
+ <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="review-layout">
|
|
|
+ <ContentWrap class="project-list-panel" style="border: none">
|
|
|
+ <div class="status-tabs">
|
|
|
+ <button
|
|
|
+ v-for="item in statusTabs"
|
|
|
+ :key="item.value"
|
|
|
+ :class="{ active: activeStatus === item.value }"
|
|
|
+ @click="activeStatus = item.value"
|
|
|
+ >
|
|
|
+ {{ item.label }} <span>{{ statusCount(item.value) }}</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="project-list">
|
|
|
+ <button
|
|
|
+ v-for="project in filteredProjects"
|
|
|
+ :key="project.id"
|
|
|
+ class="project-item"
|
|
|
+ :class="{ selected: selectedProject?.id === project.id }"
|
|
|
+ @click="selectProject(project)"
|
|
|
+ >
|
|
|
+ <div class="project-item-top">
|
|
|
+ <strong>{{ project.projectName }}</strong>
|
|
|
+ <el-tag :type="statusType(project.status)" size="small">{{ project.status }}</el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="project-meta">{{ project.projectType }} · {{ project.projectLeader }}</div>
|
|
|
+ <div class="project-item-bottom"
|
|
|
+ ><span>提交于 {{ project.submitDate }}</span
|
|
|
+ ><span>{{ project.attachments.length }} 个附件</span></div
|
|
|
+ >
|
|
|
+ </button>
|
|
|
+ <el-empty v-if="!filteredProjects.length" description="暂无待审项目" :image-size="72" />
|
|
|
+ </div>
|
|
|
+ </ContentWrap>
|
|
|
+
|
|
|
+ <ContentWrap v-if="selectedProject" class="review-detail-panel" style="border: none">
|
|
|
+ <!-- <div class="process-strip">
|
|
|
+ <div
|
|
|
+ v-for="(step, index) in selectedProject.process"
|
|
|
+ :key="step.name"
|
|
|
+ class="process-step"
|
|
|
+ :class="step.state"
|
|
|
+ >
|
|
|
+ <div class="step-icon"
|
|
|
+ ><Icon
|
|
|
+ :icon="
|
|
|
+ step.state === 'done'
|
|
|
+ ? 'ep:check'
|
|
|
+ : step.state === 'current'
|
|
|
+ ? 'ep:clock'
|
|
|
+ : 'ep:more'
|
|
|
+ "
|
|
|
+ /></div>
|
|
|
+ <div
|
|
|
+ ><strong>{{ step.name }}</strong
|
|
|
+ ><span
|
|
|
+ >{{ step.handler }}<template v-if="step.date"> · {{ step.date }}</template></span
|
|
|
+ ></div
|
|
|
+ >
|
|
|
+ <i v-if="index < selectedProject.process.length - 1"></i>
|
|
|
+ </div>
|
|
|
+ </div> -->
|
|
|
+
|
|
|
+ <el-tabs v-model="activeTab" class="detail-tabs">
|
|
|
+ <el-tab-pane label="项目基本信息" name="info">
|
|
|
+ <el-descriptions :column="2" border class="info-table">
|
|
|
+ <el-descriptions-item label="项目名称" :span="2">{{
|
|
|
+ selectedProject.projectName
|
|
|
+ }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="项目级别">{{
|
|
|
+ selectedProject.projectLevel
|
|
|
+ }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="项目类型">{{
|
|
|
+ selectedProject.projectType
|
|
|
+ }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="执行单位">{{
|
|
|
+ selectedProject.executingUnit
|
|
|
+ }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="项目负责人">{{
|
|
|
+ selectedProject.projectLeader
|
|
|
+ }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="项目周期">{{
|
|
|
+ selectedProject.period
|
|
|
+ }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="申报部门">{{
|
|
|
+ selectedProject.department
|
|
|
+ }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="项目目标" :span="2">{{
|
|
|
+ selectedProject.purpose
|
|
|
+ }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="主要研究内容" :span="2">{{
|
|
|
+ selectedProject.content
|
|
|
+ }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane label="预算摘要" name="budget">
|
|
|
+ <div class="budget-summary">
|
|
|
+ <div
|
|
|
+ ><span>预算总额</span
|
|
|
+ ><strong>¥ {{ formatAmount(selectedProject.budgetTotal) }}</strong></div
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ ><span>预算科目</span><strong>{{ selectedProject.budgets.length }} 项</strong></div
|
|
|
+ >
|
|
|
+ <div><span>预算完整性</span><strong class="success-text">已填写</strong></div>
|
|
|
+ </div>
|
|
|
+ <el-table :data="selectedProject.budgets" border>
|
|
|
+ <el-table-column type="index" label="#" width="56" align="center" />
|
|
|
+ <el-table-column prop="subject" label="财务科目" min-width="220" />
|
|
|
+ <el-table-column prop="amount" label="预算金额(元)" width="180" align="right"
|
|
|
+ ><template #default="{ row }">{{
|
|
|
+ formatAmount(row.amount)
|
|
|
+ }}</template></el-table-column
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ prop="remark"
|
|
|
+ label="预算说明"
|
|
|
+ min-width="260"
|
|
|
+ show-overflow-tooltip
|
|
|
+ />
|
|
|
+ </el-table>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane label="附件材料" name="attachments">
|
|
|
+ <div class="attachment-tip"
|
|
|
+ ><Icon icon="ep:info-filled" />
|
|
|
+ 支持在线查看文件信息,点击“预览”将在新窗口打开文件。</div
|
|
|
+ >
|
|
|
+ <div class="attachment-list">
|
|
|
+ <div
|
|
|
+ v-for="file in selectedProject.attachments"
|
|
|
+ :key="file.name"
|
|
|
+ class="attachment-item"
|
|
|
+ >
|
|
|
+ <div class="file-icon" :class="file.kind"><Icon :icon="fileIcon(file.kind)" /></div>
|
|
|
+ <div class="file-info"
|
|
|
+ ><strong>{{ file.name }}</strong
|
|
|
+ ><span>{{ file.size }} · {{ file.uploadedBy }} · {{ file.uploadedAt }}</span></div
|
|
|
+ >
|
|
|
+ <el-button link type="primary" @click="previewFile(file)">预览</el-button>
|
|
|
+ <el-button link type="primary" @click="downloadFile(file)">下载</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+
|
|
|
+ <div class="review-action">
|
|
|
+ <div
|
|
|
+ ><span>预审意见</span><small>请核对项目目标、研究内容、预算及附件的完整性</small></div
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="reviewComment"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ maxlength="500"
|
|
|
+ show-word-limit
|
|
|
+ placeholder="请输入审批意见,退回时必须填写"
|
|
|
+ />
|
|
|
+ <div class="action-buttons">
|
|
|
+ <el-button type="danger" plain @click="submitReview('退回修改')"
|
|
|
+ ><Icon icon="ep:back" class="mr-5px" />退回修改</el-button
|
|
|
+ >
|
|
|
+ <el-button type="primary" @click="submitReview('预审通过')"
|
|
|
+ ><Icon icon="ep:circle-check" class="mr-5px" />预审通过</el-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </ContentWrap>
|
|
|
+ <ContentWrap v-else class="review-detail-panel empty-panel"
|
|
|
+ ><el-empty description="请选择需要预审的项目"
|
|
|
+ /></ContentWrap>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, reactive, ref } from 'vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+
|
|
|
+defineOptions({ name: 'ResearchProjectReview' })
|
|
|
+type Status = '待预审' | '预审通过' | '退回修改'
|
|
|
+type Attachment = {
|
|
|
+ name: string
|
|
|
+ kind: 'pdf' | 'word' | 'excel'
|
|
|
+ size: string
|
|
|
+ uploadedBy: string
|
|
|
+ uploadedAt: string
|
|
|
+ url?: string
|
|
|
+}
|
|
|
+type ProcessStep = {
|
|
|
+ name: string
|
|
|
+ handler: string
|
|
|
+ date: string
|
|
|
+ state: 'done' | 'current' | 'pending'
|
|
|
+}
|
|
|
+type Project = {
|
|
|
+ id: number
|
|
|
+ projectNo: string
|
|
|
+ projectName: string
|
|
|
+ projectType: string
|
|
|
+ projectLevel: string
|
|
|
+ projectLeader: string
|
|
|
+ executingUnit: string
|
|
|
+ department: string
|
|
|
+ period: string
|
|
|
+ purpose: string
|
|
|
+ content: string
|
|
|
+ submitDate: string
|
|
|
+ status: Status
|
|
|
+ budgetTotal: number
|
|
|
+ budgets: { subject: string; amount: number; remark: string }[]
|
|
|
+ attachments: Attachment[]
|
|
|
+ process: ProcessStep[]
|
|
|
+}
|
|
|
+const projectTypes = [
|
|
|
+ '基础研究类',
|
|
|
+ '原理方法类',
|
|
|
+ '产品、工具和设备研发类',
|
|
|
+ '成熟工艺技术配套及推广类',
|
|
|
+ '情报调研类'
|
|
|
+]
|
|
|
+const statusTabs = [
|
|
|
+ { label: '全部', value: 'all' },
|
|
|
+ { label: '待预审', value: '待预审' },
|
|
|
+ { label: '已通过', value: '预审通过' },
|
|
|
+ { label: '已退回', value: '退回修改' }
|
|
|
+]
|
|
|
+const createProject = (data: Partial<Project> = {}): Project => ({
|
|
|
+ id: 1,
|
|
|
+ projectNo: 'XM202603001',
|
|
|
+ projectName: '高温高压井下工具可靠性研究',
|
|
|
+ projectType: '产品、工具和设备研发类',
|
|
|
+ projectLevel: '专业公司级/石油技术公司级',
|
|
|
+ projectLeader: '张明远',
|
|
|
+ executingUnit: '瑞恒兴仪 / 四川瑞都',
|
|
|
+ department: '科技发展部',
|
|
|
+ period: '2026-03 至 2027-03',
|
|
|
+ purpose: '提升复杂工况下工具运行可靠性,形成可推广的井下工具评价方法。',
|
|
|
+ content: '开展材料、结构及工艺适应性研究,完成样机试制、环境测试和现场验证。',
|
|
|
+ submitDate: '2026-09-16',
|
|
|
+ status: '待预审',
|
|
|
+ budgetTotal: 275000,
|
|
|
+ budgets: [
|
|
|
+ { subject: '直接材料投入', amount: 180000, remark: '样机材料及加工费用' },
|
|
|
+ { subject: '检验费', amount: 65000, remark: '高温高压环境测试' },
|
|
|
+ { subject: '差旅费', amount: 30000, remark: '现场调研与试验支持' }
|
|
|
+ ],
|
|
|
+ attachments: [
|
|
|
+ {
|
|
|
+ name: '可行性研究报告.pdf',
|
|
|
+ kind: 'pdf',
|
|
|
+ size: '2.4 MB',
|
|
|
+ uploadedBy: '张明远',
|
|
|
+ uploadedAt: '2026-09-16'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '项目实施方案.docx',
|
|
|
+ kind: 'word',
|
|
|
+ size: '856 KB',
|
|
|
+ uploadedBy: '张明远',
|
|
|
+ uploadedAt: '2026-09-16'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '项目预算明细.xlsx',
|
|
|
+ kind: 'excel',
|
|
|
+ size: '128 KB',
|
|
|
+ uploadedBy: '张明远',
|
|
|
+ uploadedAt: '2026-09-16'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ process: [
|
|
|
+ { name: '项目负责人提交', handler: '张明远', date: '2026-09-16', state: 'done' },
|
|
|
+ { name: '科技发展部预审', handler: '当前节点', date: '', state: 'current' }
|
|
|
+ ],
|
|
|
+ ...data
|
|
|
+})
|
|
|
+const projects = ref<Project[]>([
|
|
|
+ createProject(),
|
|
|
+ createProject({
|
|
|
+ id: 2,
|
|
|
+ projectNo: 'XM202603002',
|
|
|
+ projectName: '智能钻井参数优化方法研究',
|
|
|
+ projectType: '基础研究类',
|
|
|
+ projectLeader: '赵海宁',
|
|
|
+ projectLevel: '集团公司级',
|
|
|
+ budgetTotal: 420000,
|
|
|
+ status: '待预审',
|
|
|
+ submitDate: '2026-09-15',
|
|
|
+ department: '技术中心'
|
|
|
+ }),
|
|
|
+ createProject({
|
|
|
+ id: 3,
|
|
|
+ projectNo: 'XM202602018',
|
|
|
+ projectName: '油气田节能降碳技术应用研究',
|
|
|
+ projectType: '成熟工艺技术配套及推广类',
|
|
|
+ projectLeader: '陈立',
|
|
|
+ status: '预审通过',
|
|
|
+ submitDate: '2026-09-10'
|
|
|
+ })
|
|
|
+])
|
|
|
+const queryParams = reactive({ projectName: '', projectType: '', submitDate: '' })
|
|
|
+const activeStatus = ref('all')
|
|
|
+const selectedProject = ref<Project>(projects.value[0])
|
|
|
+const activeTab = ref('info')
|
|
|
+const reviewComment = ref('')
|
|
|
+const filteredProjects = computed(() =>
|
|
|
+ projects.value.filter(
|
|
|
+ (item) =>
|
|
|
+ (!queryParams.projectName || item.projectName.includes(queryParams.projectName)) &&
|
|
|
+ (!queryParams.projectType || item.projectType === queryParams.projectType) &&
|
|
|
+ (!queryParams.submitDate || item.submitDate === queryParams.submitDate) &&
|
|
|
+ (activeStatus.value === 'all' || item.status === activeStatus.value)
|
|
|
+ )
|
|
|
+)
|
|
|
+const statusCount = (status: string) =>
|
|
|
+ status === 'all'
|
|
|
+ ? projects.value.length
|
|
|
+ : projects.value.filter((item) => item.status === status).length
|
|
|
+const statusType = (status: Status) =>
|
|
|
+ status === '待预审' ? 'warning' : status === '预审通过' ? 'success' : 'danger'
|
|
|
+const selectProject = (project: Project) => {
|
|
|
+ selectedProject.value = project
|
|
|
+ reviewComment.value = ''
|
|
|
+ activeTab.value = 'info'
|
|
|
+}
|
|
|
+const resetQuery = () => {
|
|
|
+ queryParams.projectName = ''
|
|
|
+ queryParams.projectType = ''
|
|
|
+ queryParams.submitDate = ''
|
|
|
+ activeStatus.value = 'all'
|
|
|
+}
|
|
|
+const handleQuery = () => {
|
|
|
+ if (!filteredProjects.value.includes(selectedProject.value))
|
|
|
+ selectedProject.value = filteredProjects.value[0]
|
|
|
+}
|
|
|
+const formatAmount = (amount: number) =>
|
|
|
+ amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
|
+const fileIcon = (kind: Attachment['kind']) =>
|
|
|
+ kind === 'pdf' ? 'ep:document' : kind === 'word' ? 'ep:reading' : 'ep:files'
|
|
|
+const previewFile = (file: Attachment) => {
|
|
|
+ if (file.url) window.open(file.url, '_blank')
|
|
|
+ else ElMessage.info(`前端演示:${file.name}暂无在线文件地址`)
|
|
|
+}
|
|
|
+const downloadFile = (file: Attachment) => {
|
|
|
+ if (file.url) {
|
|
|
+ const link = document.createElement('a')
|
|
|
+ link.href = file.url
|
|
|
+ link.download = file.name
|
|
|
+ link.click()
|
|
|
+ } else ElMessage.info(`前端演示:${file.name}暂无下载地址`)
|
|
|
+}
|
|
|
+const submitReview = async (result: '预审通过' | '退回修改') => {
|
|
|
+ if (result === '退回修改' && !reviewComment.value.trim()) {
|
|
|
+ ElMessage.warning('退回修改时必须填写预审意见')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const confirmed = await ElMessageBox.confirm(`确定${result}该项目吗?`, '提交预审结果', {
|
|
|
+ type: result === '预审通过' ? 'success' : 'warning',
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消'
|
|
|
+ })
|
|
|
+ .then(() => true)
|
|
|
+ .catch(() => false)
|
|
|
+ if (!confirmed || !selectedProject.value) return
|
|
|
+ selectedProject.value.status = result
|
|
|
+ selectedProject.value.process = selectedProject.value.process.map((step) => ({
|
|
|
+ ...step,
|
|
|
+ state: 'done',
|
|
|
+ date: step.date || '2026-09-18'
|
|
|
+ }))
|
|
|
+ ElMessage.success(`${result}成功`)
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.review-page {
|
|
|
+ min-height: 100%;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+}
|
|
|
+.review-filter {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ background-color: #fff;
|
|
|
+ display: flex;
|
|
|
+ padding: 20px;
|
|
|
+ padding-bottom: 0px;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.review-layout {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 360px minmax(0, 1fr);
|
|
|
+ gap: 16px;
|
|
|
+ align-items: start;
|
|
|
+}
|
|
|
+.project-list-panel,
|
|
|
+.review-detail-panel {
|
|
|
+ min-height: 680px;
|
|
|
+}
|
|
|
+.panel-heading,
|
|
|
+.detail-heading,
|
|
|
+.project-item-top,
|
|
|
+.project-item-bottom,
|
|
|
+.action-buttons {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+.panel-heading h2,
|
|
|
+.detail-heading h1 {
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+.panel-heading p,
|
|
|
+.detail-heading p {
|
|
|
+ margin: 7px 0 0;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+.status-tabs {
|
|
|
+ display: flex;
|
|
|
+ gap: 4px;
|
|
|
+ margin: 0px -8px 8px;
|
|
|
+ border-bottom: 1px solid var(--el-border-color-lighter);
|
|
|
+}
|
|
|
+.status-tabs button {
|
|
|
+ padding: 10px 9px;
|
|
|
+ border: 0;
|
|
|
+ border-bottom: 2px solid transparent;
|
|
|
+ background: transparent;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+.status-tabs button.active {
|
|
|
+ border-bottom-color: var(--el-color-primary);
|
|
|
+ color: var(--el-color-primary);
|
|
|
+}
|
|
|
+.status-tabs span {
|
|
|
+ margin-left: 3px;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+.project-list {
|
|
|
+ display: grid;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+.project-item {
|
|
|
+ padding: 14px;
|
|
|
+ border: 1px solid var(--el-border-color-lighter);
|
|
|
+ border-radius: 4px;
|
|
|
+ background: var(--el-bg-color);
|
|
|
+ text-align: left;
|
|
|
+ cursor: pointer;
|
|
|
+ transition:
|
|
|
+ border-color 0.2s,
|
|
|
+ background 0.2s;
|
|
|
+}
|
|
|
+.project-item:hover,
|
|
|
+.project-item.selected {
|
|
|
+ border-color: var(--el-color-primary-light-5);
|
|
|
+ background: var(--el-color-primary-light-9);
|
|
|
+}
|
|
|
+.project-item strong {
|
|
|
+ max-width: 215px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+.project-meta,
|
|
|
+.project-item-bottom,
|
|
|
+.file-info span,
|
|
|
+.process-step span {
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+.project-meta {
|
|
|
+ margin: 10px 0;
|
|
|
+}
|
|
|
+.project-item-bottom {
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+.eyebrow {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ color: var(--el-color-primary);
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+.detail-heading h1 {
|
|
|
+ font-size: 22px;
|
|
|
+}
|
|
|
+.process-strip {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin: 10px 0 22px;
|
|
|
+ padding: 18px 20px;
|
|
|
+ background: var(--el-fill-color-light);
|
|
|
+}
|
|
|
+.process-step {
|
|
|
+ display: flex;
|
|
|
+ flex: 1;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ position: relative;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+}
|
|
|
+.process-step > div:not(.step-icon) {
|
|
|
+ display: grid;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+.process-step.done,
|
|
|
+.process-step.current {
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+}
|
|
|
+.step-icon {
|
|
|
+ display: grid;
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ place-items: center;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: var(--el-border-color);
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+.process-step.done .step-icon {
|
|
|
+ background: var(--el-color-success);
|
|
|
+}
|
|
|
+.process-step.current .step-icon {
|
|
|
+ background: var(--el-color-primary);
|
|
|
+}
|
|
|
+.process-step i {
|
|
|
+ flex: 1;
|
|
|
+ height: 1px;
|
|
|
+ margin: 0 14px;
|
|
|
+ background: var(--el-border-color);
|
|
|
+}
|
|
|
+.detail-tabs {
|
|
|
+ min-height: 330px;
|
|
|
+}
|
|
|
+.info-table {
|
|
|
+ margin-top: 8px;
|
|
|
+}
|
|
|
+.budget-summary {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ gap: 12px;
|
|
|
+ margin: 8px 0 18px;
|
|
|
+}
|
|
|
+.budget-summary div {
|
|
|
+ padding: 16px;
|
|
|
+ border: 1px solid var(--el-border-color-lighter);
|
|
|
+}
|
|
|
+.budget-summary span,
|
|
|
+.budget-summary strong {
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+.budget-summary span {
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+.budget-summary strong {
|
|
|
+ margin-top: 8px;
|
|
|
+ font-size: 20px;
|
|
|
+}
|
|
|
+.success-text {
|
|
|
+ color: var(--el-color-success);
|
|
|
+}
|
|
|
+.attachment-tip {
|
|
|
+ padding: 10px 12px;
|
|
|
+ background: var(--el-color-primary-light-9);
|
|
|
+ color: var(--el-color-primary);
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+.attachment-list {
|
|
|
+ margin-top: 14px;
|
|
|
+}
|
|
|
+.attachment-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 14px 4px;
|
|
|
+ border-bottom: 1px solid var(--el-border-color-lighter);
|
|
|
+}
|
|
|
+.file-icon {
|
|
|
+ display: grid;
|
|
|
+ width: 38px;
|
|
|
+ height: 38px;
|
|
|
+ place-items: center;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 20px;
|
|
|
+}
|
|
|
+.file-icon.pdf {
|
|
|
+ background: #fef0f0;
|
|
|
+ color: #f56c6c;
|
|
|
+}
|
|
|
+.file-icon.word {
|
|
|
+ background: #ecf5ff;
|
|
|
+ color: #409eff;
|
|
|
+}
|
|
|
+.file-icon.excel {
|
|
|
+ background: #f0f9eb;
|
|
|
+ color: #67c23a;
|
|
|
+}
|
|
|
+.file-info {
|
|
|
+ display: grid;
|
|
|
+ flex: 1;
|
|
|
+ gap: 5px;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+.file-info strong {
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+.review-action {
|
|
|
+ margin-top: 16px;
|
|
|
+ padding-top: 18px;
|
|
|
+ border-top: 1px solid var(--el-border-color-lighter);
|
|
|
+}
|
|
|
+.review-action > div:first-child {
|
|
|
+ display: grid;
|
|
|
+ gap: 5px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+.review-action small {
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+}
|
|
|
+.action-buttons {
|
|
|
+ justify-content: flex-end;
|
|
|
+ margin-top: 12px;
|
|
|
+}
|
|
|
+.empty-panel {
|
|
|
+ display: grid;
|
|
|
+ place-items: center;
|
|
|
+}
|
|
|
+@media (max-width: 1100px) {
|
|
|
+ .review-layout {
|
|
|
+ grid-template-columns: 300px minmax(0, 1fr);
|
|
|
+ }
|
|
|
+ .process-strip {
|
|
|
+ overflow-x: auto;
|
|
|
+ min-width: 620px;
|
|
|
+ }
|
|
|
+}
|
|
|
+@media (max-width: 800px) {
|
|
|
+ .review-layout {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+ .project-list-panel,
|
|
|
+ .review-detail-panel {
|
|
|
+ min-height: auto;
|
|
|
+ }
|
|
|
+ .budget-summary {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+ .detail-heading {
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+ .detail-heading h1 {
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|