edit.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. <!--
  2. * @Author: wanglj wanglijie@dashoo.cn
  3. * @Date: 2025-03-24 09:17:15
  4. * @LastEditors: wanglj wanglijie@dashoo.cn
  5. * @LastEditTime: 2025-04-14 09:30:11
  6. * @FilePath: \labsop_h5\src\view\instr\detail.vue
  7. * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  8. -->
  9. <template>
  10. <div class="entry-container">
  11. <van-form ref="formRef" required="auto" label-width="100">
  12. <h4>项目信息</h4>
  13. <van-cell-group>
  14. <van-field v-model="state.form.projectName" label="项目名称" readonly />
  15. <van-field v-model="state.form.projectSource" label="项目来源" readonly />
  16. <van-field v-model="state.form.projectIncharge" label="项目负责人" readonly />
  17. <van-field v-model="state.form.deptName" label="所属科室" readonly />
  18. <van-field v-model="state.form.subjName" label="费用科目" readonly />
  19. <van-field v-model="state.form.subSubjName" label="费用类型" readonly />
  20. <van-field v-model="showAmount" label="入账金额" readonly />
  21. <van-field v-model="showBalanceAmount" label="可用金额" readonly />
  22. <van-field v-model="state.form.purpose" label="事项" :rules="[{ required: true, message: '请输入事项' }]" />
  23. </van-cell-group>
  24. <h4>报销经费</h4>
  25. <van-cell-group>
  26. <van-field
  27. v-model="state.form.expendTime"
  28. label="支出日期"
  29. readonly
  30. is-link
  31. @click="openExpandTimePicker"
  32. :rules="[{ required: true, message: '请选择支出日期' }]"
  33. />
  34. <van-field v-model="state.form.handle" label="申请人" />
  35. <van-field v-model="state.form.expend" label="出纳" />
  36. <van-field v-model.number="state.form.amount" type="number" label="支出金额">
  37. <template #extra>元</template>
  38. </van-field>
  39. </van-cell-group>
  40. <h4>发票信息</h4>
  41. <van-cell-group v-for="(item, index) in state.form.invoice" :key="index" :class="{ mt10: index != 0 }">
  42. <van-field v-model="item.invoiceNo" label="发票号码" :rules="[{ required: true, message: '请输入发票号码' }]" />
  43. <van-field v-model.number="item.amount" type="number" label="金额" :rules="[{ required: true, message: '请输入金额' }]" />
  44. <van-field
  45. v-model="item.invoiceBy"
  46. label="开票日期"
  47. readonly
  48. is-link
  49. @click="openInvoiceByPicker(item, index)"
  50. :rules="[{ required: true, message: '请选择开票日期' }]"
  51. />
  52. <div class="file-card">
  53. <van-uploader v-model="item.fileList" :after-read="afterRead" preview-size="60" :preview-full-image="false" />
  54. </div>
  55. <van-cell v-if="index > 0" center>
  56. <template #default>
  57. <van-button class="w100" size="small" plain round type="danger" @click="deleteInvoice(index)">删除</van-button>
  58. </template>
  59. </van-cell>
  60. </van-cell-group>
  61. <van-button class="w100 mt10 mb10" size="small" plain round icon="plus" type="primary" @click="addInvoice"> 添加发票 </van-button>
  62. <h4>支付信息</h4>
  63. <van-cell-group v-for="(item, index) in state.form.payment" :key="index" :class="{ mt10: index != 0 }">
  64. <van-field v-model="item.payType" label="支付方式" readonly is-link @click="openPicker(item, index)">
  65. <template #input>
  66. <template v-if="item.payType == '10'">现金</template>
  67. <template v-else-if="item.payType == '20'">银行转账</template>
  68. <template v-else-if="item.payType == '30'">国库集中支付</template>
  69. </template>
  70. </van-field>
  71. <van-field v-model="item.receiver" label="收款人/单位" />
  72. <van-field v-model="item.receiverType" label="个人/单位类型" :rules="[{ required: true, message: '请输入个人/单位类型' }]">
  73. <template #input>
  74. <van-radio-group v-model="item.receiverType" direction="horizontal">
  75. <van-radio v-for="(item, index) in receiverTypeOptions" :key="index" :name="item.dictValue">{{ item.dictLabel }}</van-radio>
  76. </van-radio-group>
  77. </template>
  78. </van-field>
  79. <van-field v-model.number="item.amount" type="number" label="金额" :rules="[{ required: true, message: '请输入金额' }]" />
  80. <!-- <van-field v-model="item.purpose" label="用途" :rules="[{ required: true, message: '请输入用途' }]" />
  81. <van-field v-model="item.bankName" label="具体开户行" /> -->
  82. <van-cell v-if="index > 0" center>
  83. <template #default>
  84. <van-button class="w100" size="small" plain round type="danger" @click="deletePayment(index)">删除</van-button>
  85. </template>
  86. </van-cell>
  87. </van-cell-group>
  88. <van-button class="w100 mt10 mb10" size="small" plain round icon="plus" type="primary" @click="addPayment">添加支付信息</van-button>
  89. <h4>附件信息</h4>
  90. <div class="file-card">
  91. <van-uploader v-model="state.form.fileList" accept="image/*,.pdf" :after-read="afterRead" multiple preview-size="60" :preview-full-image="false" />
  92. </div>
  93. </van-form>
  94. <!-- 支出日期选择 -->
  95. <van-popup v-model:show="showExpandTimePicker" destroy-on-close position="bottom">
  96. <van-date-picker v-model="state.expendTime" title="选择日期" @confirm="onExpandTimeConfirm" @cancel="showExpandTimePicker = false" />
  97. </van-popup>
  98. <!-- 支付方式选择 -->
  99. <van-popup v-model:show="showPicker" destroy-on-close position="bottom">
  100. <van-picker :columns="columns" :model-value="state.pickerPayment" @confirm="onConfirm" @cancel="showPicker = false" />
  101. </van-popup>
  102. <!-- 开票日期选择 -->
  103. <van-popup v-model:show="showPickerInvoice" destroy-on-close position="bottom">
  104. <van-date-picker v-model="state.invoiceBy" title="选择日期" @confirm="onPaymentConfirm" @cancel="showPickerInvoice = false" />
  105. </van-popup>
  106. </div>
  107. <van-action-bar placeholder>
  108. <van-action-bar-icon icon="wap-home-o" text="首页" @click="onRouterPush('/home')" />
  109. <van-action-bar-icon icon="info-o" text="报销提醒" @click="onRouterPush('/fund/reimbursement-remind')" />
  110. <van-action-bar-button class="w100" type="primary" text="提交" @click="onClickButton" />
  111. </van-action-bar>
  112. </template>
  113. <script lang="ts" setup>
  114. import to from 'await-to-js'
  115. import { useRoute, useRouter } from 'vue-router'
  116. import { computed, onMounted, reactive, ref } from 'vue'
  117. import { formatDate } from '/@/utils/formatTime'
  118. import { showConfirmDialog, showNotify } from 'vant'
  119. import { useUserInfo } from '/@/stores/userInfo'
  120. import { storeToRefs } from 'pinia'
  121. import instAppoint from '/@/api/instr/instAppoint'
  122. import { useConfigApi } from '/@/api/system/config'
  123. import { useFundApi } from '/@/api/fund'
  124. import { useDictApi } from '/@/api/system/dict'
  125. import { getDictLabel, formatAmount, formatAmountYuan, formatInteger } from '/@/utils/other'
  126. import { useClaimApi } from '/@/api/fund/claim'
  127. import { useExpenseRemindApi } from '/@/api/fund/reimbursement-remind'
  128. import { handleUpload } from '/@/utils/upload'
  129. import { useExpenseApi } from '/@/api/base/expense'
  130. import { useFundCardApi } from '/@/api/fund/card'
  131. import { useBaseReimburseApi } from '/@/api/fund/reimburse'
  132. import { useLoginApi } from '/@/api/login'
  133. import { Local, Session } from '/@/utils/storage'
  134. const storesUseUserInfo = useUserInfo()
  135. const { userInfos, openId, unionId } = storeToRefs(storesUseUserInfo)
  136. const route = useRoute()
  137. const router = useRouter()
  138. const expenseRemindApi = useExpenseRemindApi()
  139. const fundApi = useFundApi()
  140. const loginApi = useLoginApi()
  141. const dictApi = useDictApi()
  142. const expenseApi = useExpenseApi()
  143. const configApi = useConfigApi()
  144. const fundCardApi = useFundCardApi()
  145. const baseReimburseApi = useBaseReimburseApi()
  146. const formRef = ref()
  147. const paymentReceivedTypeOptions = ref<RowDicDataType[]>([])
  148. const receiverTypeOptions = ref<RowDicDataType[]>([])
  149. const showPicker = ref(false)
  150. const columns = [
  151. {
  152. text: '现金',
  153. value: '10'
  154. },
  155. {
  156. text: '银行转账',
  157. value: '20'
  158. },
  159. {
  160. text: '国库集中支付',
  161. value: '30'
  162. }
  163. ]
  164. const showPickerInvoice = ref(false)
  165. const showExpandTimePicker = ref(false)
  166. const state = reactive({
  167. loading: false,
  168. isActiveService: false,
  169. showProject: false,
  170. shwoService: false,
  171. showExpenseCard: false,
  172. showAppointUser: false,
  173. isInstrHead: false,
  174. fundDetail: {} as any,
  175. detail: [] as any,
  176. form: {
  177. id: 0,
  178. orderNo: '',
  179. price: 0,
  180. buyerName: '',
  181. createdTime: '',
  182. status: '',
  183. fileList: [],
  184. amount: 0,
  185. noticeId: null,
  186. projectType: '',
  187. allotId: null,
  188. handle: '', //经办人
  189. projectId: 0, //项目Id
  190. projectIncharge: '', //项目负责人
  191. projectCode: '',
  192. projectName: '', //项目名称
  193. projectSource: '',
  194. deptName: '',
  195. startDate: '',
  196. endDate: '',
  197. purpose: '', //事项
  198. detail: [],
  199. invoice: [
  200. {
  201. fileList: [],
  202. fileName: '',
  203. fileUrl: '',
  204. invoiceNo: '',
  205. amount: null,
  206. invoiceBy: '',
  207. invoiceType: ''
  208. }
  209. ],
  210. payment: [
  211. {
  212. payType: '10',
  213. receiver: '',
  214. receiverType: '',
  215. amount: '',
  216. purpose: '',
  217. bankName: ''
  218. }
  219. ],
  220. deptId: null,
  221. subjCode: '',
  222. subjName: '',
  223. subSubjCode: '',
  224. subSubjName: '',
  225. expendTime: '',
  226. expend: ''
  227. },
  228. pickerPayment: [],
  229. pickerIndex: -1,
  230. invoiceBy: [],
  231. pickerPaymentIndex: -1,
  232. expendTime: []
  233. })
  234. const costSourceList = ref<RowDicDataType[]>([])
  235. const expenseSubjectList = ref<RowDicDataType[]>([])
  236. const subList = ref([])
  237. const subChildrenList = ref<any>([])
  238. const sysParamsConfig = ref()
  239. const showAmount = ref(0) //根据项目和费用类型查询入账和剩余入账
  240. const showBalanceAmount = ref(0) //根据项目和费用类型查询入账和剩余入账
  241. const getDict = async () => {
  242. await Promise.all([dictApi.getDictDataByType('PaymentReceivedType'), fundApi.getAllFirstSubj()]).then(([type, parList]) => {
  243. paymentReceivedTypeOptions.value = type?.data?.values || []
  244. state.detail =
  245. parList?.data.map((item: any) => ({
  246. ...item,
  247. amount: 0
  248. })) || []
  249. })
  250. await Promise.all([
  251. dictApi.getDictDataByType('sci_cost_source'),
  252. expenseApi.GetListNoPage(),
  253. fundApi.getAllFirstSubj(),
  254. configApi.getEntityMapByKeys({ configKeys: ['sci_fund_expense_notice_business_code', 'sci_fund_expense_notice_material_code'] }), // 获取系统参数
  255. dictApi.getDictDataByType('sci_receiver_type')
  256. ]).then(([source, subj, sub, sysParams, recevied]) => {
  257. costSourceList.value = source.data?.values
  258. expenseSubjectList.value = subj?.data?.list
  259. subList.value = sub?.data
  260. sysParamsConfig.value = sysParams?.data
  261. // 费用科目
  262. const find = subList.value.find((item: any) => item.subjCode == sysParamsConfig.value.sci_fund_expense_notice_business_code)
  263. subChildrenList.value = find?.children
  264. state.form.subjCode = find?.subjCode
  265. state.form.subjName = find?.subjName
  266. // 二级费用科目
  267. const subFind = subChildrenList.value.find((item: any) => item.subjCode == sysParamsConfig.value.sci_fund_expense_notice_material_code)
  268. state.form.subSubjCode = subFind?.subjCode
  269. state.form.subSubjName = subFind?.subjName
  270. receiverTypeOptions.value = recevied?.data?.values || []
  271. })
  272. }
  273. // 获取入账信息
  274. const getAmountInfo = async () => {
  275. if (!state.form.projectId || !state.form.subjCode) return
  276. const params = {
  277. projectId: state.form.projectId,
  278. projectType: state.form.projectType,
  279. subjCode: state.form.subjCode
  280. }
  281. showAmount.value = 0
  282. showBalanceAmount.value = 0
  283. const [err, res]: ToResponse = await to(fundCardApi.getSubjAmount(params))
  284. if (err) return
  285. showAmount.value = res?.data?.amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
  286. showBalanceAmount.value = res?.data?.balanceAmount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
  287. }
  288. const getFundDetail = async (id: number) => {
  289. const [err, res]: ToResponse = await to(expenseRemindApi.getDetails({ id }))
  290. if (err) return
  291. const row = res.data
  292. state.form = {
  293. ...state.form,
  294. ...row
  295. }
  296. state.form.noticeId = row.id
  297. state.form.amount = row.price
  298. state.form.deptName = row.projectDeptName
  299. state.form.handle = userInfos.value.nickName
  300. }
  301. // 获取上传附件信息
  302. const getUploadFileList = async () => {
  303. if (!state.form.subjCode) {
  304. state.form.fileList = []
  305. return
  306. }
  307. const find = subList.value.find((item: any) => item.subjCode == state.form.subSubjCode)
  308. const subjId = find?.id
  309. const [err, res]: ToResponse = await to(baseReimburseApi.getList({ subjId: subjId }))
  310. if (err) return
  311. const arr = res?.data?.list || []
  312. state.form.fileList = arr.map((item: any) => {
  313. return {
  314. fileName: '',
  315. fileUrl: '',
  316. fileList: [],
  317. required: item.isRequired === '10',
  318. fileType: item.fileName,
  319. accept: item.fileType,
  320. fileTempUrl: item.fileTempUrl
  321. }
  322. })
  323. }
  324. const onRouterPush = (val: string, params?: any) => {
  325. router.push({
  326. path: val,
  327. query: { ...params }
  328. })
  329. }
  330. const afterRead = async (files: any) => {
  331. if (files.length) {
  332. for (const file of files) {
  333. file.status = 'uploading'
  334. const [err, res]: ToResponse = await to(handleUpload(file.file))
  335. if (err) {
  336. file.status = 'failed'
  337. return
  338. }
  339. file.status = 'success'
  340. file.url = res
  341. file.fileName = file.file.name
  342. file.fileUrl = res
  343. file.fileSize = file.file.size
  344. file.invoiceType = file.file.type
  345. }
  346. } else {
  347. const file = files
  348. file.status = 'uploading'
  349. const [err, res]: ToResponse = await to(handleUpload(file.file))
  350. if (err) {
  351. file.status = 'failed'
  352. return
  353. }
  354. file.status = 'success'
  355. file.url = res
  356. file.fileName = file.file.name
  357. file.fileUrl = res
  358. file.fileSize = file.file.size
  359. file.invoiceType = file.file.type
  360. }
  361. }
  362. const addInvoice = () => {
  363. state.form.invoice.push({
  364. fileList: [],
  365. fileName: '',
  366. fileUrl: '',
  367. invoiceNo: '',
  368. amount: null,
  369. invoiceBy: '',
  370. invoiceType: ''
  371. })
  372. }
  373. const deleteInvoice = (idx: number) => {
  374. showConfirmDialog({
  375. title: '提示',
  376. message: '确认删除?'
  377. })
  378. .then(() => {
  379. state.form.invoice.splice(idx, 1)
  380. })
  381. .catch(() => {
  382. // on cancel
  383. })
  384. }
  385. const addPayment = () => {
  386. state.form.payment.push({
  387. payType: '10',
  388. receiver: '',
  389. receiverType: '',
  390. amount: '',
  391. purpose: '',
  392. bankName: ''
  393. })
  394. }
  395. const deletePayment = (idx: number) => {
  396. showConfirmDialog({
  397. title: '提示',
  398. message: '确认删除?'
  399. })
  400. .then(() => {
  401. state.form.payment.splice(idx, 1)
  402. })
  403. .catch(() => {
  404. // on cancel
  405. })
  406. }
  407. const openExpandTimePicker = () => {
  408. const now = new Date()
  409. state.expendTime = [formatDate(now, 'YYYY'), formatDate(now, 'mm'), formatDate(now, 'dd')]
  410. showExpandTimePicker.value = true
  411. }
  412. const onExpandTimeConfirm = ({ selectedValues }) => {
  413. state.form.expendTime = selectedValues.join('-')
  414. showExpandTimePicker.value = false
  415. }
  416. const openInvoiceByPicker = (item: any, idx: number) => {
  417. const now = new Date()
  418. state.invoiceBy = item.invoiceBy ? item.invoiceBy.split('-') : [formatDate(now, 'YYYY'), formatDate(now, 'mm'), formatDate(now, 'dd')]
  419. showPickerInvoice.value = true
  420. state.pickerPaymentIndex = idx
  421. }
  422. const onPaymentConfirm = ({ selectedValues }) => {
  423. state.form.invoice[state.pickerPaymentIndex].invoiceBy = selectedValues.join('-')
  424. showPickerInvoice.value = false
  425. }
  426. const openPicker = (item: any, idx: number) => {
  427. state.pickerPayment = [item.payType]
  428. state.pickerIndex = idx
  429. showPicker.value = true
  430. }
  431. const onConfirm = ({ selectedValues }) => {
  432. state.form.payment[state.pickerIndex].payType = selectedValues[0]
  433. showPicker.value = false
  434. }
  435. const onClickButton = async () => {
  436. state.loading = true
  437. if (state.form.invoice.length == 0) {
  438. showNotify({
  439. type: 'warning',
  440. message: '发票信息不能为空'
  441. })
  442. return
  443. }
  444. if (state.form.payment.length == 0) {
  445. showNotify({
  446. type: 'warning',
  447. message: '支付信息不能为空'
  448. })
  449. return
  450. }
  451. const [errValid] = await to(formRef.value.validate())
  452. if (errValid) {
  453. state.loading = false
  454. return
  455. }
  456. const params = JSON.parse(JSON.stringify(state.form))
  457. params.fileList = params.fileList.map((item: any) => {
  458. return {
  459. fileName: item.fileName,
  460. fileSize: item.fileSize,
  461. invoiceType: item.invoiceType,
  462. fileUrl: item.fileUrl
  463. }
  464. })
  465. params.fileUrl = JSON.stringify(params.fileList)
  466. params.invoice = params.invoice.map((item: any) => {
  467. return {
  468. amount: item.amount,
  469. invoiceBy: item.invoiceBy,
  470. invoiceNo: item.invoiceNo,
  471. fileName: item.fileList[0].fileName,
  472. fileUrl: item.fileList[0].url,
  473. invoiceType: item.fileList[0].invoiceType
  474. }
  475. })
  476. const [err]: ToResponse = await to(expenseRemindApi.create(params))
  477. state.loading = false
  478. if (err) return
  479. showNotify({
  480. type: 'success',
  481. message: '操作成功'
  482. })
  483. router.push({
  484. path: '/fund/reimbursement-remind'
  485. })
  486. }
  487. onMounted(async () => {
  488. // 进行openId登录
  489. const code: string = route.query.code ? route.query.code.toString() : ''
  490. console.log('11111111111111111111111')
  491. let param = {
  492. code: code,
  493. unionId: unionId.value,
  494. user_name: userInfos.value?.userName,
  495. tenant: Local.get('Tenant'),
  496. }
  497. if (code) {
  498. console.log('2222222222222222222222')
  499. const [err, res]: ToResponse = await to(loginApi.weChatLogin(param))
  500. if (err) {
  501. // 跳转到登录页面
  502. Local.remove('token')
  503. router.push('/login')
  504. return
  505. }
  506. // 存储 token 到浏览器缓存
  507. Local.set('token', res?.data?.token)
  508. }
  509. const id = route.query.id ? +route.query.id : 0
  510. await getDict()
  511. await getFundDetail(id)
  512. getAmountInfo()
  513. })
  514. </script>
  515. <style lang="scss" scoped>
  516. .entry-container {
  517. flex: 1;
  518. padding: 0 10px 10px;
  519. background-color: #f9f9f9;
  520. overflow-y: auto;
  521. h4 {
  522. height: 38px;
  523. line-height: 38px;
  524. display: flex;
  525. align-items: center;
  526. span {
  527. font-weight: normal;
  528. margin-left: auto;
  529. }
  530. &::before {
  531. display: inline-block;
  532. content: '';
  533. width: 3px;
  534. height: 18px;
  535. background-color: #1c9bfd;
  536. margin-right: 4px;
  537. vertical-align: middle;
  538. }
  539. .van-button {
  540. margin-left: auto;
  541. }
  542. }
  543. .file-card {
  544. background: #fff;
  545. padding: 10px 10px 0;
  546. }
  547. }
  548. </style>