InvoiceModule.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. <template>
  2. <view>
  3. <u--form :model="inputForm" labelWidth="100px" class="u-form" labelPosition="left" :rules="rules"
  4. ref="inputForm">
  5. <!-- 动态生成发票基本信息 -->
  6. <el-row :gutter="15" v-for="(item, index_experience) in inputForm.workInvoiceProjectRelationList"
  7. :key="index_experience">
  8. <el-col :span="24">
  9. <u-form-item label="">
  10. <u-divider :text="'基本信息 ' + (index_experience + 1)"></u-divider>
  11. </u-form-item>
  12. </el-col>
  13. <el-col :span="24">
  14. <u-form-item label="项目名称"
  15. :prop="'workInvoiceProjectRelationList[' + index_experience + '].projectName'">
  16. <u--input v-model="item.projectName" :disabled="true" clearable></u--input>
  17. </u-form-item>
  18. </el-col>
  19. <el-col :span="24">
  20. <u-form-item label="合同名称"
  21. :prop="'workInvoiceProjectRelationList[' + index_experience + '].workContractName'">
  22. <u--input v-model="item.workContractName" :disabled="true" clearable></u--input>
  23. </u-form-item>
  24. </el-col>
  25. <el-col :span="24">
  26. <u-form-item label="项目编号"
  27. :prop="'workInvoiceProjectRelationList[' + index_experience + '].projectNum'">
  28. <u--input v-model="item.projectNum" :disabled="true" clearable></u--input>
  29. </u-form-item>
  30. </el-col>
  31. <el-col :span="24">
  32. <u-form-item label="委托方"
  33. :prop="'workInvoiceProjectRelationList[' + index_experience + '].clientName'">
  34. <u--input v-model="item.clientName" :disabled="true" clearable></u--input>
  35. </u-form-item>
  36. </el-col>
  37. <el-col :span="24">
  38. <u-form-item label="报告号"
  39. :prop="'workInvoiceProjectRelationList[' + index_experience + '].reportDataNum'">
  40. <u--input v-model="item.reportDataNum" :disabled="true" clearable></u--input>
  41. </u-form-item>
  42. </el-col>
  43. </el-row>
  44. <u-form-item label="">
  45. <u-divider :text="`发票详情`"></u-divider>
  46. </u-form-item>
  47. <u-form-item label="发票类型" prop="invoiceTypeName">
  48. <u--input v-model="inputForm.invoiceTypeName" :disabled="true" clearable></u--input>
  49. </u-form-item>
  50. <u-form-item label="开票类型" prop="newDrawerName">
  51. <u--input v-model="inputForm.newDrawerName" :disabled="true" clearable></u--input>
  52. </u-form-item>
  53. <u-form-item label="实际开票单位" prop="clientName">
  54. <u--input v-model="inputForm.clientName" :disabled="true" clearable></u--input>
  55. </u-form-item>
  56. <u-form-item label="纳税人识别号" prop="taxpayerIdentificationNo">
  57. <u--input v-model="inputForm.taxpayerIdentificationNo" :disabled="true" clearable></u--input>
  58. </u-form-item>
  59. <u-form-item label="地址" prop="address">
  60. <u--input v-model="inputForm.address" :disabled="true" clearable></u--input>
  61. </u-form-item>
  62. <u-form-item label="电话" prop="telephone">
  63. <u--input v-model="inputForm.telephone" :disabled="true" clearable></u--input>
  64. </u-form-item>
  65. <u-form-item label="开户银行" prop="bank">
  66. <u--input v-model="inputForm.bank" :disabled="true" clearable></u--input>
  67. </u-form-item>
  68. <u-form-item label="银行账号" prop="bankNumber">
  69. <u--input v-model="inputForm.bankNumber" :disabled="true" clearable></u--input>
  70. </u-form-item>
  71. <u-form-item label="收款类型" prop="chargeType">
  72. <u--input v-model="inputForm.chargeType" :disabled="true" clearable></u--input>
  73. </u-form-item>
  74. <u-form-item label="开票内容" prop="billingContent">
  75. <u--input v-model="inputForm.billingContent" :disabled="true" clearable></u--input>
  76. </u-form-item>
  77. <u-form-item label="发票金额(元)" prop="money">
  78. <u--input v-model="inputForm.money" :disabled="true" clearable></u--input>
  79. </u-form-item>
  80. <u-form-item label="开票内容要求" prop="content">
  81. <u--input v-model="inputForm.content" :disabled="true" clearable></u--input>
  82. </u-form-item>
  83. <u-form-item label="开票人" prop="drawerName">
  84. <u--input v-model="inputForm.drawerName" :disabled="true" clearable></u--input>
  85. </u-form-item>
  86. <u-form-item label="开票时间" prop="invoiceDate" :required="true">
  87. <el-date-picker v-model="inputForm.invoiceDate" type="date" placeholder="选择开票时间" style="width:100%"
  88. size="default" placement="bottom-start" clearable>
  89. </el-date-picker>
  90. </u-form-item>
  91. <u-form-item label="领票时间" prop="takeDate">
  92. <el-date-picker v-model="inputForm.takeDate" type="date" placeholder="选择领票时间" style="width:100%"
  93. size="default" placement="bottom-start" clearable>
  94. </el-date-picker>
  95. </u-form-item>
  96. <u-form-item label="实际开票人" prop="actualDrawerName">
  97. <u--input v-model="inputForm.actualDrawerName" :disabled="true" clearable></u--input>
  98. </u-form-item>
  99. <u-form-item label="接收邮箱" prop="actualDrawerEmailAddress">
  100. <u--input v-model="inputForm.actualDrawerEmailAddress" :disabled="true" clearable></u--input>
  101. </u-form-item>
  102. <u-form-item label="对账人" prop="accountCheckingUserName">
  103. <u--input v-model="inputForm.accountCheckingUserName" :disabled="true" clearable></u--input>
  104. </u-form-item>
  105. <u-form-item label="对账地区" prop="area">
  106. <u--input v-model="inputForm.area" :disabled="true" clearable></u--input>
  107. </u-form-item>
  108. <u-form-item label="备注" prop="remarks">
  109. <u--input v-model="inputForm.remarks" :disabled="true" clearable></u--input>
  110. </u-form-item>
  111. <el-row :gutter="15" :key="item.id" v-for="(item, index_experience) in this.inputForm.workAccountList">
  112. <el-col :span="24">
  113. <u-form-item label="">
  114. <el-divider content-position="left"> 发票明细详情 {{ index_experience + 1 }}</el-divider>
  115. </u-form-item>
  116. </el-col>
  117. <el-col :span="24">
  118. <u-form-item label="发票代码" :prop="'workAccountList[' + index_experience + '].code'" :rules="[
  119. ]">
  120. <u--input v-model="item.code" placeholder="请填写发票代码" clearable></u--input>
  121. </u-form-item>
  122. </el-col>
  123. <el-col :span="24">
  124. <u-form-item label="发票号" :prop="'workAccountList[' + index_experience + '].number'" :required="true"
  125. :rules="[
  126. ]">
  127. <u--input v-model="item.number" placeholder="请填写发票号" @blur="validateNumber(index_experience)"
  128. clearable></u--input>
  129. </u-form-item>
  130. </el-col>
  131. <el-col :span="24">
  132. <u-form-item label="开票金额(元)" :prop="'workAccountList[' + index_experience + '].account'"
  133. :required="true" :rules="[
  134. { validator: validateAmount, trigger: 'blur' }
  135. ]">
  136. <u-input v-model="item.account" placeholder="请填写开票金额(元)" clearable
  137. @input="handleInput($event, index_experience)"
  138. @blur="handleBlur($event, index_experience)"></u-input>
  139. </u-form-item>
  140. </el-col>
  141. <el-col :span="24">
  142. <u-form-item label="税率(%)" :prop="'workAccountList[' + index_experience + '].rate'" :rules="[
  143. ]">
  144. <u-input v-model="item.rate" placeholder="请填写税率(%)"
  145. @input="handleRateInput($event, index_experience)"
  146. @blur="checkRate($event, index_experience)" clearable></u-input>
  147. </u-form-item>
  148. </el-col>
  149. <el-col :span="24">
  150. <u-form-item label="金额" :prop="'workAccountList[' + index_experience + '].amount'" :rules="[
  151. ]">
  152. <u-input v-model="item.amount" placeholder="请填写金额" readonly clearable></u-input>
  153. </u-form-item>
  154. </el-col>
  155. <el-col :span="24">
  156. <u-form-item label="税额" :prop="'workAccountList[' + index_experience + '].tax'" :rules="[
  157. ]">
  158. <u-input v-model="item.tax" placeholder="请填写税额" readonly clearable></u-input>
  159. </u-form-item>
  160. </el-col>
  161. <el-col :span="24" style="text-align: center">
  162. <u-form-item label="">
  163. <el-button style="width: 100%" type="danger" @click="removeRow(index_experience)" plain>删除发票明细
  164. {{ index_experience + 1 }}</el-button>
  165. </u-form-item>
  166. </el-col>
  167. </el-row>
  168. <u-form-item label="">
  169. <el-button style="width: 100%" type="primary" @click="addRow()" plain>新增发票明细</el-button>
  170. </u-form-item>
  171. <u-form-item label="附件">
  172. <!-- <el-upload class="upload-demo"
  173. :action="`http://cpaoa.xgccpm.com/api/public-modules-server/oss/file/webUpload/upload`"
  174. :on-remove="(file, fileList) => handleRemove(file, fileList, '')" :file-list="inputForm.files"
  175. :on-success="(response, file, fileList) => handleUploadSuccess(response, file, fileList, '')"
  176. :limit="3">
  177. <el-button size="small" type="primary" v-if="false">点击上传</el-button>
  178. <div slot="tip" class="el-upload__tip">只能上传不超过 3 个文件</div>
  179. <template slot="file" slot-scope="{ file }" v-if="shouldShowFile(file)">
  180. <span @click="handleFileClick(file)">{{ file.name }}</span>
  181. </template>
  182. </el-upload> -->
  183. <!-- <UploadComponent
  184. :uploadUrl="`http://127.0.0.1:8000/api/public-modules-server/oss/file/webUpload/upload`"
  185. @onRemove="(file, fileList, fileIndex) => handleRemove(file, fileList, '', '', fileIndex)"
  186. @onSuccess="(file, fileList) => handleUploadSuccess(file, fileList, '', '')"
  187. :fileList="inputForm.files" :limit="3" :isDelete="!testFlag" :isUpload="!testFlag">
  188. </UploadComponent> -->
  189. </u-form-item>
  190. </u--form>
  191. </view>
  192. </template>
  193. <script>
  194. import ccpmService from '@/api/centerservice/ccpm/CcpmService'
  195. import OSSService from "@/api/sys/OSSService";
  196. import moment from "moment";
  197. // import UploadComponent from '@/pages/common/UploadComponent.vue';
  198. export default {
  199. name: 'DynamicForm',
  200. props: {
  201. invoice: {
  202. type: Array,
  203. required: true
  204. },
  205. },
  206. conponents: {
  207. // UploadComponent
  208. },
  209. data() {
  210. return {
  211. inputForm: {
  212. workInvoiceProjectRelationList: [],
  213. workAccountList: [],
  214. invoiceTypeName: "",
  215. newDrawerName: "",
  216. clientName: "",
  217. taxpayerIdentificationNo: "",
  218. address: "",
  219. telephone: "",
  220. bank: "",
  221. bankNumber: "",
  222. chargeType: "",
  223. billingContent: "",
  224. money: "",
  225. content: "",
  226. drawerName: "",
  227. invoiceDate: "",
  228. takeDate: "",
  229. actualDrawerName: "",
  230. actualDrawerEmailAddress: "",
  231. accountCheckingUserName: "",
  232. area: "",
  233. remarks: "",
  234. files: []
  235. },
  236. rules: {
  237. invoiceDate: [
  238. {
  239. required: true,
  240. message: '开票时间不能为空',
  241. trigger: ['blur', 'change']
  242. }
  243. ],
  244. },
  245. isDeletingRow: false, // 标志变量
  246. };
  247. },
  248. ossService: null,
  249. created() {
  250. this.ossService = new OSSService();
  251. },
  252. watch: {
  253. invoice: {
  254. handler(newVal) {
  255. this.assignment();
  256. },
  257. deep: true,
  258. immediate: true
  259. }
  260. },
  261. methods: {
  262. getInputForm() {
  263. return this.inputForm;
  264. },
  265. assignment() {
  266. if (this.isDeletingRow) {
  267. return; // 如果正在删除行,则不执行此方法
  268. } else {
  269. if (Array.isArray(this.invoice) && this.invoice.length > 0) {
  270. this.inputForm = this.recover(this.inputForm, this.invoice[0])
  271. // this.inputForm = this.invoice[0];
  272. if (this.isEmpty(this.inputForm.invoiceDate)) {
  273. this.inputForm.invoiceDate = moment(new Date()).format('YYYY-MM-DD');
  274. }
  275. if (this.isEmpty(this.inputForm.workAccountList) || this.inputForm.workAccountList.length === 0) {
  276. this.inputForm.workAccountList.push({
  277. code: '',
  278. number: '',
  279. account: this.inputForm.money,
  280. rate: '',
  281. amount: '',
  282. tax: '',
  283. allAmount: ''
  284. })
  285. }
  286. }
  287. }
  288. },
  289. isEmpty(value) {
  290. return value == null || value === undefined || (typeof value === 'string' && value.trim() === '') || (Array.isArray(value) && value.length === 0);
  291. },
  292. isNotEmpty(value) {
  293. return !this.isEmpty(value);
  294. },
  295. shouldShowFile(file) {
  296. if (this.inputForm.files && this.inputForm.files.length > 0) {
  297. return this.inputForm.files.indexOf(file) !== -1;
  298. }
  299. return false;
  300. },
  301. async handleFileClick(file) {
  302. await this.ossService.getTemporaryUrl(file.url).then((data) => {
  303. file.lsUrl = data;
  304. });
  305. if (this.isImage(file.name)) {
  306. this.handleImageClick(file.lsUrl);
  307. } else {
  308. window.location.href = file.lsUrl;
  309. }
  310. },
  311. isImage(fileName) {
  312. const ext = fileName.toLowerCase().split('.').pop();
  313. return ['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext);
  314. },
  315. handleImageClick(url) {
  316. uni.previewImage({
  317. current: url,
  318. urls: [url],
  319. });
  320. },
  321. handleDelete(file) {
  322. const index = this.inputForm.files.indexOf(file);
  323. if (index !== -1) {
  324. this.inputForm.files.splice(index, 1);
  325. }
  326. },
  327. async validateNumber(index) {
  328. const numberString = this.inputForm.workAccountList[index].number.trim(); // 去除空格
  329. let errorDetected = false; // 布尔变量用于检测是否有错误发生
  330. if (this.isNotEmpty(numberString)) {
  331. if (!/^\d+$/.test(numberString)) { // 使用正则表达式检查是否只包含数字字符
  332. uni.showToast({
  333. title: '发票号只能输入整数',
  334. icon: 'none',
  335. duration: 2000
  336. });
  337. errorDetected = true; // 如果有错误,设置为 true
  338. }
  339. // 验证发票号是否大于 8 位
  340. if (parseInt(numberString) > 99999999) {
  341. uni.showToast({
  342. title: '“发票号” 不可以大于 8 位,请重新输入',
  343. icon: 'none',
  344. duration: 2000
  345. });
  346. errorDetected = true; // 如果有错误,设置为 true
  347. }
  348. // 验证是否重复
  349. for (let i = 0; i < this.inputForm.workAccountList.length; i++) {
  350. if (index !== i && numberString === this.inputForm.workAccountList[i].number) {
  351. uni.showToast({
  352. title: '“发票号” 已存在,请重新输入',
  353. icon: 'none',
  354. duration: 2000
  355. });
  356. errorDetected = true; // 如果有错误,设置为 true
  357. break; // 找到重复即可跳出循环
  358. }
  359. }
  360. // 查询是否已存在
  361. await ccpmService.queryByNumber(numberString).then((data) => {
  362. if (data === true) {
  363. uni.showToast({
  364. title: '“发票号” 已存在,请重新输入',
  365. icon: 'none',
  366. duration: 2000
  367. });
  368. errorDetected = true; // 如果有错误,设置为 true
  369. }
  370. });
  371. // 只在发生错误时清空输入字段
  372. if (errorDetected) {
  373. this.$set(this.inputForm.workAccountList[index], 'number', '');
  374. }
  375. }
  376. },
  377. // 根据开票金额和税率计算出金额: 开票金额-税率*开票金额
  378. getAmount(rate, rowIndex) {
  379. let amount = this.inputForm.workAccountList[rowIndex].amount
  380. let account = this.inputForm.workAccountList[rowIndex].account
  381. if (!this.isEmpty(account) && !this.isEmpty(rate)) {
  382. amount = parseFloat((parseFloat(account) - parseFloat((parseFloat(account) * parseFloat((parseFloat(rate) / 100).toFixed(4))).toFixed(4))).toFixed(2))
  383. } else {
  384. amount = ''
  385. }
  386. this.$set(this.inputForm.workAccountList[rowIndex], 'amount', amount)
  387. },
  388. // 根据开票金额和税率计算出税额: 税率*开票金额
  389. getTax(rate, rowIndex) {
  390. let tax = this.inputForm.workAccountList[rowIndex].tax
  391. let account = this.inputForm.workAccountList[rowIndex].account
  392. if (!this.isEmpty(account) && !this.isEmpty(rate)) {
  393. tax = parseFloat((parseFloat(account) * parseFloat((parseFloat(rate) / 100).toFixed(4))).toFixed(2))
  394. } else {
  395. tax = ''
  396. }
  397. this.$set(this.inputForm.workAccountList[rowIndex], 'tax', tax)
  398. },
  399. validateAmount(rule, value, callback) {
  400. const reg = /^\d+(\.\d{1,2})?$/;
  401. if (!value) {
  402. callback(new Error('请输入开票金额'));
  403. } else if (!reg.test(value)) {
  404. callback(new Error('请输入有效的开票金额,最多两位小数'));
  405. } else {
  406. callback();
  407. }
  408. },
  409. handleInput(event, index) {
  410. const value = event;
  411. const reg = /^\d*\.?\d{0,2}$/;
  412. if (!reg.test(value)) {
  413. // 保留符合规则的部分
  414. const validValue = value.slice(0, -1);
  415. this.$nextTick(() => {
  416. this.$set(this.inputForm.workAccountList[index], 'account', validValue);
  417. });
  418. } else {
  419. this.$set(this.inputForm.workAccountList[index], 'account', value);
  420. }
  421. },
  422. handleBlur(event, index) {
  423. const value = event;
  424. if (!value || isNaN(value)) {
  425. this.$set(this.inputForm.workAccountList[index], 'account', '');
  426. } else {
  427. const formattedValue = parseFloat(value).toFixed(2);
  428. this.$nextTick(() => {
  429. this.$set(this.inputForm.workAccountList[index], 'account', formattedValue);
  430. });
  431. }
  432. },
  433. handleRateInput(event, rowIndex) {
  434. const value = event;
  435. const reg = /^\d*\.?\d{0,2}$/;
  436. if (!reg.test(value)) {
  437. // 保留符合规则的部分
  438. const validValue = value.slice(0, -1);
  439. this.$nextTick(() => {
  440. this.$set(this.inputForm.workAccountList[rowIndex], 'rate', validValue);
  441. });
  442. } else {
  443. this.$set(this.inputForm.workAccountList[rowIndex], 'rate', value);
  444. }
  445. },
  446. checkRate(event, rowIndex) {
  447. const value = event;
  448. if (!value || isNaN(value)) {
  449. this.$set(this.inputForm.workAccountList[rowIndex], 'rate', '');
  450. } else {
  451. let validValue = parseFloat(value);
  452. if (!this.isEmpty(value)) {
  453. if (validValue < 1 || validValue > 100) {
  454. uni.showToast({
  455. title: '“税率” 请填写 1 到 100 之间的数字,请重新输入',
  456. icon: 'none',
  457. duration: 2000
  458. });
  459. validValue = '';
  460. } else {
  461. validValue = validValue.toFixed(2);
  462. }
  463. }
  464. this.$nextTick(() => {
  465. this.$set(this.inputForm.workAccountList[rowIndex], 'rate', validValue);
  466. });
  467. this.getAmount(validValue, rowIndex);
  468. this.getTax(validValue, rowIndex);
  469. }
  470. },
  471. addRow() {
  472. this.inputForm.workAccountList.push({
  473. code: '',
  474. number: '',
  475. account: '',
  476. rate: '',
  477. amount: '',
  478. tax: '',
  479. allAmount: ''
  480. })
  481. },
  482. removeRow(index) {
  483. this.isDeletingRow = true; // 设置标志变量
  484. this.inputForm.workAccountList.splice(index, 1);
  485. },
  486. }
  487. };
  488. </script>
  489. <style scoped>
  490. .upload-demo {
  491. margin-top: 10px;
  492. }
  493. </style>