index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. <template>
  2. <div class="oa-sys-list-view">
  3. <div class="searchBox">
  4. <div class="form">
  5. <span class="formSpan">项目名称:</span>
  6. <el-input v-model="queryParams.xmmc" placeholder="请输入项目名称" style="width: 210px" />
  7. </div>
  8. <div class="form">
  9. <span class="formSpan">项目部门:</span>
  10. <DeptTree v-model="queryParams['xmbmId']" placeholder="请选择部门" />
  11. </div>
  12. <div class="form">
  13. <span class="formSpan">跟踪人员:</span>
  14. <UserOrgTree v-model="queryParams['gzryId']" placeholder="请选择跟踪人员" />
  15. </div>
  16. <div class="form">
  17. <span class="formSpan">状态:</span>
  18. <el-select v-model="queryParams.zt" placeholder="请选择状态">
  19. <el-option
  20. v-for="(item, index) in getDictOptions(DICT_TYPE.TRACKING_PROJECT_STATE)"
  21. :key="index"
  22. :label="item.label"
  23. :value="item.value"
  24. />
  25. </el-select>
  26. </div>
  27. <div class="form">
  28. <span class="formSpan">区域:</span>
  29. <DistrictTree
  30. class="form-item-disable-style"
  31. placeholder="请选择区域"
  32. v-model="queryParams.xzqdm"
  33. style="width: 100%"
  34. />
  35. </div>
  36. <div class="form">
  37. <span class="formSpan">所属板块:</span>
  38. <el-select v-model="queryParams.ssbk" placeholder="请选择所属板块" clearable>
  39. <el-option
  40. v-for="dict in getDictOptions(DICT_TYPE.PROJECT_SECTOR)"
  41. :key="dict.value"
  42. :label="dict.label"
  43. :value="dict.value"
  44. />
  45. </el-select>
  46. </div>
  47. <div class="from">
  48. <div class="btnBox">
  49. <el-button type="primary" style="background: #3485ff" @click="searchHandle">
  50. <img src="@/assets/imgs/OA/search.png" class="mr-8px" alt="" />
  51. 查询</el-button
  52. >
  53. <el-button type="primary" @click="addOrEditHandle()">
  54. <img src="@/assets/imgs/OA/open.png" class="mr-8px" alt="" />
  55. 新增</el-button
  56. >
  57. </div>
  58. </div>
  59. </div>
  60. <div class="infoBox">
  61. <ul>
  62. <li v-for="(item, index) in infoList" :key="index" class="mr-40px">
  63. <img class="mr-8px" :src="getAssetURL(item.icon)" alt="" />
  64. <p>{{ item.name }}:</p>
  65. <h4 class="font-size-18px">{{ item.num.toFixed(0) }}{{ item.unit }}</h4>
  66. </li>
  67. </ul>
  68. </div>
  69. <div class="tableBox">
  70. <div class="table" ref="tableRef">
  71. <el-table
  72. stripe
  73. :data="tableData"
  74. style="width: 100%; height: 100%"
  75. highlight-current-row
  76. :style="{ height: tableHeight + 'px' }"
  77. :header-cell-style="{
  78. background: '#E5F0FB',
  79. color: '#233755',
  80. height: '50px'
  81. }"
  82. >
  83. <el-table-column label="序号" width="60" align="center">
  84. <template #default="scope">{{ scope.$index + 1 }}</template>
  85. </el-table-column>
  86. <el-table-column prop="zt" label="状态" width="80">
  87. <template #default="scope">
  88. {{ getDictLabel(DICT_TYPE.TRACKING_PROJECT_STATE, scope.row.zt) }}
  89. </template>
  90. </el-table-column>
  91. <el-table-column prop="xzqmc" label="区域" width="120">
  92. <template #default="scope">
  93. {{ formatDistrict(scope.row.xzqmc)[0] }}
  94. </template>
  95. </el-table-column>
  96. <el-table-column prop="xzqmc" label="县市区" width="100">
  97. <template #default="scope">
  98. {{ formatDistrict(scope.row.xzqmc)[1] }}
  99. </template>
  100. </el-table-column>
  101. <el-table-column
  102. :show-overflow-tooltip="true"
  103. prop="xmmc"
  104. label="项目名称"
  105. :min-width="320"
  106. />
  107. <el-table-column prop="zt" label="所属板块" width="120">
  108. <template #default="scope">
  109. {{ getDictLabel(DICT_TYPE.PROJECT_SECTOR, scope.row.ssbk) }}
  110. </template>
  111. </el-table-column>
  112. <el-table-column prop="gzry" label="跟踪人员" width="120" />
  113. <el-table-column prop="gzry" label="区域总监" width="120" />
  114. <el-table-column prop="sfjc" label="是否进场" width="120">
  115. <template #default="scope">
  116. {{ scope.row.sfjc == '0' ? '是' : '否' }}
  117. </template>
  118. </el-table-column>
  119. <el-table-column prop="ygje" label="预估金额(元)" width="140" align="center">
  120. <template #default="scope">
  121. {{ (scope.row.ygje ?? 0).toFixed(2) }}
  122. </template>
  123. </el-table-column>
  124. <el-table-column prop="zbje" label="中标金额(元)" width="140" align="center">
  125. <template #default="scope">
  126. {{ (scope.row.zbje ?? 0).toFixed(2) }}
  127. </template>
  128. </el-table-column>
  129. <el-table-column prop="jf" label="甲方单位" width="220" :show-overflow-tooltip="true" />
  130. <el-table-column
  131. prop="jfdjr"
  132. label="甲方对接人"
  133. width="120"
  134. :show-overflow-tooltip="true"
  135. />
  136. <el-table-column
  137. prop="xmbm"
  138. label="项目归属部门"
  139. width="160"
  140. :show-overflow-tooltip="true"
  141. />
  142. <el-table-column prop="latestModifyTime" label="更新时间" width="180">
  143. <template #default="scope">
  144. {{ formatDate(scope.row.latestModifyTime) }}
  145. </template>
  146. </el-table-column>
  147. <el-table-column prop="xmjl" label="开关" width="100">
  148. <template #default="scope">
  149. <el-switch
  150. v-model="scope.row.status"
  151. :active-value="0"
  152. @change="switchChange(scope.row)"
  153. :inactive-value="1"
  154. />
  155. </template>
  156. </el-table-column>
  157. <el-table-column label="操作" width="80" fixed="right">
  158. <template #default="scope">
  159. <div class="operateBtn" @click="addOrEditHandle(scope.row)">
  160. <span>编辑</span>
  161. </div>
  162. </template>
  163. </el-table-column>
  164. </el-table>
  165. </div>
  166. <div class="pageBox">
  167. <el-pagination
  168. v-model:current-page="queryParams.pageNo"
  169. :page-size="queryParams.pageSize"
  170. background
  171. layout="sizes, total, prev, pager, next, jumper"
  172. :total="total"
  173. :page-sizes="[15, 20, 30, 40]"
  174. @size-change="handleSizeChange"
  175. @current-change="handleCurrentChange"
  176. />
  177. </div>
  178. </div>
  179. <div class="editorCover" v-if="dialogVisible" :title="dialogTitle" align-center>
  180. <div class="cover-header">
  181. <h2>{{ dialogTitle }}</h2>
  182. </div>
  183. <div class="cover-content">
  184. <el-form
  185. ref="ruleFormRef"
  186. :rules="rules"
  187. :model="formData"
  188. style="width: 100%"
  189. label-width="auto"
  190. class="demo-ruleForm"
  191. status-icon
  192. >
  193. <el-form-item label="项目名称" prop="xmmc">
  194. <el-input v-model="formData.xmmc" />
  195. </el-form-item>
  196. <el-row :gutter="100">
  197. <el-col :span="12">
  198. <el-form-item label="所属板块" prop="ssbk">
  199. <el-select
  200. v-model="formData.ssbk"
  201. placeholder="请选择所属板块"
  202. clearable
  203. style="width: 100%"
  204. >
  205. <el-option
  206. v-for="dict in getDictOptions(DICT_TYPE.PROJECT_SECTOR)"
  207. :key="dict.value"
  208. :label="dict.label"
  209. :value="dict.value"
  210. />
  211. </el-select>
  212. </el-form-item>
  213. </el-col>
  214. <el-col :span="12">
  215. <el-form-item label="所属部门" prop="xmbmId">
  216. <DeptTree
  217. v-model="formData.xmbmId"
  218. @node-click="(item) => treeNodeClick(item, 'xmbm')"
  219. placeholder="请选择部门"
  220. />
  221. </el-form-item>
  222. </el-col>
  223. </el-row>
  224. <el-row :gutter="100">
  225. <el-col :span="12">
  226. <el-form-item label="跟踪人员" prop="gzryId">
  227. <UserOrgTree
  228. v-model="formData.gzryId"
  229. placeholder="请选择跟踪人员"
  230. @node-click="(item) => treeNodeClick(item, 'gzry')"
  231. />
  232. </el-form-item>
  233. </el-col>
  234. <el-col :span="12">
  235. <el-form-item label="区域总监" prop="qyzjId">
  236. <UserOrgTree
  237. v-model="formData.qyzjId"
  238. placeholder="请选择区域总监"
  239. @node-click="(item) => treeNodeClick(item, 'qyzj')"
  240. />
  241. </el-form-item>
  242. </el-col>
  243. </el-row>
  244. <el-row :gutter="100">
  245. <el-col :span="12">
  246. <el-form-item label="甲方单位" prop="jfId">
  247. <TreeSelectV2
  248. :data="contractTree"
  249. :props="{ label: 'name', value: 'id' }"
  250. v-model="formData.jfId"
  251. :filter-method="filterNodeMethod"
  252. @node-click="(item) => treeNodeClick(item, 'jf')"
  253. style="width: 100%"
  254. />
  255. </el-form-item>
  256. </el-col>
  257. <el-col :span="12">
  258. <el-form-item label="甲方科室" prop="jfks">
  259. <el-input v-model="formData.jfks" />
  260. </el-form-item>
  261. </el-col>
  262. </el-row>
  263. <el-row :gutter="100">
  264. <el-col :span="12">
  265. <el-form-item label="甲方对接人" prop="jfdjr">
  266. <el-input v-model="formData.jfdjr" />
  267. </el-form-item>
  268. </el-col>
  269. <el-col :span="12">
  270. <el-form-item label="是否进场" prop="sfjc">
  271. <el-select v-model="formData.sfjc" style="width: 100%">
  272. <el-option label="是" :value="0" />
  273. <el-option label="否" :value="1" />
  274. </el-select>
  275. </el-form-item>
  276. </el-col>
  277. </el-row>
  278. <el-row :gutter="100">
  279. <el-col :span="12">
  280. <el-form-item label="预估金额(元)" prop="ygje">
  281. <el-input v-model="formData.ygje" />
  282. </el-form-item>
  283. </el-col>
  284. <el-col :span="12">
  285. <el-form-item label="中标金额(元)" prop="zbje">
  286. <el-input v-model="formData.zbje" />
  287. </el-form-item>
  288. </el-col>
  289. </el-row>
  290. <el-row :gutter="100">
  291. <el-col :span="12">
  292. <el-form-item label="行政区" prop="xzqdm">
  293. <DistrictTree
  294. class="form-item-disable-style"
  295. placeholder="请选择行政区"
  296. v-model="formData.xzqdm"
  297. @node-click="(item, labels) => treeNodeClick(labels, 'xzqmc')"
  298. style="width: 100%"
  299. />
  300. </el-form-item>
  301. </el-col>
  302. <el-col :span="12">
  303. <el-form-item label="谁可见" prop="visibleUserIds">
  304. <UserOrgTree
  305. v-model="visibleUserIds"
  306. :multiple="true"
  307. placeholder="请选择跟谁可见"
  308. @change="visibleUserChange"
  309. />
  310. </el-form-item>
  311. </el-col>
  312. </el-row>
  313. <el-row :gutter="100">
  314. <el-col :span="12">
  315. <el-form-item label="状态" prop="zt">
  316. <el-select v-model="formData.zt" placeholder="请选择状态" style="width: 100%">
  317. <el-option
  318. v-for="(item, index) in getDictOptions(DICT_TYPE.TRACKING_PROJECT_STATE)"
  319. :key="index"
  320. :label="item.label"
  321. :value="item.value"
  322. />
  323. </el-select>
  324. </el-form-item>
  325. </el-col>
  326. </el-row>
  327. <el-form-item label="备注" prop="desc">
  328. <el-input v-model="formData.bz" rows="5" type="textarea" />
  329. </el-form-item>
  330. </el-form>
  331. </div>
  332. <div class="cover-footer">
  333. <el-button @click="dialogVisible = false">关闭</el-button>
  334. <el-button type="primary" @click="saveProjectTracking(ruleFormRef)"> 保存 </el-button>
  335. </div>
  336. </div>
  337. </div>
  338. </template>
  339. <script setup lang="ts">
  340. import type { FormInstance, FormRules } from 'element-plus'
  341. import { useQuery } from '@tanstack/vue-query'
  342. import { DICT_TYPE, getDictLabel, getDictOptions } from '@/utils/dict'
  343. import { IFormType, useMixins, infoList, user } from './common'
  344. import request from '@/config/axios'
  345. import { formatDate } from '@/utils/formatTime'
  346. import { filterNodeMethod } from '@/utils/tree'
  347. import { getAssetURL } from '@/utils/auth'
  348. import TreeSelectV2 from '@/components/TreeSelectV2/index.vue'
  349. import UserOrgTree from '@/views/OaSystem/components/UserOrgTree/index.vue'
  350. import DeptTree from '@/views/OaSystem/components/DeptTree/index.vue'
  351. import DistrictTree from '@/views/OaSystem/components/DistrictTree/index.vue'
  352. import { queryCustomerTree } from '@/service/contract'
  353. defineOptions({ name: 'ProjectTrack' })
  354. /***
  355. * 查询委托方数据
  356. * **/
  357. const { data: contractTree } = useQuery(['contractTree'], async () => await queryCustomerTree(), {
  358. staleTime: Infinity
  359. })
  360. const visibleUserIds = ref<string[]>([])
  361. const tableRef: any = ref(null)
  362. const tableHeight: any = ref(0)
  363. const { queryParams, formData, initFormData } = useMixins()
  364. const handleSizeChange = (size) => {
  365. queryParams.pageNo = 1
  366. queryParams.pageSize = size
  367. queryProjectTrackByPage()
  368. }
  369. const handleCurrentChange = (pageNo: number) => {
  370. queryParams.pageNo = pageNo
  371. queryProjectTrackByPage()
  372. }
  373. const formatDistrict = (xzqmc: string) => {
  374. if (!xzqmc) return ''
  375. const narr: string[] = []
  376. const arrs = xzqmc.split('-')
  377. if (arrs.length === 1) {
  378. narr.push(arrs[0], '省级')
  379. return narr
  380. }
  381. if (arrs.length === 2) {
  382. narr.push(`${arrs[0]}-${arrs[1]}`, '市级')
  383. }
  384. if (arrs.length === 3) {
  385. narr.push(`${arrs[0]}-${arrs[1]}`, arrs[2])
  386. }
  387. return narr
  388. }
  389. const tableData = ref<Array<any>>([])
  390. const total = ref<number>()
  391. const searchHandle: () => void = () => {
  392. queryParams.pageNo = 1
  393. queryProjectTrackingSummary()
  394. queryProjectTrackByPage()
  395. }
  396. const queryProjectTrackByPage = async (): Promise<void> => {
  397. const urlApi = `/projectTracking/page`
  398. const sendData = {
  399. ...queryParams
  400. }
  401. const result = await request.get({ url: urlApi, params: sendData }, '/business')
  402. tableData.value = result['list']
  403. total.value = result['total']
  404. }
  405. const queryProjectTrackByDetail = async (id: string): Promise<void> => {
  406. const urlApi = `/projectTracking/getById`
  407. const sendData = {
  408. id
  409. }
  410. return await request.get({ url: urlApi, params: sendData }, '/business')
  411. }
  412. queryProjectTrackByPage()
  413. /**
  414. * 项目金额、项目个数汇总
  415. */
  416. const queryProjectTrackingSummary = async (): Promise<void> => {
  417. const urlApi = `/projectTracking/summary`
  418. const sendData = {
  419. ...queryParams
  420. }
  421. const result = await request.get({ url: urlApi, params: sendData }, '/business')
  422. infoList[0]['num'] = result ? result.amount ?? 0 : 0
  423. infoList[1]['num'] = result ? result.nums ?? 0 : 0
  424. }
  425. queryProjectTrackingSummary()
  426. /**
  427. * 项目跟踪:新增、编辑
  428. */
  429. const dialogTitle = ref<string>('项目跟踪填报')
  430. const dialogVisible = ref<boolean>(false)
  431. const addOrEditHandle = (row?: IFormType) => {
  432. dialogVisible.value = true
  433. if (!row) {
  434. dialogTitle.value = '项目跟踪填报'
  435. initFormData()
  436. visibleUserIds.value = []
  437. } else {
  438. dialogTitle.value = '项目跟踪编辑'
  439. queryProjectTrackByDetail(row['id']).then((result) => {
  440. initFormData(result as any)
  441. visibleUserIds.value = result['visibleUserIds'].split(',')
  442. })
  443. }
  444. }
  445. const treeNodeClick = (item, type: string) => {
  446. if (!item) return
  447. if (type === 'xzqmc') {
  448. formData.value[type] = item.join('-')
  449. return
  450. }
  451. formData.value[type] = item['label'] || item['name']
  452. }
  453. const visibleUserChange = (item) => {
  454. formData.value['visibleUserIds'] = item.join(',')
  455. }
  456. const rules = reactive<FormRules<IFormType>>({
  457. xmmc: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
  458. ssbk: [{ required: true, message: '所属板块不能为空', trigger: 'change' }],
  459. xmbmId: [{ required: true, message: '所属部门不能为空', trigger: 'change' }],
  460. gzryId: [{ required: true, message: '跟踪人员不能为空', trigger: 'change' }],
  461. qyzjId: [{ required: true, message: '区域总监不能为空', trigger: 'change' }],
  462. jfId: [{ required: true, message: '甲方单位不能为空', trigger: 'blur' }],
  463. jfks: [{ required: true, message: '甲方科室不能为空', trigger: 'blur' }],
  464. jfdjr: [{ required: true, message: '甲方对接人不能为空', trigger: 'blur' }],
  465. sfjc: [{ required: true, message: '是否进场不能为空', trigger: 'change' }],
  466. ygje: [{ required: true, message: '预估金额不能为空', trigger: 'blur' }],
  467. zbje: [{ required: true, message: '中标金额不能为空', trigger: 'blur' }],
  468. xzqdm: [{ required: true, message: '行政区不能为空', trigger: 'change' }],
  469. visibleUserIds: [{ required: true, message: '谁可见不能为空', trigger: 'change' }],
  470. zt: [{ required: true, message: '状态不能为空', trigger: 'change' }]
  471. })
  472. const ruleFormRef = ref<FormInstance>()
  473. const saveProjectTracking = async (formEl: FormInstance | undefined): Promise<void> => {
  474. if (!formEl) return
  475. await formEl.validate(async (valid, fields) => {
  476. if (valid) {
  477. const urlApi = `/projectTracking/save`
  478. const sendData = {
  479. ...formData.value
  480. }
  481. // sendData['visibleUserIds'] = visibleUserIds.value.join(',')
  482. const result = await request.post({ url: urlApi, data: sendData }, '/business')
  483. if (result) {
  484. searchHandle()
  485. dialogVisible.value = false
  486. }
  487. } else {
  488. console.log('error submit!', fields)
  489. }
  490. })
  491. }
  492. const switchChange = (item) => {
  493. updateStatus({
  494. id: item['id'],
  495. status: item['status']
  496. })
  497. }
  498. const updateStatus = async (data: { id: string; status: string }): Promise<void> => {
  499. const urlApi = `/update-status`
  500. return await request.put({ url: urlApi, data: data }, '/business')
  501. }
  502. onMounted(() => {
  503. tableHeight.value = tableRef.value.clientHeight
  504. })
  505. </script>
  506. <style lang="scss" scoped>
  507. :deep(.el-radio) {
  508. margin-right: 10px;
  509. }
  510. .searchBox {
  511. display: flex;
  512. white-space: nowrap;
  513. .form {
  514. margin-right: 16px !important;
  515. }
  516. .formSpan {
  517. width: auto !important;
  518. }
  519. }
  520. .oa-sys-list-view {
  521. position: relative;
  522. .editorCover {
  523. position: absolute;
  524. left: 0px;
  525. right: 0px;
  526. top: 0px;
  527. bottom: 0px;
  528. z-index: 9;
  529. background: #fff;
  530. display: flex;
  531. flex-direction: column;
  532. border-radius: 20px;
  533. > div {
  534. &.cover-header {
  535. padding: 20px 30px;
  536. }
  537. &.cover-content {
  538. padding: 20px 100px;
  539. flex: 1px;
  540. flex-grow: 1;
  541. overflow: auto;
  542. }
  543. &.cover-footer {
  544. text-align: right;
  545. padding: 40px;
  546. }
  547. }
  548. }
  549. }
  550. </style>