FormInfo.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. <template>
  2. <div>
  3. <el-card>
  4. <template slot="header">
  5. <div
  6. class="card-title"
  7. style="display:flex;"
  8. v-if="currentTicket">
  9. <div style="display:flex;">
  10. <div class="top-title">当前票种:<b>{{ currentTicket.name || '无' }}</b></div>
  11. <span v-if="currentTicket.isSale === 0" style="color: red">(停售)</span>
  12. <div v-if="category === 'batch'" style="margin-left: 20px;">当前场次:<b>{{ currentBatchName || '无' }}</b></div>
  13. </div>
  14. <div>{{ form.playDateBegin | formatDate }} 票价:<span class="price"><b>{{ currentPrice }}</b> 元</span></div>
  15. </div>
  16. <span
  17. v-else
  18. style="color:red">请选择门票</span>
  19. </template>
  20. <el-form
  21. v-loading="loading"
  22. class="sale-form-wrap"
  23. ref="form"
  24. :model="form"
  25. :inline="true"
  26. label-width="100px">
  27. <el-form-item
  28. v-if="isTeam"
  29. verify
  30. required
  31. label="团体">
  32. <el-select
  33. filterable
  34. v-model="form.teamId">
  35. <el-option
  36. v-for="item in teamList"
  37. :key="item.id"
  38. :label="item.name"
  39. :value="item.id">
  40. </el-option>
  41. </el-select>
  42. </el-form-item>
  43. <el-form-item
  44. v-if="isTeam"
  45. label="导游">
  46. <el-select
  47. filterable
  48. v-model="form.guideId">
  49. <el-option
  50. v-for="item in guideList"
  51. :key="item.id"
  52. :label="item.name"
  53. :value="item.id">
  54. </el-option>
  55. </el-select>
  56. </el-form-item>
  57. <el-form-item
  58. verify
  59. can-be-empty
  60. label="顾客姓名"
  61. prop="buyerName">
  62. <el-input
  63. v-model="form.buyerName"></el-input>
  64. </el-form-item>
  65. <el-form-item
  66. can-be-empty
  67. :verify="verifyPhone"
  68. label="顾客电话"
  69. prop="buyerPhone">
  70. <el-input
  71. v-model="form.buyerPhone"></el-input>
  72. </el-form-item>
  73. <el-form-item
  74. label="证件类型"
  75. prop="buyerIdentifyType">
  76. <el-select
  77. :disabled="identifyLockCheckStyle"
  78. v-model="form.buyerIdentifyType"
  79. placeholder="请选择">
  80. <el-option
  81. v-for="(item, idx) in papersType"
  82. :key="`id-${idx}`"
  83. :label="item.label"
  84. :value="item.value">
  85. </el-option>
  86. </el-select>
  87. <el-checkbox
  88. style="margin-left:10px"
  89. v-model="identifyLockCheckStyle">
  90. 锁定
  91. </el-checkbox>
  92. </el-form-item>
  93. <!-- <el-form-item
  94. key="id1"
  95. :verify="form.buyerIdentifyType === 0 ? verifyId : ''"
  96. can-be-empty
  97. v-if="form.buyerIdentifyType === 0"
  98. label="证件号码"
  99. prop="buyerIdentify">
  100. <ReaderInput
  101. type="ID"
  102. @change="form.buyerName = $event.Name,Certificate=$event"
  103. @input="form.buyerIdentify = $event"
  104. v-model="form.buyerIdentify"></ReaderInput>
  105. </el-form-item> -->
  106. <el-form-item
  107. key="id2"
  108. label="证件号码"
  109. prop="buyerIdentify">
  110. <el-input v-model="form.buyerIdentify"></el-input>
  111. </el-form-item>
  112. <el-form-item
  113. v-if="Certificate.Nation"
  114. label="国家"
  115. >
  116. {{ Certificate.Nation }}
  117. </el-form-item>
  118. <el-form-item
  119. v-if="Certificate.Sex"
  120. label="性别"
  121. >
  122. {{ Certificate.Sex }}
  123. </el-form-item>
  124. <el-form-item
  125. v-if="Certificate.ValidDate"
  126. label="有效截止日期"
  127. >
  128. {{ Certificate.ValidDate }}
  129. </el-form-item>
  130. <el-form-item
  131. prop="playDateBegin"
  132. label="游玩日期">
  133. <el-date-picker
  134. :clearable="false"
  135. type="date"
  136. :editable="false"
  137. disabled
  138. :picker-options="dateOption"
  139. v-model="form.playDateBegin"
  140. >
  141. </el-date-picker>
  142. </el-form-item>
  143. <el-form-item
  144. prop="playDateEnd"
  145. label="结束日期">
  146. <el-date-picker
  147. :clearable="false"
  148. :editable="false"
  149. disabled
  150. :picker-options="dateOption"
  151. v-model="form.playDateEnd"
  152. type="date"
  153. >
  154. </el-date-picker>
  155. </el-form-item>
  156. <el-form-item
  157. verify
  158. number
  159. label="购买数量"
  160. prop="count"
  161. >
  162. <el-input-number
  163. ref="ticketCount"
  164. name="ticket-count"
  165. v-model.number="form.count"
  166. :min="1"
  167. :max="10000"
  168. :precision="0"
  169. ></el-input-number>
  170. </el-form-item>
  171. <el-form-item
  172. label="检票方式"
  173. prop="checkWay">
  174. <el-select
  175. v-model="form.checkWay"
  176. :disabled="retailLockCheckStyle">
  177. <el-option
  178. v-for="item in checkWayList"
  179. :key="item.val"
  180. :value="item.val"
  181. :label="item.name"
  182. ></el-option>
  183. </el-select>
  184. <el-checkbox
  185. style="margin-left:10px"
  186. v-model="retailLockCheckStyle">
  187. 锁定
  188. </el-checkbox>
  189. </el-form-item>
  190. <!-- v-if="form.checkWay === 3 || form.checkWay === 4" -->
  191. <el-form-item
  192. label="游客信息"
  193. >
  194. <div
  195. v-if="currentTicket"
  196. class="input-space"
  197. >
  198. <el-button
  199. type="primary"
  200. size="small"
  201. icon="el-icon-edit"
  202. @click="showTouristList"
  203. >
  204. 编辑查看
  205. </el-button>
  206. </div>
  207. </el-form-item>
  208. </el-form>
  209. <div class="btn-wrap">
  210. <el-button
  211. @click="resetForm"
  212. style="margin-right:20px">
  213. 重置表单
  214. </el-button>
  215. <el-button
  216. type="primary"
  217. @click="addToOrder">
  218. 加入订单
  219. </el-button>
  220. </div>
  221. </el-card>
  222. <!-- 游客列表 -->
  223. <TouristList
  224. :check-ticket-no="needCheckTicket"
  225. :check-face="needCheckFace"
  226. :check-id="needCheckId"
  227. :check-ic="needCheckIc"
  228. :tourist-list="touristList"
  229. :update-tourist="updateTourist"
  230. ref="touristList"></TouristList>
  231. </div>
  232. </template>
  233. <script>
  234. import { randomString } from '@/utils'
  235. import { verifyId, verifyPhone } from '@/utils/validate'
  236. import { checkWayList } from '@/assets/staticData'
  237. import { setTourist } from '@/pages/common'
  238. import TouristList from '../common/TouristList'
  239. // import ReaderInput from '@/components/ReaderInput'
  240. import moment from 'moment'
  241. import { IDENTIFY_TYPES } from '@/const'
  242. import { getTeamList, getGuideList } from '@/api/checker'
  243. export default {
  244. props: {
  245. currentTicket: {
  246. type: Object,
  247. default: () => null
  248. },
  249. currentBatch: {
  250. type: Object,
  251. default: () => null
  252. },
  253. orderItems: {
  254. type: Array,
  255. default: () => []
  256. }
  257. },
  258. components: {
  259. TouristList
  260. // ReaderInput
  261. },
  262. computed: {
  263. category () {
  264. return this.$route.meta.category || 'ticket'
  265. },
  266. isTeam () {
  267. return this.$route.meta.type === 'team'
  268. },
  269. totalPrice () {
  270. if (!this.currentTicket) {
  271. return 0
  272. }
  273. return this.$NP.times(this.currentPrice, this.form.count)
  274. },
  275. scenicName () {
  276. return this.$store.state.user.scenicName
  277. },
  278. currentBatchName () {
  279. if (!this.currentTicket || !this.currentBatch) return ''
  280. const { name, startTime, endTime } = this.currentBatch
  281. return name ? `${name} ${startTime} - ${endTime}` : ''
  282. },
  283. needCheckTicket () {
  284. return this.form.checkWay === 5
  285. },
  286. needCheckId () {
  287. const { category, currentTicket } = this
  288. return category === 'member' && currentTicket.checkType.includes('idcard')
  289. },
  290. needCheckFace () {
  291. const { category, currentTicket } = this
  292. return category === 'member' && currentTicket.checkType.includes('face')
  293. },
  294. needCheckIc () {
  295. const { category, currentTicket } = this
  296. return category === 'member' && currentTicket.checkType.includes('card')
  297. }
  298. },
  299. data () {
  300. return {
  301. loading: false,
  302. keyControl: 2, // 键盘控制:1,2,3标识选中顺序
  303. isFocus: false, // 当前组件是否选中
  304. papersType: IDENTIFY_TYPES,
  305. checkWayList,
  306. pricePlan: [],
  307. currentPrice: 0,
  308. currentPlan: null,
  309. retailLockCheckStyle: this.$localStore.get('retailLockCheckStyle') || false,
  310. identifyLockCheckStyle: this.$localStore.get('identifyLockCheckStyle') || false,
  311. touristList: [{
  312. batchConfigId: randomString(),
  313. guestName: '',
  314. guestPhone: '',
  315. guestIdentify: '',
  316. guestIdentifyType: 0,
  317. ticketTypeId: 0,
  318. checkNum: 1,
  319. price: 0,
  320. seatId: 0,
  321. playDateBegin: new Date(moment().startOf('day').valueOf()),
  322. lock: false // 是否可编辑
  323. }],
  324. form: {
  325. buyerIdentify: '',
  326. buyerName: '',
  327. buyerIdentifyType: 0,
  328. buyerPhone: '',
  329. groupIndividual: 1,
  330. count: this.$localStore.get('initNum') === '空' ? undefined : 1,
  331. payChannel: '现金',
  332. playDateBegin: new Date(moment().startOf('day').valueOf()),
  333. playDateEnd: '',
  334. price: 0,
  335. teamId: '',
  336. guideId: '',
  337. checkWay: this.isTeam ? 2 : 1,
  338. actualMoney: 0 // 实付金额,不需要传给后台
  339. },
  340. Certificate: {
  341. },
  342. dateOption: {
  343. // 日历禁用日期
  344. disabledDate () {
  345. return false
  346. }
  347. },
  348. disablePlayDateBegin: false,
  349. teamList: [],
  350. guideList: []
  351. }
  352. },
  353. created () {
  354. if (this.isTeam) {
  355. getTeamList({ pageSize: -1, pageNum: 1 }).then(res => {
  356. this.teamList = res.data.records || []
  357. })
  358. getGuideList({ pageSize: -1, pageNum: 1 }).then(res => {
  359. this.guideList = res.data.records || []
  360. })
  361. }
  362. },
  363. methods: {
  364. verifyId,
  365. verifyPhone,
  366. randomString,
  367. setFocus () {
  368. this.$refs.ticketCount.focus()
  369. this.$refs.ticketCount.select()
  370. },
  371. resetForm () {
  372. this.touristList = [{
  373. batchConfigId: randomString(),
  374. guestName: '',
  375. guestPhone: '',
  376. guestIdentify: '',
  377. guestIdentifyType: 0,
  378. lock: false // 是否可编辑
  379. }]
  380. this.$refs.form.resetFields()
  381. this.form.checkWay = this.isTeam ? 2 : 1
  382. this.Certificate = {}
  383. },
  384. setEndDate (val) {
  385. const { category, currentTicket } = this
  386. const { useDateType, memberUseDateType, useDays } = currentTicket
  387. if ((category === 'ticket' && useDateType === 1) || (category === 'member' && memberUseDateType === 2)) {
  388. this.form.playDateEnd = moment(val).add((useDays || 1) - 1, 'days').format('YYYY-MM-DD')
  389. }
  390. if (category === 'batch') {
  391. this.form.playDateEnd = moment(val).format('YYYY-MM-DD')
  392. }
  393. if (category === 'member' && memberUseDateType === 1) {
  394. this.form.playDateEnd = moment(val).add((useDays || 1) - 1, 'days').format('YYYY-MM-DD')
  395. }
  396. },
  397. // 更新游客列表
  398. updateTourist (data) {
  399. this.form.count = data.length
  400. this.touristList = data
  401. },
  402. // 同步游客数据与游客数
  403. syncTouristList () {
  404. let num = this.form.count - this.touristList.length
  405. setTourist(this.touristList, num)
  406. },
  407. // 显示游客弹窗
  408. showTouristList () {
  409. this.syncTouristList()
  410. this.$refs.touristList.show()
  411. },
  412. isPlayToday () {
  413. const isBefore = moment(this.form.playDateBegin).isBefore(moment().format('YYYY-MM-DD'))
  414. const isAfter = moment(this.form.playDateBegin).isAfter(moment().format('YYYY-MM-DD'))
  415. return !(isBefore || isAfter)
  416. },
  417. addToOrder (callback) {
  418. if (this.currentTicket && this.currentTicket.isSale === 0) {
  419. return this.$message.error('当前票种已停售')
  420. }
  421. if (!this.isPlayToday()) {
  422. // 客户端下单的时候游玩日期不是当天,提示一下
  423. this.$message.warning('友情提示:您下单的游玩日期不是今天!')
  424. }
  425. if (!this.currentTicket) {
  426. return this.$message.error('请选择门票')
  427. }
  428. if (this.category === 'batch') {
  429. if (!this.currentBatch) {
  430. return this.$message.error('请选场次')
  431. }
  432. if (this.currentBatch.leftNums !== -1) {
  433. if (this.currentBatch.leftNums === 0) {
  434. return this.$message.error('当前场次已售罄')
  435. }
  436. if (this.currentBatch.leftNums < this.form.count) {
  437. return this.$message.error('当前场次剩余票数不足')
  438. }
  439. }
  440. }
  441. if (this.loading) return
  442. this.$refs.form.validate((valid) => {
  443. if (valid) {
  444. this.syncTouristList()
  445. const { playDateBegin, playDateEnd } = this.form
  446. const { currentBatch } = this
  447. const { id, name, checkCount, isUseDateLimit } = this.currentTicket
  448. const price = this.currentPrice
  449. const originalPrice = this.currentPrice
  450. if (this.isTeam) {
  451. if (!this.form.teamId) {
  452. this.$message.error('请选择团队')
  453. return
  454. }
  455. }
  456. if (this.form.checkWay === 3) {
  457. // 是否存在未填写的内容
  458. let blankData = this.touristList.find(item => {
  459. return !item.guestName || !item.guestIdentify
  460. })
  461. if (blankData) {
  462. this.$message.error('游客信息不完整,请编辑查看')
  463. return
  464. }
  465. }
  466. if (this.form.checkWay === 4) {
  467. // 是否存在未填写的内容
  468. let blankData = this.touristList.find(item => {
  469. return !item.face
  470. })
  471. if (blankData) {
  472. this.$message.error('游客人脸信息不完整,请编辑查看')
  473. return
  474. }
  475. }
  476. const pdb = moment(playDateBegin).format('YYYY-MM-DD')
  477. const pde = moment(playDateEnd).format('YYYY-MM-DD')
  478. let tickets = this.touristList.map(item => {
  479. return {
  480. ...item,
  481. ticketTypeId: id,
  482. checkNum: checkCount,
  483. price,
  484. originalPrice,
  485. discountPrice: 0,
  486. ticketNo: item.ticketNo,
  487. batchConfigId: currentBatch?.id || '',
  488. face: item.face,
  489. isPrepare: this.form.checkWay === 5 ? 1 : 0,
  490. pricePlanId: this.currentPlan || 0,
  491. playDateBegin: pdb,
  492. playDateEnd: pde
  493. }
  494. })
  495. // 合并游玩日期相同的票种数据
  496. const existItem = this.orderItems.find(i => {
  497. let dateIsEque
  498. if (isUseDateLimit) {
  499. dateIsEque = JSON.stringify(i.playDateBegin) === JSON.stringify(pdb)
  500. } else {
  501. dateIsEque = true
  502. }
  503. // 日期对象不全等
  504. return i.id === id && dateIsEque && i.price === this.currentPrice
  505. })
  506. if (existItem) {
  507. Object.assign(existItem, this.form)
  508. existItem.tickets = existItem.tickets.concat(tickets)
  509. existItem.count = existItem.tickets.length
  510. existItem.total = this.$NP.plus(existItem.total, this.totalPrice)
  511. } else {
  512. this.orderItems.push({
  513. ...this.form,
  514. keyId: randomString(), // 唯一标识
  515. id, // 票种Id
  516. ticketName: name,
  517. count: tickets.length,
  518. price,
  519. originalPrice,
  520. discountPrice: 0,
  521. total: this.totalPrice,
  522. playDateBegin: pdb,
  523. currentTicket: {
  524. ...this.currentTicket,
  525. pricePlanId:
  526. this.currentPlan,
  527. price,
  528. playDateBegin: pdb,
  529. playDateEnd: pde
  530. },
  531. tickets
  532. })
  533. }
  534. // 重置游客数量
  535. this.touristList = [{
  536. batchConfigId: randomString(),
  537. guestName: '',
  538. guestPhone: '',
  539. guestIdentify: '',
  540. guestIdentifyType: 0,
  541. playDateBegin: moment().format('YYYY-MM-DD'),
  542. lock: false // 是否可编辑
  543. }]
  544. this.form.count = this.$localStore.get('initNum') === '空' ? undefined : 1
  545. if (callback && typeof callback === 'function') {
  546. callback()
  547. }
  548. this.$nextTick(() => {
  549. this.$refs.form.clearValidate()
  550. })
  551. }
  552. })
  553. },
  554. setPrice (price) {
  555. this.currentPrice = price || this.currentTicket.price
  556. this.form.price = this.currentPrice
  557. },
  558. defaultPapersType () {
  559. if (process.env.VUE_APP_PROJECT === 'zql') {
  560. return 0
  561. } else if (this.$localStore.get('retail_identify')) {
  562. return this.$localStore.get('retail_identify')
  563. } else {
  564. return ''
  565. }
  566. }
  567. },
  568. watch: {
  569. async currentTicket (val) {
  570. if (!val) return
  571. await this.$nextTick()
  572. this.resetForm()
  573. const { useDateType, useDateStart, useDateEnd, useDays, memberUseDateType, playDate } = val
  574. this.disablePlayDateBegin = false
  575. switch (this.category) {
  576. case 'ticket':
  577. if (useDateType === 2) {
  578. this.form.playDateBegin = moment(useDateStart).format('YYYY-MM-DD')
  579. this.form.playDateEnd = moment(useDateEnd).format('YYYY-MM-DD')
  580. this.disablePlayDateBegin = true
  581. } else {
  582. this.form.playDateBegin = moment(playDate).format('YYYY-MM-DD')
  583. this.form.playDateEnd = moment(playDate).add((useDays || 1) - 1, 'days').format('YYYY-MM-DD')
  584. }
  585. break
  586. case 'batch':
  587. this.form.playDateBegin = moment(playDate).format('YYYY-MM-DD')
  588. this.form.playDateEnd = moment(playDate).format('YYYY-MM-DD')
  589. this.disablePlayDateBegin = true
  590. break
  591. case 'member':
  592. if (memberUseDateType === 1) {
  593. this.form.playDateBegin = moment(playDate).format('YYYY-MM-DD')
  594. this.form.playDateEnd = moment(playDate).add((useDays || 1) - 1, 'days').format('YYYY-MM-DD')
  595. } else {
  596. this.form.playDateBegin = moment(useDateStart).format('YYYY-MM-DD')
  597. this.form.playDateEnd = moment(useDateEnd).format('YYYY-MM-DD')
  598. }
  599. this.disablePlayDateBegin = true
  600. break
  601. default :
  602. break
  603. }
  604. // 设置不可选日期
  605. this.dateOption.disabledDate = (date) => {
  606. let beforeToday = moment().subtract(1, 'days').isAfter(date)
  607. let beforeStart = moment(val.useDateStart).isAfter(date)
  608. let afterEnd = moment(val.useDateEnd).isBefore(date)
  609. return beforeToday || beforeStart || afterEnd
  610. }
  611. this.timer && clearTimeout(this.timer)
  612. this.timer = setTimeout(() => {
  613. this.setPrice(this.currentTicket.price)
  614. }, 300)
  615. },
  616. retailLockCheckStyle (val) {
  617. if (val) {
  618. this.$localStore.set('retailCheckWay', this.form.checkWay)
  619. }
  620. this.$localStore.set('retailLockCheckStyle', val)
  621. },
  622. identifyLockCheckStyle (val) {
  623. if (val) {
  624. this.$localStore.set('retail_identify', this.form.buyerIdentifyType)
  625. }
  626. this.$localStore.set('identifyLockCheckStyle', val)
  627. },
  628. 'form.playDateBegin' (val) {
  629. if (val) {
  630. this.setEndDate(val)
  631. this.currentPlan = null
  632. this.$emit('changePlayDate', moment(val).format('YYYY-MM-DD'))
  633. this.timer && clearTimeout(this.timer)
  634. }
  635. }
  636. }
  637. }
  638. </script>
  639. <style lang="scss" scoped>
  640. .el-input,.el-select,.el-date-editor,.el-date-editor.el-input,.el-input-number {
  641. width: 200px;
  642. }
  643. </style>