ticket.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. <template>
  2. <div class="form-wrap">
  3. <el-form
  4. class="search-box"
  5. ref="form"
  6. :model="form"
  7. :inline="true"
  8. >
  9. <div class="block-title">
  10. 查询条件
  11. </div>
  12. <el-form-item
  13. label="名称"
  14. prop="ticketTypeName"
  15. >
  16. <el-input v-model="form.ticketTypeName"></el-input>
  17. </el-form-item>
  18. <el-form-item
  19. label="类型"
  20. prop="category"
  21. >
  22. <el-select v-model="form.category">
  23. <el-option
  24. value=""
  25. label="全部"
  26. ></el-option>
  27. <el-option
  28. v-for="item in ticketCategory"
  29. :key="item.id"
  30. :value="item.id"
  31. :label="item.name">
  32. </el-option>
  33. </el-select>
  34. </el-form-item>
  35. <el-form-item
  36. label="是否可售"
  37. prop="isSale"
  38. >
  39. <el-select v-model="form.isSale">
  40. <el-option
  41. label="全部"
  42. value=""
  43. ></el-option>
  44. <el-option
  45. label="是"
  46. :value="1"
  47. ></el-option>
  48. <el-option
  49. label="否"
  50. :value="0"
  51. ></el-option>
  52. </el-select>
  53. </el-form-item>
  54. <el-form-item
  55. label="景点"
  56. prop="scenicId"
  57. >
  58. <el-select v-model="form.scenicId">
  59. <el-option
  60. value=""
  61. label="全部"
  62. ></el-option>
  63. <el-option
  64. v-for="item in scenicList"
  65. :key="item.id"
  66. :value="item.id"
  67. :label="item.name">
  68. </el-option>
  69. </el-select>
  70. </el-form-item>
  71. <div class="btn-wrap">
  72. <el-button
  73. @click="reset"
  74. >
  75. 重置
  76. </el-button>
  77. <el-button
  78. type="primary"
  79. @click="getTicketTypeList"
  80. >
  81. 搜索
  82. </el-button>
  83. <el-button
  84. type="primary"
  85. @click="showDialog('add')"
  86. >
  87. 新增票种
  88. </el-button>
  89. </div>
  90. </el-form>
  91. <div class="table-box">
  92. <!-- <div class="block-title">
  93. 现有票种
  94. </div> -->
  95. <el-table
  96. border
  97. stripe
  98. v-loading="loading"
  99. :data="tableData"
  100. >
  101. <el-table-column
  102. prop="id"
  103. label="票种ID"
  104. width="80"
  105. >
  106. </el-table-column>
  107. <el-table-column
  108. prop="name"
  109. label="票种名称"
  110. >
  111. </el-table-column>
  112. <el-table-column
  113. label="票种分类"
  114. >
  115. <template slot-scope="scope">
  116. {{ getTicketCate(scope.row.category) }}
  117. </template>
  118. </el-table-column>
  119. <el-table-column
  120. prop="price"
  121. label="窗口价格"
  122. >
  123. </el-table-column>
  124. <el-table-column
  125. label="在售状态"
  126. >
  127. <template slot-scope="scope">
  128. <el-switch
  129. @change="handelStatusChange($event,scope.row)"
  130. v-model="scope.row.isSale"
  131. active-text="在售"
  132. inactive-text="禁售"
  133. :active-value="1"
  134. :inactive-value="0">
  135. </el-switch>
  136. </template>
  137. </el-table-column>
  138. <el-table-column
  139. prop="useDate"
  140. label="使用日期"
  141. >
  142. <template slot-scope="scope">
  143. {{ scope.row.useDateStart }} - {{ scope.row.useDateEnd }}
  144. </template>
  145. </el-table-column>
  146. <el-table-column
  147. prop="checkType"
  148. label="核销凭证"
  149. >
  150. <template slot-scope="scope">
  151. {{ scope.row.checkType | checkTypeFilter }}
  152. </template>
  153. </el-table-column>
  154. <el-table-column
  155. prop="checkRule"
  156. label="核销规则"
  157. >
  158. <template slot-scope="scope">
  159. {{ scope.row.checkRule | checkRuleFilter }}
  160. </template>
  161. </el-table-column>
  162. <el-table-column
  163. prop="checkScenic"
  164. label="核销景点"
  165. >
  166. <template slot-scope="scope">
  167. {{ getScenicName(scope.row.ticketTypeScenicList) }}
  168. </template>
  169. </el-table-column>
  170. <el-table-column
  171. label="团散"
  172. >
  173. <template slot-scope="scope">
  174. {{ scope.row.teamIndividual === 1 ? '散客' : scope.row.teamIndividual === 2 ? '团队' : '不限' }}
  175. </template>
  176. </el-table-column>
  177. <el-table-column
  178. label="操作"
  179. >
  180. <template slot-scope="scope">
  181. <el-link
  182. type="primary"
  183. size="small"
  184. @click="showDialog('edit', scope.row)"
  185. >
  186. 编辑
  187. </el-link>
  188. <el-link
  189. type="primary"
  190. size="small"
  191. @click="showDialog('priceCalendar', scope.row)"
  192. >
  193. 价格日历
  194. </el-link>
  195. <br>
  196. <el-link
  197. type="primary"
  198. size="small"
  199. @click="showDialog('add', itemWithoutCategoryAndShorthand(scope.row))"
  200. >
  201. 复制
  202. </el-link>
  203. <el-link
  204. type="primary"
  205. size="small"
  206. @click="deleteTicket(scope.row.id)"
  207. >
  208. 删除
  209. </el-link>
  210. </template>
  211. </el-table-column>
  212. </el-table>
  213. <el-pagination
  214. background
  215. :current-page.sync="form.pageNum"
  216. :page-sizes="[10, 20, 50, 100]"
  217. :page-size="form.pageSize"
  218. @size-change="handleSizeChange"
  219. layout="total, sizes, prev, pager, next, jumper"
  220. @current-change="getTicketTypeList()"
  221. :total="total">
  222. </el-pagination>
  223. </div>
  224. <!-- 票种弹框 -->
  225. <TicketDialog
  226. ref="ticketDialogRef"
  227. :dialog-type="dialogType"
  228. :ticket-item="curItem"
  229. :scenic-list="scenicList"
  230. :ticket-tag-list="ticketTagList"
  231. :member-type-list="memberTypeList"
  232. :invoice-seller="InvoiceSeller"
  233. @updateList="getTicketTypeList"
  234. ></TicketDialog>
  235. <PriceCalendar ref="priceCalendarRef"></PriceCalendar>
  236. </div>
  237. </template>
  238. <script>
  239. import moment from 'moment'
  240. import { getTicketTypeList, deleteTicket, getScenic, updateTicket, listInvoiceSales } from '@/api/ticketType'
  241. import { getTicketTypeTagList } from '@/api/ticketTypeTag'
  242. import { getMemberType } from '@/api/member'
  243. import TicketDialog from './ticket/Dialog'
  244. import PriceCalendar from './ticket/PriceCalendar'
  245. import { TicketCategory } from './ticket/constant'
  246. import { cloneDeep } from '@/utils'
  247. const getCheckTypeName = (type) => {
  248. switch (type) {
  249. case 'qrcode':
  250. return '二维码'
  251. case 'face':
  252. return '人脸'
  253. case 'card':
  254. return 'IC卡'
  255. case 'idcard':
  256. return '身份证'
  257. case 'manual_check':
  258. return '人工检票'
  259. case 'print_check':
  260. return '取票即检票'
  261. case 'buy_check':
  262. return '购买即检票'
  263. default:
  264. break
  265. }
  266. }
  267. const getCheckRuleName = (rule) => {
  268. switch (rule) {
  269. case '1':
  270. return '人工,检票设备核销'
  271. case '2':
  272. return '取票即核销'
  273. case '3':
  274. return '购买即核销'
  275. default:
  276. break
  277. }
  278. }
  279. export default {
  280. computed: {
  281. ticketCategory () {
  282. return this.$store.state.app.ticketCategory
  283. }
  284. },
  285. data () {
  286. return {
  287. form: {
  288. ticketTypeName: '',
  289. scenicId: '',
  290. category: '',
  291. isSale: '',
  292. pageNum: 1,
  293. pageSize: 10
  294. },
  295. loading: false,
  296. total: 0,
  297. tableData: [],
  298. memberTypeList: [],
  299. dialogVisible: false,
  300. // 弹框类型 'add' 或 'edit'
  301. dialogType: '',
  302. curItem: {},
  303. scenicList: [],
  304. ticketTagList: [],
  305. ticketPricePlanList: [],
  306. InvoiceSeller: []
  307. }
  308. },
  309. filters: {
  310. runStopColor (input) {
  311. return input ? 'success' : 'danger'
  312. },
  313. checkTypeFilter (input) {
  314. if (!input) return ''
  315. const types = input.split(',')
  316. let result = ''
  317. types.forEach(type => {
  318. result += getCheckTypeName(type) + '、'
  319. })
  320. return result.slice(0, -1) // 去掉最后一个逗号
  321. },
  322. checkRuleFilter (input) {
  323. if (!input) return ''
  324. const rules = input.split(',')
  325. let result = ''
  326. rules.forEach(rule => {
  327. result += getCheckRuleName(rule) + '、'
  328. })
  329. return result.slice(0, -1) // 去掉最后一个逗号
  330. }
  331. },
  332. created () {
  333. this.getScenicList()
  334. this.getTicketTypeList()
  335. // this.getMemberList()
  336. // this.getTicketTagList()
  337. // this.getTicketPricePlanList()
  338. // this.getInvoiceSeller()
  339. },
  340. components: {
  341. TicketDialog,
  342. PriceCalendar
  343. },
  344. methods: {
  345. getScenicName (scenicList) {
  346. if (!scenicList || scenicList.length === 0) return ''
  347. return scenicList.map(s => s.scenicName).join('、')
  348. },
  349. getTicketCate (cate) {
  350. return TicketCategory[cate]
  351. },
  352. getInvoiceSeller () {
  353. listInvoiceSales({}).then(res => {
  354. this.InvoiceSeller = res?.data
  355. })
  356. },
  357. getTicketTagList () {
  358. getTicketTypeTagList({}).then(res => {
  359. this.ticketTagList = res.children
  360. })
  361. },
  362. // 获取景点列表
  363. async getScenicList () {
  364. const res = await getScenic()
  365. this.scenicList = res?.data.records || []
  366. },
  367. async showDialog (type, item) {
  368. if (type === 'priceCalendar') {
  369. this.$refs.priceCalendarRef.show(item)
  370. return
  371. }
  372. this.dialogType = type
  373. this.curItem = item
  374. this.$refs.ticketDialogRef.show(item, type)
  375. this.dialogVisible = true
  376. },
  377. // '创建票种失败,名称或助记符重复' 复制时去掉名称和助记符,待复制时用户填入
  378. itemWithoutCategoryAndShorthand (item) {
  379. const copyItem = cloneDeep(item)
  380. copyItem.name = '' // 名称
  381. copyItem.id = ''
  382. return copyItem
  383. },
  384. getMemberList () {
  385. getMemberType({
  386. pageNum: 0,
  387. pageSize: -1
  388. }).then(res => {
  389. this.memberTypeList = res.list
  390. })
  391. },
  392. handleSizeChange (size) {
  393. this.form.pageSize = size
  394. this.getTicketTypeList()
  395. },
  396. // 获取票种列表
  397. getTicketTypeList () {
  398. this.loading = true
  399. getTicketTypeList(this.form).then(res => {
  400. this.tableData = res.data.records.map(item => {
  401. item.scenics = item.ticketTypeScenicList?.map(s => s.scenicName).join('、')
  402. return item
  403. })
  404. this.total = res.data.total || 0
  405. }).finally(() => {
  406. this.loading = false
  407. })
  408. // 更新全局票种列表
  409. // this.$store.dispatch('getTicketTypeList')
  410. },
  411. // 删除票种
  412. deleteTicket (id) {
  413. this.$confirm('确定删除?', '提示', {
  414. confirmButtonText: '确定',
  415. cancelButtonText: '取消',
  416. type: 'warning'
  417. }).then(() => {
  418. deleteTicket(id).then(res => {
  419. this.$message.success('删除成功')
  420. this.getTicketTypeList()
  421. })
  422. })
  423. },
  424. reset () {
  425. this.$refs.form.resetFields()
  426. },
  427. offSale (data) {
  428. let form = Object.assign({}, data)
  429. form.isSale = 0
  430. updateTicket(form).then(res => {
  431. if (res.code !== '200') {
  432. this.$message.success(res.msg || '操作失败')
  433. return
  434. }
  435. this.$message.success('操作成功')
  436. this.getTicketTypeList()
  437. })
  438. },
  439. onSale (data) {
  440. let saleEnd = moment(data.saleDateEnd).hours(23).minutes(59).seconds(59)
  441. if (moment().isAfter(saleEnd)) {
  442. this.$alert('请修改票种可售日期', '票种已过期', {
  443. confirmButtonText: '知道了'
  444. })
  445. } else {
  446. let form = Object.assign({}, data)
  447. form.isSale = 1
  448. form.ticketTypeSourceList = form.source || []
  449. updateTicket(form).then(res => {
  450. if (res.code === '200') {
  451. this.$message.success('操作成功')
  452. this.getTicketTypeList()
  453. }
  454. })
  455. }
  456. },
  457. handleClose () {
  458. this.dialogVisible = false
  459. },
  460. handelStatusChange (value, ticket) {
  461. const currentTicket = cloneDeep(ticket)
  462. currentTicket.ticketTypeSourceList = ticket.ticketTypeSourceList || ticket.source || []
  463. if (!currentTicket.ticketTypeSourceList) {
  464. currentTicket.ticketTypeSourceList = []
  465. }
  466. if (!currentTicket.ticketTypeScenicList) {
  467. currentTicket.ticketTypeScenicList = []
  468. }
  469. if (!currentTicket.singleScenicList) {
  470. currentTicket.singleScenicList = []
  471. }
  472. if (!currentTicket.groupScenicList) {
  473. currentTicket.groupScenicList = []
  474. }
  475. if (!currentTicket.batchConfigList) {
  476. currentTicket.batchConfigList = []
  477. }
  478. let groupFlag = ''
  479. let groupIndex = 0
  480. currentTicket.ticketTypeScenicList.forEach(item => {
  481. if (!item.groupFlag) {
  482. currentTicket.singleScenicList.push({
  483. scenicId: item.scenicId,
  484. checkLimitTimes: item.checkLimitTimes,
  485. singleDayLimitTimes: item.singleDayLimitTimes
  486. })
  487. } else {
  488. if (groupFlag !== item.groupFlag) {
  489. groupFlag = item.groupFlag
  490. groupIndex += 1
  491. currentTicket.groupScenicList.push({
  492. scenicId: [item.scenicId],
  493. checkLimitTimes: item.checkLimitTimes,
  494. scenicLimitTimes: item.scenicLimitTimes,
  495. singleDayLimitTimes: item.singleDayLimitTimes
  496. })
  497. } else {
  498. currentTicket.groupScenicList[groupIndex - 1].scenicId.push(item.scenicId)
  499. }
  500. }
  501. })
  502. const gsList = []
  503. const { groupScenicList } = currentTicket
  504. for (let i = 0; i < groupScenicList.length; i++) {
  505. const item = groupScenicList[i]
  506. const sList = item.scenicId.map(scenicId => {
  507. return {
  508. scenicId,
  509. checkLimitTimes: item.checkLimitTimes,
  510. singleDayLimitTimes: item.singleDayLimitTimes || -1,
  511. scenicLimitTimes: item.scenicLimitTimes || -1
  512. }
  513. })
  514. gsList.push(sList)
  515. }
  516. currentTicket.groupScenicList = gsList
  517. currentTicket.isSale = value
  518. updateTicket(currentTicket).then(res => {
  519. if (res.code === '200') {
  520. this.$message.success('操作成功')
  521. this.getTicketTypeList()
  522. }
  523. })
  524. }
  525. }
  526. }
  527. </script>
  528. <style lang="scss" scoped>
  529. .offSale-btn{
  530. color: #F56C6C
  531. }
  532. .onSale-btn{
  533. color: #67C23A
  534. }
  535. </style>