Files
tms-erp-web/src/views/business/project-apply.vue

1922 lines
63 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<basic-container class="project-apply-page">
<avue-crud
:option="tableOption"
:table-loading="loading"
:data="data"
v-model:page="page"
v-model="tableForm"
ref="crud"
:permission="permissionList"
@search-change="searchChange"
@search-reset="searchReset"
@selection-change="selectionChange"
@current-change="currentChange"
@size-change="sizeChange"
@refresh-change="refreshChange"
@on-load="onLoad"
>
<template #menu-left>
<el-button
type="primary"
icon="el-icon-plus"
v-if="canMajorProjectSupplement"
@click="openProjectDialog('majorSupplement')"
>
重大项目补录
</el-button>
<el-button
type="primary"
icon="el-icon-plus"
v-if="canCreate"
@click="openProjectDialog('add')"
>
新增
</el-button>
<el-button
type="primary"
icon="el-icon-download"
plain
v-if="config.exportUrl && hasPermission(`${config.permission}_export`)"
@click="handleExport"
>
批量导出
</el-button>
<el-button
type="danger"
icon="el-icon-delete"
plain
v-if="hasPermission(`${config.permission}_delete`) && !readonlyScope"
@click="handleDelete"
>
批量删除
</el-button>
</template>
<template #approvalStatus="{ row }">
<el-tag :type="statusTagType(row.approvalStatus)">
{{ displayStatus(row, 'approvalStatus') }}
</el-tag>
</template>
<template #menu="{ row, index }">
<el-link
type="primary"
v-if="hasPermission(`${config.permission}_view`)"
@click="openProjectDialog('view', row)"
>
查看
</el-link>
<el-link type="primary" v-if="canEdit(row)" @click="openProjectDialog('edit', row)">
编辑
</el-link>
<el-link
v-for="operation in customOperations(row)"
:key="operation.action"
:type="operation.type || 'primary'"
@click="handleCustomOperation(operation, row)"
>
{{ operation.label }}
</el-link>
<el-link type="primary" v-if="canDelete(row)" @click="rowDel(row)"> 删除 </el-link>
</template>
</avue-crud>
<empty-pagination
:page="page"
@size-change="sizeChange"
@current-change="currentChange"
@load="onLoad(page, query)"
/>
<el-dialog
v-model="projectBox"
:title="projectDialogTitle"
append-to-body
destroy-on-close
width="1440px"
top="4vh"
class="project-apply-dialog"
@closed="resetProjectDialog"
>
<el-form
ref="projectForm"
:model="form"
:rules="formRules"
label-position="right"
label-width="auto"
class="project-apply-form"
:disabled="dialogReadonly"
>
<div class="dialog-section-title">项目基本信息</div>
<div class="project-apply-form__grid">
<el-form-item v-if="isChangeDialog" label="变更类型" prop="changeType">
<el-select v-model="form.changeType" placeholder="请选择变更类型">
<el-option
v-for="item in changeTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<div v-if="isChangeDialog"></div>
<el-form-item label="项目类型" prop="projectType">
<el-select
v-model="form.projectType"
placeholder="请选择项目类型"
clearable
:disabled="isBasicInfoReadonly || isMajorProjectSupplementDialog"
@change="handleProjectTypeChange"
>
<el-option
v-for="item in projectTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="项目编号" prop="projectCode">
<el-input v-model="form.projectCode" disabled placeholder="系统自动生成" />
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="form.projectName"
placeholder="请填写项目名称"
maxlength="50"
:disabled="isBasicInfoReadonly"
/>
</el-form-item>
<el-form-item label="项目简称" prop="projectShortName">
<el-input
v-model="form.projectShortName"
placeholder="请填写项目简称"
maxlength="20"
:disabled="isBasicInfoReadonly"
/>
</el-form-item>
<el-form-item label="业务部门" prop="businessDeptId">
<el-select
v-model="form.businessDeptId"
placeholder="请选择业务部门"
filterable
clearable
:disabled="isBasicInfoReadonly"
@change="handleBusinessDeptChange"
>
<el-option
v-for="item in deptOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="承办部门" prop="undertakeDeptId">
<el-select
v-model="form.undertakeDeptId"
placeholder="请选择承办部门"
filterable
clearable
:disabled="isBasicInfoReadonly"
@change="handleUndertakeDeptChange"
>
<el-option
v-for="item in deptOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="项目由来" prop="projectSource">
<el-select
v-model="form.projectSource"
placeholder="请选择项目由来"
clearable
:disabled="isBasicInfoReadonly"
>
<el-option
v-for="item in projectSourceOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="项目由来说明" prop="sourceRemark">
<el-input v-model="form.sourceRemark" maxlength="300" :disabled="isBasicInfoReadonly" />
</el-form-item>
</div>
<div class="dialog-section-title">项目主要内容</div>
<div class="project-apply-form__grid">
<el-form-item label="客户名称" prop="customerNames">
<el-select
v-model="selectedCustomerId"
placeholder="搜索选择"
filterable
clearable
:loading="customerLoading"
@visible-change="visible => visible && loadCustomerOptions('客户')"
@change="handleCustomerChange"
>
<el-option
v-for="item in customerOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="下游承运商" prop="carrierNames">
<el-select
v-model="selectedCarrierIds"
placeholder="支持多选选择后下方列表展示"
multiple
filterable
clearable
collapse-tags
:loading="carrierLoading"
@visible-change="visible => visible && loadCustomerOptions('承运商')"
@change="handleCarrierChange"
>
<el-option
v-for="item in carrierOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="项目资金使用额度万元" prop="fundLimit">
<el-input
v-model="form.fundLimit"
placeholder="实际业务回款周期内付款额度"
@input="value => handleNumberInput('fundLimit', value)"
/>
</el-form-item>
<el-form-item label="项目应收账款额度万元" prop="receivableLimit">
<el-input
v-model="form.receivableLimit"
placeholder="回款期内最大应收账款额度"
@input="value => handleNumberInput('receivableLimit', value)"
/>
</el-form-item>
<el-form-item label="应收账款回款期限" prop="receivableDays">
<el-input
v-model="form.receivableDays"
@input="value => handleIntegerInput('receivableDays', value)"
>
<template #suffix>天</template>
</el-input>
</el-form-item>
<el-form-item label="回款账期" prop="paymentDays">
<el-input
v-model="form.paymentDays"
@input="value => handleIntegerInput('paymentDays', value)"
>
<template #suffix>天</template>
</el-input>
</el-form-item>
<el-form-item label="货物类型" prop="cargoType">
<el-select v-model="form.cargoType" placeholder="请选择货物类型" filterable clearable>
<el-option
v-for="item in cargoTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="预估货物数量" prop="cargoQuantity">
<el-input
v-model="form.cargoQuantity"
placeholder="请输入数字"
@input="value => handleIntegerInput('cargoQuantity', value)"
/>
</el-form-item>
<el-form-item label="业务周期" prop="businessDateRange">
<el-date-picker
v-model="form.businessDateRange"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
unlink-panels
/>
</el-form-item>
<el-form-item label="运输线路" prop="transportRoute">
<el-input v-model="form.transportRoute" maxlength="100" />
</el-form-item>
<el-form-item label="运输类型" prop="transportType">
<el-select v-model="form.transportType" placeholder="请选择运输类型" clearable>
<el-option
v-for="item in transportTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="业务类型" prop="businessType">
<el-select v-model="form.businessType" placeholder="请选择业务类型" clearable>
<el-option
v-for="item in businessTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="项目规模万元" prop="projectScale">
<el-input
v-model="form.projectScale"
@input="value => handleNumberInput('projectScale', value)"
/>
</el-form-item>
<el-form-item label="结算方式" prop="settlementMode">
<el-select v-model="form.settlementMode" placeholder="请选择结算方式" clearable>
<el-option
v-for="item in settlementModeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="预估利润万元" prop="estimatedProfit">
<el-input
v-model="form.estimatedProfit"
@input="value => handleNumberInput('estimatedProfit', value)"
/>
</el-form-item>
<el-form-item label="资金需求万元" prop="fundDemand">
<el-input
v-model="form.fundDemand"
@input="value => handleNumberInput('fundDemand', value)"
/>
</el-form-item>
<el-form-item label="项目经办人" prop="handlerUserId">
<el-input
v-model="form.handlerUserName"
readonly
placeholder="弹窗搜索选择"
@click="openUserPicker('handler')"
>
<template #suffix>
<el-icon class="project-apply-form__picker-icon"><Search /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="项目负责人" prop="principalUserId">
<el-input
v-model="form.principalUserName"
readonly
placeholder="弹窗搜索选择"
@click="openUserPicker('principal')"
>
<template #suffix>
<el-icon class="project-apply-form__picker-icon"><Search /></el-icon>
</template>
</el-input>
</el-form-item>
</div>
<div class="dialog-section-title project-apply-form__table-title">客户信息</div>
<el-table :data="customerRows" border class="project-apply-form__table">
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column prop="customer" label="客户" min-width="160" align="center" />
<el-table-column prop="credit" label="客户信用额度" min-width="150" align="center" />
<el-table-column prop="companyNature" label="企业性质" min-width="140" align="center" />
<el-table-column prop="legalPerson" label="法定代表人" min-width="140" align="center" />
<el-table-column
prop="address"
label="联系地址"
min-width="180"
align="center"
show-overflow-tooltip
/>
<el-table-column prop="contact" label="联系人" min-width="120" align="center" />
<el-table-column prop="phone" label="联系方式" min-width="140" align="center" />
</el-table>
<div class="dialog-section-title">承运商信息</div>
<el-table :data="carrierRows" border class="project-apply-form__table">
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column prop="carrier" label="承运商" min-width="180" align="center" />
<el-table-column prop="companyNature" label="企业性质" min-width="140" align="center" />
<el-table-column prop="legalPerson" label="法定代表人" min-width="140" align="center" />
<el-table-column
prop="address"
label="联系地址"
min-width="180"
align="center"
show-overflow-tooltip
/>
<el-table-column prop="contact" label="联系人" min-width="120" align="center" />
<el-table-column prop="phone" label="联系方式" min-width="140" align="center" />
</el-table>
<div class="dialog-section-title">项目情况说明</div>
<div class="project-apply-form__textarea-list">
<el-form-item label="项目简介" prop="projectIntro">
<el-input
v-model="form.projectIntro"
type="textarea"
:rows="4"
placeholder="请填写备注信息"
maxlength="200"
show-word-limit
/>
</el-form-item>
<el-form-item label="盈利模式及利润测算情况说明" prop="profitRemark">
<el-input
v-model="form.profitRemark"
type="textarea"
:rows="4"
placeholder="请填写备注信息"
maxlength="200"
show-word-limit
/>
</el-form-item>
<el-form-item label="项目风险点" prop="riskPoint">
<el-input
v-model="form.riskPoint"
type="textarea"
:rows="4"
placeholder="请填写备注信息"
maxlength="200"
show-word-limit
/>
</el-form-item>
<el-form-item label="风险防范措施及应急预案" prop="emergencyPlan">
<el-input
v-model="form.emergencyPlan"
type="textarea"
:rows="4"
placeholder="请填写备注信息"
maxlength="200"
show-word-limit
/>
</el-form-item>
</div>
<div class="project-apply-form__material-head">
<div class="dialog-section-title project-apply-form__material-title">项目材料</div>
<el-button type="primary" :disabled="!attachmentRows.length" @click="handleBatchDownload"
>批量下载</el-button
>
</div>
<el-table :data="attachmentRows" border class="project-apply-form__table">
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column label="附件类型" min-width="160" align="center">
<template #default="{ row }">
<el-select v-model="row.fileType" placeholder="请选择" :disabled="dialogReadonly">
<el-option
v-for="item in projectAttachmentTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="文件名" min-width="220" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.originalName || row.name }}</template>
</el-table-column>
<el-table-column label="附件描述" min-width="240" align="center">
<template #default="{ row }">
<el-input
v-model="row.description"
placeholder="请输入附件描述"
:disabled="dialogReadonly"
/>
</template>
</el-table-column>
<el-table-column label="文件大小" min-width="120" align="center">
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
</el-table-column>
<el-table-column prop="uploadUserName" label="上传人" min-width="140" align="center" />
<el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center" />
<el-table-column label="操作" width="110" align="center">
<template #default="{ row, $index }">
<el-link type="primary" @click="downloadAttachment(row)">下载</el-link>
<el-link
v-if="!dialogReadonly"
type="primary"
@click="removeAttachment($index)"
>删除</el-link
>
</template>
</el-table-column>
</el-table>
<vehicle-attachment-upload
v-model="attachmentRows"
class="project-apply-form__upload"
:readonly="dialogReadonly"
:file-types="attachmentFileTypes"
:max-size="500"
button-text="上传附件"
tip="支持pdfbmpjpegpngjpgdocdocxpptpptxxlsxxlsemlmsgzip的文件格式单个文件大小限制500M"
@change="handleAttachmentChange"
/>
<div v-if="!isChangeDialog" class="dialog-section-title project-apply-form__table-title">
变更记录
</div>
<el-table
v-if="!isChangeDialog"
:data="changeRows"
border
class="project-apply-form__table"
>
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column prop="changeDate" label="变更日期" min-width="140" align="center" />
<el-table-column prop="changeType" label="变更类型" min-width="160" align="center" />
<el-table-column prop="handler" label="经办人" min-width="120" align="center" />
<el-table-column
prop="content"
label="变更内容"
min-width="180"
align="center"
show-overflow-tooltip
/>
<el-table-column
prop="reason"
label="变更原因"
min-width="180"
align="center"
show-overflow-tooltip
/>
<el-table-column prop="status" label="状态" min-width="120" align="center" />
<el-table-column label="操作" width="100" align="center">
<template #default="{ row }">
<el-link type="primary" @click="handleChangeRecordAction(row)">
{{ row.action || '查看' }}
</el-link>
</template>
</el-table-column>
</el-table>
<template v-if="isChangeDialog">
<div class="dialog-section-title">变更内容</div>
<el-form-item prop="changeContent" class="project-apply-form__change-textarea">
<el-input
v-model="form.changeContent"
type="textarea"
:rows="5"
placeholder="2000字以内"
maxlength="2000"
show-word-limit
/>
</el-form-item>
<div class="dialog-section-title">变更原因</div>
<el-form-item prop="changeReason" class="project-apply-form__change-textarea">
<el-input
v-model="form.changeReason"
type="textarea"
:rows="5"
placeholder="2000字以内"
maxlength="2000"
show-word-limit
/>
</el-form-item>
</template>
</el-form>
<template #footer>
<div
class="project-apply-dialog__footer"
:class="{ 'project-apply-dialog__footer--change': isChangeDialog }"
>
<template v-if="isChangeDialog">
<el-button type="primary" :loading="submitLoading" @click="submitChangeProject">
提交
</el-button>
<el-button type="primary" :loading="submitLoading" @click="saveChangeProject">
保存
</el-button>
<el-button @click="projectBox = false">关闭</el-button>
</template>
<template v-else>
<el-button @click="projectBox = false">取消</el-button>
<el-button
v-if="!dialogReadonly"
type="primary"
:loading="submitLoading"
@click="submitProject"
>
提交
</el-button>
</template>
</div>
</template>
</el-dialog>
<el-dialog
v-model="userBox"
title="选择用户"
append-to-body
destroy-on-close
width="900px"
class="project-apply-user-dialog"
>
<div class="project-apply-user-dialog__search">
<el-input
v-model="userQuery.realName"
placeholder="用户姓名"
clearable
@keyup.enter="loadUsers"
/>
<el-input
v-model="userQuery.account"
placeholder="登录账号"
clearable
@keyup.enter="loadUsers"
/>
<el-button type="primary" @click="loadUsers">查询</el-button>
<el-button @click="resetUserQuery">重置</el-button>
</div>
<el-table :data="userData" border v-loading="userLoading" @row-dblclick="selectUser">
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="account" label="登录账号" min-width="140" align="center" />
<el-table-column prop="realName" label="用户姓名" min-width="140" align="center" />
<el-table-column prop="deptName" label="所属部门" min-width="180" align="center" />
<el-table-column label="操作" width="100" align="center">
<template #default="{ row }">
<el-link type="primary" @click="selectUser(row)">选择</el-link>
</template>
</el-table-column>
</el-table>
<div class="project-apply-user-dialog__pager">
<el-pagination
v-model:current-page="userPage.currentPage"
v-model:page-size="userPage.pageSize"
:page-sizes="[10, 20, 50]"
:total="userPage.total"
layout="total, sizes, prev, pager, next"
@current-change="loadUsers"
@size-change="loadUsers"
/>
</div>
</el-dialog>
</basic-container>
</template>
<script>
import { exportBlob } from '@/api/common';
import * as api from '@/api/business/project-apply';
import {
getList as getCustomerArchiveList,
getDetail as getCustomerArchiveDetail,
} from '@/api/vehicle/customer-archive';
import { getDeptTree } from '@/api/system/dept';
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
import { getList as getUserList } from '@/api/system/user';
import { getToken } from '@/utils/auth';
import { applyTableMenuWidth } from '@/utils/table-menu';
import { downloadFileByUrl, downloadXls } from '@/utils/util';
import { mapGetters } from 'vuex';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import {
businessTypeOptions,
projectSourceOptions,
projectTypeOptions,
settlementModeOptions,
} from '@/option/business/common';
import { config, option } from '@/option/business/project-apply';
const emptyForm = () => ({
projectType: '',
projectCode: '',
projectName: '',
projectShortName: '',
businessDeptId: '',
businessDeptName: '',
undertakeDeptId: '',
undertakeDeptName: '',
projectSource: '商务洽谈',
sourceRemark: '',
changeType: '',
customerNames: '',
carrierNames: '',
fundLimit: '',
receivableLimit: '',
receivableDays: '',
paymentDays: '',
cargoType: '',
cargoQuantity: '',
businessDateRange: [],
businessStartDate: '',
businessEndDate: '',
transportRoute: '',
transportType: '',
businessType: '',
projectScale: '',
settlementMode: '',
estimatedProfit: '',
fundDemand: '',
handlerUserId: '',
handlerUserName: '',
principalUserId: '',
principalUserName: '',
customerJson: '',
carrierJson: '',
attachmentsJson: '',
situationRemark: '',
projectIntro: '',
profitRemark: '',
riskPoint: '',
emergencyPlan: '',
changeContent: '',
changeReason: '',
});
const changeTypeOptions = [
{ label: '项目变更', value: '项目变更' },
{ label: '项目备案调整', value: '项目备案调整' },
];
const normalProjectAttachmentTypeOptions = [
'利润测算表',
'单一来源供应商申请表/三方比价表',
'合同模板',
'项目操作说明',
'项目分析报告',
'项目考察报告',
].map(item => ({ label: item, value: item }));
const majorProjectAttachmentTypeOptions = [
'会议纪要',
'评审表决票',
'利润测算表',
'单一来源供应商申请表/三方比价表',
'项目操作说明',
'项目分析报告',
'项目考察报告',
'合同模板',
'上游尽职调查报告',
'下游尽职调查报告',
'业务承诺函',
].map(item => ({ label: item, value: item }));
export default {
data() {
const validateAmount = (rule, value, callback) => {
if (value === '' || value === undefined || value === null) {
callback();
return;
}
if (Number(value) < 0) {
callback(new Error(`${rule.label || '金额'}不能小于0`));
return;
}
callback();
};
const validateRequiredAmount = (rule, value, callback) => {
if (value === '' || value === undefined || value === null) {
callback(new Error(`请输入${rule.label}`));
return;
}
validateAmount(rule, value, callback);
};
const validateDays = (rule, value, callback) => {
if (value === '' || value === undefined || value === null) {
callback(new Error(`请输入${rule.label}`));
return;
}
const number = Number(value);
if (!Number.isInteger(number) || number < 0 || number > 999) {
callback(new Error(`${rule.label}范围为0到999天`));
return;
}
callback();
};
const validateUser = (rule, value, callback) => {
if (value || this.form[rule.nameProp]) {
callback();
return;
}
callback(new Error(`请选择${rule.label}`));
};
return {
api,
config,
projectTypeOptions,
projectSourceOptions,
transportTypeOptions: [],
businessTypeOptions,
settlementModeOptions,
changeTypeOptions,
tableForm: {},
tableOption: this.buildTableOption(),
loading: true,
data: [],
query: {},
allDept: 0,
page: {
pageSize: 10,
pageSizes: [10, 20, 50, 100],
currentPage: 1,
total: 0,
},
selectionList: [],
projectBox: false,
dialogType: 'add',
dialogReadonly: false,
submitLoading: false,
form: emptyForm(),
formRules: {
changeType: [{ required: true, message: '请选择变更类型', trigger: 'change' }],
projectType: [{ required: true, message: '请选择项目类型', trigger: 'change' }],
projectName: [
{ required: true, message: '请输入项目名称', trigger: 'blur' },
{ max: 50, message: '项目名称不能超过50个字符', trigger: 'blur' },
],
projectShortName: [
{ required: true, message: '请输入项目简称', trigger: 'blur' },
{ max: 20, message: '项目简称不能超过20个字符', trigger: 'blur' },
],
businessDeptId: [{ required: true, message: '请选择业务部门', trigger: 'change' }],
undertakeDeptId: [{ required: true, message: '请选择承办部门', trigger: 'change' }],
customerNames: [{ required: true, message: '请选择客户名称', trigger: 'change' }],
carrierNames: [{ required: true, message: '请选择下游承运商', trigger: 'change' }],
fundLimit: [
{
required: true,
label: '项目资金使用额度',
validator: validateRequiredAmount,
trigger: 'blur',
},
],
receivableLimit: [
{
required: true,
label: '项目应收账款额度',
validator: validateRequiredAmount,
trigger: 'blur',
},
],
receivableDays: [
{ required: true, label: '应收账款回款期限', validator: validateDays, trigger: 'blur' },
],
paymentDays: [
{ required: true, label: '回款账期', validator: validateDays, trigger: 'blur' },
],
projectScale: [{ label: '项目规模', validator: validateAmount, trigger: 'blur' }],
estimatedProfit: [{ label: '预估利润', validator: validateAmount, trigger: 'blur' }],
fundDemand: [{ label: '资金需求', validator: validateAmount, trigger: 'blur' }],
handlerUserId: [
{
label: '项目经办人',
nameProp: 'handlerUserName',
validator: validateUser,
trigger: 'change',
},
],
principalUserId: [
{
label: '项目负责人',
nameProp: 'principalUserName',
validator: validateUser,
trigger: 'change',
},
],
changeContent: [
{ required: true, message: '请输入变更内容', trigger: 'blur' },
{ max: 2000, message: '变更内容不能超过2000个字符', trigger: 'blur' },
],
changeReason: [
{ required: true, message: '请输入变更原因', trigger: 'blur' },
{ max: 2000, message: '变更原因不能超过2000个字符', trigger: 'blur' },
],
},
deptOptions: [],
cargoTypeOptions: [],
selectedCustomerId: '',
selectedCarrierIds: [],
customerOptions: [],
carrierOptions: [],
customerLoading: false,
carrierLoading: false,
customerRows: [],
carrierRows: [],
attachmentRows: [],
attachmentFileTypes: [
'pdf',
'bmp',
'jpeg',
'png',
'jpg',
'doc',
'docx',
'ppt',
'pptx',
'xlsx',
'xls',
'eml',
'msg',
'zip',
],
changeRows: [],
userBox: false,
userPickType: '',
userLoading: false,
userData: [],
userQuery: {
realName: '',
account: '',
},
userPage: {
currentPage: 1,
pageSize: 10,
total: 0,
},
};
},
computed: {
...mapGetters(['permission', 'userInfo']),
permissionList() {
return {
addBtn: this.canCreate,
};
},
isAdmin() {
const authority = this.userInfo && this.userInfo.authority;
return Array.isArray(authority)
? authority.includes('admin')
: String(authority || '').includes('admin');
},
canViewAllDept() {
return this.isAdmin;
},
readonlyScope() {
return this.config.enableAllDept && this.allDept === 1;
},
canCreate() {
return this.hasPermission(`${this.config.permission}_add`) && !this.readonlyScope;
},
canMajorProjectSupplement() {
return this.hasPermission(`${this.config.permission}_supplement`) && !this.readonlyScope;
},
isMajorProjectSupplementDialog() {
return this.dialogType === 'majorSupplement';
},
isChangeDialog() {
return this.dialogType === 'change';
},
isBasicInfoReadonly() {
return this.dialogReadonly || this.isChangeDialog;
},
ids() {
return this.selectionList.map(item => item.id).join(',');
},
projectDialogTitle() {
if (this.isChangeDialog) {
return '项目变更';
}
if (this.isMajorProjectSupplementDialog) {
return '重大项目补录';
}
const text = { add: '新增', edit: '编辑', view: '查看' }[this.dialogType] || '新增';
return `${text}${this.config.title}`;
},
projectAttachmentTypeOptions() {
return this.form.projectType === '重大项目'
? majorProjectAttachmentTypeOptions
: normalProjectAttachmentTypeOptions;
},
},
created() {
this.loadDeptOptions();
this.loadCargoTypeOptions();
this.loadTransportTypeOptions();
},
methods: {
buildTableOption() {
return applyTableMenuWidth(
{
...option,
dialogType: 'dialog',
addBtn: false,
viewBtn: false,
editBtn: false,
delBtn: false,
column: (option.column || []).map(column => ({ ...column, display: false })),
},
4
);
},
hasPermission(code) {
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
},
statusValue(row) {
return row[this.config.statusProp || 'status'];
},
inStatus(row, statusList) {
if (!statusList || !statusList.length) return true;
return statusList.includes(this.statusValue(row));
},
canEdit(row) {
return (
this.hasPermission(`${this.config.permission}_edit`) &&
!this.readonlyScope &&
!row.readonly &&
this.inStatus(row, this.config.editStatus)
);
},
canDelete(row) {
return (
this.hasPermission(`${this.config.permission}_delete`) &&
!this.readonlyScope &&
!row.readonly &&
this.inStatus(row, this.config.deleteStatus)
);
},
customOperations(row) {
return (this.config.operations || []).filter(operation => this.canOperation(operation, row));
},
canOperation(operation, row) {
const permission = operation.permission || `${this.config.permission}_${operation.action}`;
if (!this.hasPermission(permission) || this.readonlyScope || row.readonly) return false;
const prop = operation.statusProp || this.config.statusProp || 'status';
const statusList = operation.status || [];
return !statusList.length || statusList.includes(row[prop]);
},
displayStatus(row, prop) {
const textProp = prop === this.config.statusProp ? this.config.statusTextProp : '';
if (textProp && row[textProp]) return row[textProp];
const column = this.findColumn(this.tableOption.column, prop);
const item = column && (column.dicData || []).find(dic => dic.value === row[prop]);
return item ? item.label : row[prop] || '未知';
},
statusTagType(status) {
if (
[
1,
'pending',
'waiting_dispatch',
'processing',
'dispatching',
'approved',
'change_approved',
].includes(status)
) {
return 'success';
}
if ([2, 'cancelled', 'withdrawn', 'draft'].includes(status)) return 'info';
if (['rejected', 'change_rejected'].includes(status)) return 'danger';
if (status === 'completed') return 'primary';
return 'warning';
},
onLoad(page, params = {}) {
this.loading = true;
this.api
.getList(page.currentPage, page.pageSize, {
...params,
...this.query,
allDept: this.allDept,
})
.then(res => {
const result = res.data.data || {};
this.page.total = result.total || 0;
this.data = result.records || [];
this.selectionClear();
})
.finally(() => {
this.loading = false;
});
},
searchReset() {
this.query = {};
this.page.currentPage = 1;
this.onLoad(this.page);
},
searchChange(params, done) {
this.query = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
done();
},
selectionChange(list) {
this.selectionList = list;
},
selectionClear() {
this.selectionList = [];
if (this.$refs.crud) {
this.$refs.crud.toggleSelection();
}
},
currentChange(currentPage) {
this.page.currentPage = currentPage;
},
sizeChange(pageSize) {
this.page.pageSize = pageSize;
},
refreshChange() {
this.onLoad(this.page, this.query);
},
handleScopeChange() {
if (!this.canViewAllDept) this.allDept = 0;
this.page.currentPage = 1;
this.onLoad(this.page);
},
rowDel(row) {
this.$confirm('确定将选择数据删除?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => this.api.remove(row.id))
.then(res => this.afterRemove(res.data.data));
},
handleDelete() {
if (!this.selectionList.length) {
this.$message.warning('请选择至少一条数据');
return;
}
const selection = this.selectionList.filter(item => this.canDelete(item));
if (!selection.length) {
this.$message.warning('所选数据当前状态不可删除');
return;
}
this.$confirm('确定将选择数据删除?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => this.api.remove(selection.map(item => item.id).join(',')))
.then(res => this.afterRemove(res.data.data));
},
afterRemove(result = {}) {
this.onLoad(this.page);
const successCount = result.successCount || 0;
const skippedCount = result.skippedCount || 0;
if (skippedCount) {
this.$message.warning(`成功删除${successCount}条,因业务引用跳过${skippedCount}条`);
} else {
this.$message.success('删除成功!');
}
this.selectionClear();
},
handleCustomOperation(operation, row) {
if (operation.action === 'startChange') {
this.openProjectDialog('change', row);
return;
}
const run = value => {
const args = operation.prompt ? [row.id, value] : [row.id];
this.api[operation.action](...args).then(res => {
if (res.data?.success === false || res.data?.data === false) {
this.$message.error(res.data?.msg || `${operation.label}失败,请联系管理员`);
return;
}
this.$message.success(`${operation.label}成功`);
this.onLoad(this.page, this.query);
});
};
if (operation.prompt) {
this.$prompt(operation.prompt, operation.label, {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\S+/,
inputErrorMessage: operation.promptError || '请输入内容',
}).then(({ value }) => run(value));
return;
}
this.$confirm(operation.confirm || `确定${operation.label}该${this.config.title}?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => run());
},
buildExportParams() {
return {
...this.query,
allDept: this.allDept,
ids: this.ids,
[this.website.tokenHeader]: getToken(),
};
},
handleExport() {
this.$confirm(`是否导出${this.config.exportName || this.config.title}?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob(this.config.exportUrl, this.buildExportParams(), { feedback: true })
.then(res => {
downloadXls(
res.data,
`${this.config.exportName || this.config.title}${this.$dayjs().format(
'YYYY-MM-DD HH:mm:ss'
)}.xlsx`
);
})
.finally(() => {
NProgress.done();
});
});
},
openProjectDialog(type, row = {}) {
this.dialogType = type;
this.dialogReadonly = type === 'view';
this.projectBox = true;
if (type === 'add' || type === 'majorSupplement') {
this.applyProjectDetail({
...emptyForm(),
projectType: type === 'majorSupplement' ? '重大项目' : '',
});
this.fillDefaultUsers();
return;
}
this.api.getDetail(row.id).then(res => {
const detail = res.data.data || {};
this.applyProjectDetail({
...detail,
changeType: type === 'change' ? this.resolveChangeType(detail) : detail.changeType,
});
});
},
resetProjectDialog() {
this.$refs.projectForm?.clearValidate();
this.form = emptyForm();
this.customerRows = [];
this.carrierRows = [];
this.attachmentRows = [];
this.changeRows = [];
this.selectedCustomerId = '';
this.selectedCarrierIds = [];
},
applyProjectDetail(row) {
const form = {
...emptyForm(),
...row,
businessDateRange:
row.businessStartDate && row.businessEndDate
? [row.businessStartDate, row.businessEndDate]
: [],
};
const situation = this.parseSituation(row.situationRemark);
const customerRows = this.parseJsonArray(row.customerJson);
const carrierRows = this.parseJsonArray(row.carrierJson);
this.form = {
...form,
...situation,
customerNames: this.getCustomerRowNames(customerRows, 'customer') || form.customerNames,
carrierNames: this.getCustomerRowNames(carrierRows, 'carrier') || form.carrierNames,
};
this.customerRows = customerRows;
this.carrierRows = carrierRows;
this.attachmentRows = this.parseJsonArray(row.attachmentsJson);
this.changeRows = this.buildChangeRows(row);
this.selectedCustomerId = this.getCustomerRowId(this.customerRows[0]);
this.selectedCarrierIds = this.carrierRows
.map(item => this.getCustomerRowId(item))
.filter(Boolean);
this.syncSelectedCustomerOptions();
},
fillDefaultUsers() {
const userId = this.userInfo?.userId || this.userInfo?.id || '';
const userName =
this.userInfo?.realName || this.userInfo?.userName || this.userInfo?.account || '';
if (userId && userName) {
this.form.handlerUserId = userId;
this.form.handlerUserName = userName;
this.form.principalUserId = userId;
this.form.principalUserName = userName;
}
},
resolveChangeType(row = {}) {
if (row.changeType) return row.changeType;
const node = row.currentNode || '';
if (String(node).includes('备案')) return '项目备案调整';
return '项目变更';
},
submitProject() {
this.$refs.projectForm.validate(valid => {
if (!valid) {
this.handleValidateFail();
return;
}
if (!this.validateAttachmentFileTypes()) return;
this.submitLoading = true;
this.api
.submit(this.normalizeSubmitForm())
.then(
res => {
if (res.data?.success === false || res.data?.data === false) {
this.$message.error(res.data?.msg || '保存失败,请联系管理员');
return;
}
this.$message.success('操作成功!');
this.projectBox = false;
this.onLoad(this.page, this.query);
},
error => {
window.console.log(error);
}
)
.finally(() => {
this.submitLoading = false;
});
});
},
saveChangeProject() {
this.submitChangeForm(false);
},
submitChangeProject() {
this.submitChangeForm(true);
},
submitChangeForm(needSubmit) {
this.$refs.projectForm.validate(valid => {
if (!valid) {
this.handleValidateFail();
return;
}
if (!this.validateAttachmentFileTypes()) return;
this.submitLoading = true;
const request = needSubmit ? this.api.submitChange : this.api.saveChange;
request(this.normalizeSubmitForm({ includeChangeType: true }))
.then(res => {
if (res.data?.success === false || res.data?.data === false) {
this.$message.error(
res.data?.msg || `${needSubmit ? '提交' : '保存'}失败,请联系管理员`
);
return;
}
this.$message.success(`${needSubmit ? '提交' : '保存'}成功!`);
this.projectBox = false;
this.onLoad(this.page, this.query);
})
.finally(() => {
this.submitLoading = false;
});
});
},
normalizeSubmitForm(options = {}) {
const [businessStartDate, businessEndDate] = this.form.businessDateRange || [];
const submitRow = {
...this.form,
businessStartDate,
businessEndDate,
customerJson: JSON.stringify(this.customerRows),
carrierJson: JSON.stringify(this.carrierRows),
attachmentsJson: JSON.stringify(this.attachmentRows),
situationRemark: JSON.stringify({
projectIntro: this.form.projectIntro || '',
profitRemark: this.form.profitRemark || '',
riskPoint: this.form.riskPoint || '',
emergencyPlan: this.form.emergencyPlan || '',
}),
};
delete submitRow.businessDateRange;
if (!options.includeChangeType) {
delete submitRow.changeType;
}
['projectIntro', 'profitRemark', 'riskPoint', 'emergencyPlan'].forEach(
key => delete submitRow[key]
);
Object.keys(submitRow).forEach(key => {
if (typeof submitRow[key] === 'string') {
submitRow[key] = submitRow[key].trim();
}
});
return submitRow;
},
normalizeAttachmentFileTypes() {
const values = this.projectAttachmentTypeOptions.map(item => item.value);
this.attachmentRows = this.attachmentRows.map(row => ({
...row,
fileType: values.includes(row.fileType) ? row.fileType : '',
}));
},
validateAttachmentFileTypes() {
const values = this.projectAttachmentTypeOptions.map(item => item.value);
const invalidIndex = this.attachmentRows.findIndex(row => !values.includes(row.fileType));
if (invalidIndex === -1) return true;
this.$message.warning(`请选择第${invalidIndex + 1}行项目材料的附件类型`);
this.$nextTick(() => {
const field = this.$el.querySelector('.project-apply-form__material-head');
if (field && typeof field.scrollIntoView === 'function') {
field.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
return false;
},
handleValidateFail() {
this.$message.warning('请完善表单必填项后再提交');
this.$nextTick(() => {
const field = this.$el.querySelector('.is-error');
if (field && typeof field.scrollIntoView === 'function') {
field.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
},
parseSituation(value) {
if (!value) {
return { projectIntro: '', profitRemark: '', riskPoint: '', emergencyPlan: '' };
}
try {
const data = JSON.parse(value);
if (data && typeof data === 'object') {
return {
projectIntro: data.projectIntro || '',
profitRemark: data.profitRemark || '',
riskPoint: data.riskPoint || '',
emergencyPlan: data.emergencyPlan || '',
};
}
} catch (error) {
return { projectIntro: value, profitRemark: '', riskPoint: '', emergencyPlan: '' };
}
return { projectIntro: String(value), profitRemark: '', riskPoint: '', emergencyPlan: '' };
},
parseJsonArray(value) {
if (!value) return [];
if (Array.isArray(value)) return value;
try {
const list = JSON.parse(value);
return Array.isArray(list) ? list : [];
} catch (error) {
return [];
}
},
getCustomerRowId(row = {}) {
return row.id || row.customerId || row.carrierId || '';
},
getCustomerRowLabel(row = {}, type) {
return (
row[type === 'carrier' ? 'carrier' : 'customer'] ||
row.fullName ||
row.shortName ||
row.customerName ||
row.carrierName ||
row.customerCode ||
''
);
},
getCustomerRowNames(rows = [], type) {
return rows
.map(row => this.getCustomerRowLabel(row, type))
.filter(Boolean)
.join('、');
},
buildCustomerOptionsFromRows(rows = [], type) {
return rows
.map(row => {
const value = this.getCustomerRowId(row);
const label = this.getCustomerRowLabel(row, type);
return value && label ? { label, value, raw: row } : null;
})
.filter(Boolean);
},
mergeCustomerOptions(options = [], selectedOptions = []) {
const result = [...selectedOptions];
options.forEach(option => {
const exists = result.some(item => String(item.value) === String(option.value));
if (!exists) {
result.push(option);
}
});
return result;
},
syncSelectedCustomerOptions() {
this.customerOptions = this.mergeCustomerOptions(
this.customerOptions,
this.buildCustomerOptionsFromRows(this.customerRows, 'customer')
);
this.carrierOptions = this.mergeCustomerOptions(
this.carrierOptions,
this.buildCustomerOptionsFromRows(this.carrierRows, 'carrier')
);
},
buildChangeRows(row) {
if (!row || (!row.changeContent && !row.changeReason)) return [];
return [
{
changeDate: row.updateTime || row.createTime || '',
changeType:
row.changeType ||
(String(row.currentNode || '').includes('备案') ? '项目备案调整' : '项目变更'),
handler: row.handlerUserName || '',
content: row.changeContent || '',
reason: row.changeReason || '',
status: row.approvalStatusName || row.approvalStatus || '',
action: this.dialogReadonly ? '流程' : '编辑',
},
];
},
handleBusinessDeptChange(value) {
const dept = this.deptOptions.find(item => item.value === value);
this.form.businessDeptName = dept ? dept.rawLabel : '';
this.$refs.projectForm?.validateField('businessDeptId');
},
handleUndertakeDeptChange(value) {
const dept = this.deptOptions.find(item => item.value === value);
this.form.undertakeDeptName = dept ? dept.rawLabel : '';
this.$refs.projectForm?.validateField('undertakeDeptId');
},
handleProjectTypeChange() {
this.normalizeAttachmentFileTypes();
this.$refs.projectForm?.validateField('projectType');
},
loadDeptOptions() {
getDeptTree().then(res => {
this.deptOptions = this.flattenDept(res.data.data || []);
});
},
flattenDept(list = [], level = 0) {
return list.reduce((result, item) => {
const label = item.title || item.deptName || item.name;
result.push({
label: `${' '.repeat(level)}${label}`,
rawLabel: label,
value: item.id,
});
if (item.children && item.children.length) {
result.push(...this.flattenDept(item.children, level + 1));
}
return result;
}, []);
},
loadCargoTypeOptions() {
getSystemDictionary({ code: 'type_of_goods' }).then(res => {
this.cargoTypeOptions = (res.data.data || []).map(item => ({
label: item.dictValue,
value: item.dictKey,
}));
});
},
loadTransportTypeOptions() {
getBizDictionary({ code: 'transport_type' }).then(res => {
this.transportTypeOptions = (res.data.data || []).map(item => ({
label: item.dictValue,
value: item.dictKey,
}));
});
},
loadCustomerOptions(type) {
const loadingKey = type === '客户' ? 'customerLoading' : 'carrierLoading';
const optionsKey = type === '客户' ? 'customerOptions' : 'carrierOptions';
this[loadingKey] = true;
getCustomerArchiveList(1, 100, {
customerType: type,
status: 1,
...(type === '客户' ? { approvalStatus: 'approved' } : {}),
})
.then(res => {
const records = (res.data.data && res.data.data.records) || [];
const selectedOptions = this.buildCustomerOptionsFromRows(
type === '客户' ? this.customerRows : this.carrierRows,
type === '客户' ? 'customer' : 'carrier'
);
this[optionsKey] = this.mergeCustomerOptions(
records.map(item => this.mapCustomerOption(item)),
selectedOptions
);
})
.finally(() => {
this[loadingKey] = false;
});
},
mapCustomerOption(item) {
const label = item.fullName || item.shortName || item.customerCode;
return {
label,
value: item.id,
raw: item,
};
},
handleCustomerChange(id) {
if (!id) {
this.customerRows = [];
this.form.customerNames = '';
this.form.customerJson = '';
return;
}
this.loadCustomerDetailRow(id, 'customer').then(row => {
this.customerRows = [row];
this.form.customerNames = row.customer;
this.form.customerJson = JSON.stringify(this.customerRows);
this.syncSelectedCustomerOptions();
this.$refs.projectForm?.validateField('customerNames');
});
},
handleCarrierChange(ids) {
if (!ids || !ids.length) {
this.carrierRows = [];
this.form.carrierNames = '';
this.form.carrierJson = '';
return;
}
Promise.all(ids.map(id => this.loadCustomerDetailRow(id, 'carrier'))).then(rows => {
this.carrierRows = rows;
this.form.carrierNames = rows.map(item => item.carrier).join('、');
this.form.carrierJson = JSON.stringify(this.carrierRows);
this.syncSelectedCustomerOptions();
this.$refs.projectForm?.validateField('carrierNames');
});
},
loadCustomerDetailRow(id, type) {
return getCustomerArchiveDetail(id).then(res => {
const detail = res.data.data || {};
return this.mapCustomerTableRow(detail, type);
});
},
mapCustomerTableRow(item, type) {
const contacts = item.contacts || [];
const defaultContact = contacts.find(contact => contact.isDefault === 1) || contacts[0] || {};
const name = item.fullName || item.shortName || item.customerCode || '';
const row = {
id: item.id,
credit: item.maxCreditLimit || item.applyCreditLimit || '',
companyNature: item.customerNature || '',
legalPerson: item.legalPerson || '',
address: item.registeredAddress || '',
contact: defaultContact.contactName || item.principal || '',
phone: defaultContact.contactPhone || item.contactPhone || '',
};
if (type === 'carrier') {
row.carrier = name;
} else {
row.customer = name;
}
return row;
},
handleNumberInput(prop, value) {
const text = String(value || '').replace(/[^\d.]/g, '');
const parts = text.split('.');
this.form[prop] =
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
},
handleIntegerInput(prop, value) {
this.form[prop] = String(value || '').replace(/[^\d]/g, '');
},
handleAttachmentChange(list) {
const userName = this.userInfo?.realName || this.userInfo?.userName || '';
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
const values = this.projectAttachmentTypeOptions.map(item => item.value);
this.attachmentRows = (list || []).map(item => ({
...item,
fileType: values.includes(item.fileType) ? item.fileType : '',
description: item.description || '',
uploadUserName: item.uploadUserName || userName,
uploadTime: item.uploadTime || uploadTime,
}));
},
removeAttachment(index) {
this.attachmentRows.splice(index, 1);
},
formatFileSize(size) {
const number = Number(size);
if (!number) return '';
if (number < 1024) return `${number}B`;
if (number < 1024 * 1024) return `${(number / 1024).toFixed(1)}KB`;
return `${(number / 1024 / 1024).toFixed(1)}MB`;
},
downloadAttachment(row) {
const url = row.url || row.link;
if (!url) {
this.$message.warning('附件地址为空');
return;
}
downloadFileByUrl(url, row.originalName || row.name || '附件');
},
handleBatchDownload() {
this.attachmentRows.forEach(row => this.downloadAttachment(row));
},
handleChangeRecordAction() {
this.$message.info('变更记录详情由后端明细能力生成后展示');
},
openUserPicker(type) {
if (this.dialogReadonly) return;
this.userPickType = type;
this.userBox = true;
this.loadUsers();
},
loadUsers() {
this.userLoading = true;
getUserList(this.userPage.currentPage, this.userPage.pageSize, this.userQuery)
.then(res => {
const result = res.data.data || {};
this.userData = result.records || [];
this.userPage.total = result.total || 0;
})
.finally(() => {
this.userLoading = false;
});
},
resetUserQuery() {
this.userQuery = { realName: '', account: '' };
this.userPage.currentPage = 1;
this.loadUsers();
},
selectUser(row) {
if (this.userPickType === 'handler') {
this.form.handlerUserId = row.id;
this.form.handlerUserName = row.realName || row.name || row.account;
this.$refs.projectForm?.validateField('handlerUserId');
} else {
this.form.principalUserId = row.id;
this.form.principalUserName = row.realName || row.name || row.account;
this.$refs.projectForm?.validateField('principalUserId');
}
this.userBox = false;
},
},
};
</script>
<style lang="scss" scoped>
.project-apply-form {
padding: 0;
:deep(.el-input),
:deep(.el-select),
:deep(.el-date-editor) {
width: 100%;
}
:deep(.el-form-item) {
margin-bottom: 16px;
}
:deep(.el-form-item__label) {
min-width: 160px;
white-space: normal;
line-height: 20px;
}
// 分区标题融入白色卡片顶部(与下方内容块拼成一张完整卡片)
:deep(.dialog-section-title:not(.project-apply-form__material-title)) {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 8px;
background: #fff;
border-radius: 6px 6px 0 0;
padding: 14px 16px 4px;
margin-bottom: 0;
font-weight: 500;
}
&__grid {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
column-gap: 72px;
row-gap: 0;
margin-bottom: 16px;
background: #fff;
border-radius: 0 0 6px 6px;
padding: 14px 16px 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
&__picker-icon {
color: #a8abb2;
cursor: pointer;
}
&__table-title {
display: flex;
align-items: center;
gap: 24px;
}
&__tip {
color: #ff7a95;
font-size: 14px;
font-weight: 500;
}
&__table {
width: 100%;
margin-bottom: 16px;
background: #fff;
border-radius: 0 0 6px 6px;
padding: 12px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
:deep(.el-table__header th) {
background: #f5f7fa;
color: #303133;
font-weight: 600;
}
:deep(.el-table__cell) {
border-color: #eff1f7;
}
}
&__textarea-list {
margin-bottom: 16px;
background: #fff;
border-radius: 0 0 6px 6px;
padding: 14px 16px 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
:deep(.el-form-item) {
align-items: flex-start;
}
:deep(.el-form-item__content) {
width: calc(100% - 180px);
}
}
&__material-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
background: #fff;
border-radius: 6px;
padding: 12px 16px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
&__material-title {
flex: 1;
}
&__material-warn {
margin-left: 24px;
color: #f56c6c;
font-size: 14px;
font-weight: 500;
}
&__upload {
margin: 0 0 16px;
background: #fff;
border-radius: 6px;
padding: 12px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
&__change-textarea {
margin-bottom: 16px;
background: #fff;
border-radius: 0 0 6px 6px;
padding: 14px 16px 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
:deep(.el-form-item__content) {
width: 100%;
margin-left: 0 !important;
}
}
}
:global(.project-apply-dialog .el-dialog__body) {
max-height: 76vh;
overflow-y: auto;
overflow-x: hidden;
padding: 16px;
background: #f5f6fa;
}
.project-apply-dialog__footer {
display: flex;
justify-content: flex-end;
gap: 8px;
&--change {
justify-content: flex-start;
padding-left: 24px;
}
}
.project-apply-user-dialog {
&__search {
display: grid;
grid-template-columns: 1fr 1fr auto auto;
gap: 8px;
margin-bottom: 12px;
}
&__pager {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
}
@media (max-width: 1200px) {
.project-apply-form {
&__grid {
grid-template-columns: 1fr;
column-gap: 0;
}
}
}
</style>