index.vue 19 KB

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