index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. <script lang="ts" setup>
  2. import { ref, reactive, onMounted, inject } from "vue";
  3. import { tableShowOverflowTooltip } from '@/utils/globalVariables'
  4. import { getFromValue, resetFromValue, setTemplateDataDisable, downloadFile, formatDate, confirmAction, getTemplateKey, downloadTemplate } from '@/utils/tools'
  5. import { GenerateForm } from '@zmjs/form-design';
  6. import { post, get, uploadFile } from "@/utils/request";
  7. import { CONTRACT_DELETION, CONTRACT_OPERATION, DELETE_CONTRACT_FILE, EDIT_CONTRACT, EXPORT_CONTRACT, GET_CONTRACT_LIST, GET_PAYMENT_LIST, IMPORITEM, OBTAIN_CONTRACT_TYPE, statusArray, UPLOAD_ATTACHMENTS } from './api'
  8. import { GET_CONTRACT_TEMPLATE, ADD_CONTRACT } from './api'
  9. import { UploadRequestOptions } from "element-plus";
  10. import AddEditor from "./component/addEditor.vue";
  11. const globalPopup = inject<GlobalPopup>('globalPopup')
  12. const filterContractForm = reactive({
  13. name: '',
  14. number: '',
  15. typeName: '', // 合同类型
  16. status: '',
  17. startDate: '',
  18. endDate: '',
  19. paymentStartDate: '',
  20. paymentEndDate: '',
  21. })
  22. const fixedData = reactive({
  23. typeOfContractList: [] as optionType[],
  24. stateList: statusArray as optionType[],
  25. })
  26. const allLoading = reactive({
  27. exportLoading: false,
  28. contractTableLading: false,
  29. addEditorSaveLoading: false,
  30. importLoading: false
  31. })
  32. const allVisable = reactive({
  33. addEditorVisable: false,
  34. rejectDataVisable: false,
  35. importVisible: false
  36. })
  37. const allDynamicText = reactive({
  38. addEditorText: '新增合同',
  39. importText: '合同管理导入模板.xlsx'
  40. })
  41. const paging = reactive({
  42. pageIndex: 1,
  43. pageSize: 20,
  44. pageTotal: 0
  45. })
  46. const contractTableList = ref<any>([]);
  47. const addEditorVisableForm = reactive({
  48. paymentPlan: [],
  49. enclosure: [],
  50. enclosureDetele: []
  51. })
  52. const editForm = ref<any>({})
  53. const generateForm = ref<any>(null) // 模板
  54. const addEditorRef = ref<any>(null)
  55. const generateFormKey = ref<number>(1)
  56. const contractTemplate = ref<any>({
  57. list: [] as any[],
  58. config: {}
  59. })
  60. const rejectData = reactive({
  61. id: '',
  62. status: 2,
  63. msg: '',
  64. })
  65. onMounted(() => {
  66. getObtainContractType()
  67. getTemplateConfig()
  68. getContractTableList()
  69. })
  70. function contractRejection(row: any, status: number) {
  71. rejectData.id = row.id
  72. rejectData.status = status
  73. rejectData.msg = ''
  74. allVisable.rejectDataVisable = true
  75. }
  76. function deleteContract(row: any) {
  77. confirmAction('确定要删除该合同吗?', '合同删除').then(() => {
  78. post(CONTRACT_DELETION, { id: row.id }).then(() => {
  79. globalPopup?.showSuccess('删除成功')
  80. getContractTableList()
  81. })
  82. })
  83. }
  84. function contractApproved(row: any, status: number) {
  85. confirmAction('确认审核通过吗?,通过后合同基本信息无法修改', '合同通过').then(() => {
  86. contractOperation(row, status)
  87. })
  88. }
  89. function rejectTheContract() {
  90. confirmAction('确认驳回吗?,驳回后合同基本信息无法修改', '合同驳回').then(() => {
  91. contractOperation(rejectData, 2)
  92. })
  93. }
  94. async function importBusiness(param: UploadRequestOptions) {
  95. allLoading.importLoading = true
  96. const formData = new FormData();
  97. formData.append('file', param.file)
  98. formData.append('userId', sessionStorage.getItem('token') || '')
  99. const res = await uploadFile(IMPORITEM, formData).finally(() => {
  100. allLoading.importLoading = false
  101. })
  102. if (res.code == 'ok') {
  103. globalPopup?.showSuccess('导入成功' || '')
  104. getContractTableList()
  105. return
  106. }
  107. globalPopup?.showError(res.msg || '')
  108. }
  109. function exportContract() {
  110. allLoading.exportLoading = true
  111. post(EXPORT_CONTRACT, {}).then((res) => {
  112. downloadFile('合同导出.xlsx', res.data)
  113. }).finally(() => {
  114. allLoading.exportLoading = false
  115. })
  116. }
  117. async function addEditor(row?: any) {
  118. allLoading.addEditorSaveLoading = false
  119. editForm.value = row ? setEditForm(row) : {}
  120. allDynamicText.addEditorText = row ? '编辑合同' : '新增合同'
  121. contractTemplate.value.list = setTemplateDataDisable(contractTemplate.value.list, [...getTemplateKey(contractTemplate.value.list)], row?.status == 0)
  122. generateFormKey.value++
  123. setAddEditorVisableFormData(row)
  124. if (row) {
  125. const { data = [] } = await post(GET_PAYMENT_LIST, { contractId: row.id })
  126. addEditorVisableForm.paymentPlan = data.map((item: any) => {
  127. return {
  128. isPayed: item.isPayed, amount: item.amount, payDate: item.payDate, id: item.id
  129. }
  130. })
  131. }
  132. allVisable.addEditorVisable = true
  133. }
  134. function setEditForm(row: any) {
  135. const { id } = row
  136. const filedList = getTemplateKey(contractTemplate.value.list)
  137. const formFiled: any = {}
  138. filedList.forEach((item: any) => {
  139. formFiled[item] = row[item]
  140. })
  141. return {
  142. ...formFiled, id
  143. }
  144. }
  145. function setAddEditorVisableFormData(row: any) {
  146. addEditorVisableForm.enclosureDetele = []
  147. if (!row) {
  148. addEditorVisableForm.paymentPlan = []
  149. addEditorVisableForm.enclosure = []
  150. }
  151. if (row) {
  152. const { files = [] } = row
  153. addEditorVisableForm.enclosure = files.map((item: any) => {
  154. return {
  155. name: item.documentName,
  156. id: item.id,
  157. url: item.url
  158. }
  159. })
  160. }
  161. }
  162. function resetFiltering() {
  163. let newResetForm = resetFromValue(filterContractForm)
  164. Object.assign(filterContractForm, newResetForm)
  165. }
  166. function contractOperation(row: any, status: number) {
  167. let formVal = { status, id: row.id }
  168. if (status == 2) {
  169. formVal = row
  170. }
  171. post(CONTRACT_OPERATION, { ...formVal }).then(() => {
  172. globalPopup?.showSuccess("操作成功")
  173. if (status == 2) {
  174. allVisable.rejectDataVisable = false
  175. }
  176. getContractTableList()
  177. })
  178. }
  179. function getContractTableList() {
  180. allLoading.contractTableLading = true
  181. const formVal = getFromValue({ ...filterContractForm, pageIndex: paging.pageIndex, pageSize: paging.pageSize })
  182. post(GET_CONTRACT_LIST, { ...formVal }).then((res) => {
  183. // 赋值列表
  184. const { total = 0, data = [] } = res.data
  185. paging.pageTotal = total
  186. contractTableList.value = data
  187. }).finally(() => {
  188. allLoading.contractTableLading = false
  189. })
  190. }
  191. async function addEditorSave() {
  192. const url = editForm.value.id ? EDIT_CONTRACT : ADD_CONTRACT
  193. const data = await generateForm.value.getData()
  194. const { paymentPlan = [], enclosure = [], enclosureDetele = [] } = addEditorRef.value.getAddEditorData()
  195. let newPaymentPlan = paymentPlan
  196. if (editForm.value.id) {
  197. newPaymentPlan = paymentPlan.map((item: any) => { return { ...item, contractId: editForm.value.id } })
  198. }
  199. const formVal = getFromValue({
  200. ...editForm.value,
  201. ...data,
  202. startDate: data.startDate ? formatDate(new Date(data.startDate)) : '',
  203. endDate: data.endDate ? formatDate(new Date(data.endDate)) : '',
  204. paymentListStr: JSON.stringify(newPaymentPlan)
  205. })
  206. let totalAmount = 0
  207. // 判断
  208. for(let i in paymentPlan) {
  209. if(!paymentPlan[i].payDate) {
  210. globalPopup?.showWarning('回款日期不能为空')
  211. return
  212. }
  213. if(!paymentPlan[i].amount || paymentPlan[i].amount == 0) {
  214. globalPopup?.showWarning('回款金额不能为空和0')
  215. return
  216. }
  217. totalAmount += +paymentPlan[i].amount || 0
  218. }
  219. if(totalAmount > +(formVal.amounts || 0)) {
  220. globalPopup?.showWarning('总回款金额不得大于合同金额')
  221. return
  222. }
  223. allLoading.addEditorSaveLoading = true
  224. post(url, { ...formVal }).then((res) => {
  225. if (enclosure.length > 0) {
  226. fileUpload(String(res.data || editForm.value.id), enclosure)
  227. }
  228. if (enclosureDetele.length > 0) {
  229. fileUploadDetele(String(res.data || editForm.value.id), enclosureDetele)
  230. }
  231. if(enclosure.length == 0) {
  232. allLoading.addEditorSaveLoading = false
  233. globalPopup?.showSuccess('添加成功')
  234. allVisable.addEditorVisable = false
  235. getContractTableList()
  236. }
  237. }).catch(() => {
  238. allVisable.addEditorVisable = false
  239. })
  240. }
  241. function fileUpload(id: string, enclosure = []) {
  242. const formData = new FormData()
  243. formData.append('ContractId', id);
  244. enclosure.forEach((item: any) => {
  245. if (item.needUpload) {
  246. formData.append('file', item.file);
  247. }
  248. });
  249. post(UPLOAD_ATTACHMENTS, formData).then(re => {
  250. globalPopup?.showSuccess(re.msg)
  251. getContractTableList()
  252. }).finally(() => {
  253. allVisable.addEditorVisable = false
  254. })
  255. }
  256. function fileUploadDetele(id: string, enclosureDetele = []) {
  257. post(DELETE_CONTRACT_FILE, { contractId: id, fileIds: enclosureDetele.join(',') })
  258. }
  259. function handleSizeChange(val: number) {
  260. paging.pageIndex = 1
  261. paging.pageSize = val
  262. getContractTableList()
  263. }
  264. function handleCurrentChange(val: number) {
  265. paging.pageIndex = val
  266. getContractTableList()
  267. }
  268. function getTemplateConfig() {
  269. get(GET_CONTRACT_TEMPLATE).then(res => {
  270. const datas = JSON.parse(res.data[0] && res.data[0].config)
  271. contractTemplate.value = datas
  272. })
  273. }
  274. function getObtainContractType() {
  275. get(OBTAIN_CONTRACT_TYPE).then(res => {
  276. const list = res.data || []
  277. fixedData.typeOfContractList = list.map((item: any) => {
  278. return {
  279. label: item.name,
  280. value: item.id
  281. }
  282. })
  283. })
  284. }
  285. </script>
  286. <template>
  287. <div class="h-full flex">
  288. <div class="p-5 w-80 pr-0">
  289. <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
  290. <div class="flex-1 p-3 overflow-y-auto">
  291. <el-form :model="filterContractForm" label-width="100px" style="max-width: 600px">
  292. <el-form-item label="合同编号">
  293. <el-input v-model="filterContractForm.number" clearable placeholder="请输入"></el-input>
  294. </el-form-item>
  295. <el-form-item label="合同类型">
  296. <el-select v-model="filterContractForm.typeName" placeholder="请选择" clearable filterable>
  297. <el-option v-for="item in fixedData.typeOfContractList" :key="item.value" :label="item.label"
  298. :value="item.value" />
  299. </el-select>
  300. </el-form-item>
  301. <el-form-item label="状态">
  302. <el-select v-model="filterContractForm.status" placeholder="请选择" clearable filterable>
  303. <el-option v-for="item in fixedData.stateList" :key="item.value" :label="item.label"
  304. :value="item.value" />
  305. </el-select>
  306. </el-form-item>
  307. <el-form-item label="创建时间">
  308. <el-date-picker v-model="filterContractForm.startDate" type="date" placeholder="请选择" :clearable="false"
  309. format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
  310. </el-form-item>
  311. <el-form-item label="">
  312. <el-date-picker v-model="filterContractForm.endDate" type="date" placeholder="请选择" :clearable="false"
  313. format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
  314. </el-form-item>
  315. <el-form-item label="下笔回款日期">
  316. <el-date-picker v-model="filterContractForm.paymentStartDate" type="date" placeholder="请选择"
  317. :clearable="false" format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
  318. </el-form-item>
  319. <el-form-item label="">
  320. <el-date-picker v-model="filterContractForm.paymentEndDate" type="date" placeholder="请选择"
  321. :clearable="false" format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
  322. </el-form-item>
  323. </el-form>
  324. </div>
  325. <div class="w-full flex p-3 shadow-[0_-3px_5px_0px_rgba(0,0,0,0.2)]">
  326. <El-button class="w-full" @click="resetFiltering()">重置</El-Button>
  327. <El-button type="primary" class="w-full" :loading="allLoading.contractTableLading"
  328. @click="getContractTableList()">搜索</El-Button>
  329. </div>
  330. </div>
  331. </div>
  332. <div class="flex-1 p-5 overflow-auto">
  333. <div class="bg-white w-full h-full p-3 shadow-md rounded-md flex flex-col">
  334. <div class="flex justify-end pb-3">
  335. <el-button type="primary" v-permission="['contractAdd']" @click="addEditor()">新增合同</el-button>
  336. <el-button type="primary" v-permission="['contractImport']" @click="allVisable.importVisible = true">导入</el-button>
  337. <el-button type="primary" v-permission="['contractExport']" :loading="allLoading.exportLoading"
  338. @click="exportContract()">导出</el-button>
  339. </div>
  340. <div class="flex-1 w-full overflow-hidden">
  341. <el-table ref="contractTableRef" :show-overflow-tooltip="tableShowOverflowTooltip" :data="contractTableList"
  342. border v-loading="allLoading.contractTableLading" style="width: 100%;height: 100%;">
  343. <el-table-column prop="number" label="合同编号" width="180"></el-table-column>
  344. <el-table-column prop="name" label="合同名称" width="180"></el-table-column>
  345. <el-table-column prop="amounts" label="合同金额" width="180">
  346. <template #default="scope">
  347. ¥ {{ scope.row.amounts ? scope.row.amounts.toFixed(2) : '0.00' }}
  348. </template>
  349. </el-table-column>
  350. <el-table-column prop="payment" label="已回款金额" width="180">
  351. <template #default="scope">
  352. ¥ {{ scope.row.payment ? scope.row.payment.toFixed(2) : '0.00' }}
  353. </template>
  354. </el-table-column>
  355. <el-table-column prop="payment" label="已回款进度" width="180">
  356. <template #default="scope">
  357. {{ scope.row.payment ? (100 * scope.row.payment / scope.row.amounts).toFixed(1) + '%' : '0%' }}
  358. </template>
  359. </el-table-column>
  360. <el-table-column prop="nextPaymentDate" label="下笔回款日期" width="180">
  361. <template #default="scope">
  362. {{ scope.row.nextPaymentDate ? scope.row.nextPaymentDate : '-' }}
  363. </template>
  364. </el-table-column>
  365. <el-table-column prop="nextPaymentAmount" label="下笔回款金额" width="180">
  366. <template #default="scope">
  367. {{ scope.row.nextPaymentAmount ? '¥' + scope.row.nextPaymentAmount.toFixed(2) : '-' }}
  368. </template>
  369. </el-table-column>
  370. <el-table-column prop="typeName" label="合同类型" width="180"></el-table-column>
  371. <el-table-column prop="name" label="状态" width="180">
  372. <template #default="scope">
  373. <span :style="fixedData.stateList[scope.row.status].color">{{
  374. fixedData.stateList[scope.row.status].label }}</span>
  375. </template>
  376. </el-table-column>
  377. <el-table-column prop="indate" label="创建时间" width="180"></el-table-column>
  378. <el-table-column label="操作" fixed="right" width="200" v-permission="['contractAdd', 'auditContractData']">
  379. <template #default="scope">
  380. <el-button link type="primary" size="large" v-permission="['contractAdd']"
  381. @click="addEditor(scope.row)">编辑</el-button>
  382. <el-button link type="success" size="large" v-permission="['auditContractData']"
  383. v-if="(scope.row.status == 1 || scope.row.status == 3)"
  384. @click="contractApproved(scope.row, 0)">通过</el-button>
  385. <el-button link type="danger" size="large" v-permission="['auditContractData']"
  386. v-if="(scope.row.status == 1 || scope.row.status == 3)"
  387. @click="contractRejection(scope.row, 2)">驳回</el-button>
  388. <el-button link type="danger" size="large" v-permission="['contractAdd']"
  389. @click="deleteContract(scope.row)">删除</el-button>
  390. </template>
  391. </el-table-column>
  392. </el-table>
  393. </div>
  394. <div class="flex justify-end pt-3">
  395. <el-pagination layout="total, prev, pager, next, sizes" v-model:current-page="paging.pageIndex"
  396. v-model:page-size="paging.pageSize" :page-sizes="[10, 20, 30, 50, 100]" @size-change="handleSizeChange"
  397. @current-change="handleCurrentChange" :total="paging.pageTotal" />
  398. </div>
  399. </div>
  400. </div>
  401. <!-- 弹窗 -->
  402. <el-dialog v-model="allVisable.addEditorVisable" width="1000" :show-close="false" top="10vh">
  403. <template #header="{ close, titleId, titleClass }">
  404. <div class="flex justify-between items-center border-b pb-3 dialog-header">
  405. <h4 :id="titleId">{{ allDynamicText.addEditorText }}</h4>
  406. <div>
  407. <el-button type="primary" :loading="allLoading.addEditorSaveLoading" @click="addEditorSave">保存</el-button>
  408. <el-button @click="allVisable.addEditorVisable = false">取消</el-button>
  409. </div>
  410. </div>
  411. </template>
  412. <div class="h-[60vh] overflow-y-auto scroll-bar pt-3">
  413. <div class="ml-4 mr-4">
  414. <GenerateForm ref="generateForm" :data="contractTemplate" :value="editForm" :key="generateFormKey" />
  415. <AddEditor ref="addEditorRef" :payment-plan="addEditorVisableForm.paymentPlan"
  416. :enclosure="addEditorVisableForm.enclosure" :enclosure-detele="addEditorVisableForm.enclosureDetele">
  417. </AddEditor>
  418. </div>
  419. </div>
  420. </el-dialog>
  421. <el-dialog v-model="allVisable.rejectDataVisable" width="1000" :show-close="false" top="10vh">
  422. <template #header="{ close, titleId, titleClass }">
  423. <div class="flex justify-between items-center border-b pb-3 dialog-header">
  424. <h4 :id="titleId">驳回合同</h4>
  425. <div>
  426. <el-button type="primary" @click="rejectTheContract">驳回</el-button>
  427. <el-button @click="allVisable.rejectDataVisable = false">取消</el-button>
  428. </div>
  429. </div>
  430. </template>
  431. <div class="overflow-y-auto scroll-bar pt-3">
  432. <div class="ml-4 mr-4">
  433. <el-input v-model="rejectData.msg" style="width: 100%" :autosize="{ minRows: 6, maxRows: 6 }" type="textarea"
  434. placeholder="请输入驳回原因" />
  435. </div>
  436. </div>
  437. </el-dialog>
  438. <!-- 导入 -->
  439. <el-dialog v-model="allVisable.importVisible" width="680" :show-close="false" top="10vh">
  440. <template #header="{ close, titleId, titleClass }">
  441. <div class="flex justify-between items-center border-b pb-3 dialog-header">
  442. <h4 :id="titleId">导入合同</h4>
  443. <div class="flex">
  444. <el-upload class="upload-demo mr-3" :limit="1" :show-file-list="false" accept=".xlsx"
  445. :http-request="importBusiness">
  446. <el-button type="primary" :loading="allLoading.importLoading">导入</el-button>
  447. </el-upload>
  448. <el-button @click="allVisable.importVisible = false">取消</el-button>
  449. </div>
  450. </div>
  451. </template>
  452. <div class="p-8">
  453. <div class="ml-4 mr-4">
  454. <div class="flex items-center">1、点击下载 <el-link type="primary"
  455. @click="downloadTemplate('Contract', allDynamicText.importText)">{{ allDynamicText.importText }}</el-link></div>
  456. <div class="mt-4">2、填写合同名称必填</div>
  457. </div>
  458. </div>
  459. </el-dialog>
  460. </div>
  461. </template>
  462. <style lang="scss" scoped></style>