Просмотр исходного кода

feat(ticketSetting): implement ticket type group management

Add functionality to manage ticket type groups, including creating,
editing, deleting, and assigning ticket types to specific groups.

- Add `ticketTypeGroup` API module for group CRUD and batch assignment
- Implement `TicketTypeGroup` management interface with list, dialog,
  and assignment components
- Add print template binding to ticket dialogs
- Update router to include the new ticket type group management route
- Refactor ticket dialog to use local scenic list and async data fetching
LaveyD 1 неделя назад
Родитель
Сommit
595194bad6

+ 3 - 1
.gitignore

@@ -25,4 +25,6 @@ yarn-error.log*
 /dist_electron
 /dist_electron_other
 
-node_modules*.zip
+node_modules*.zip
+
+.kilo

+ 26 - 0
src/api/ticketTypeGroup.js

@@ -0,0 +1,26 @@
+import http from '@/utils/request'
+
+// 获取票种分组列表
+export function getTicketTypeGroupList (params) {
+  return http.post('/admin/ticketTypeGroup', { data: params })
+}
+
+// 新增分组
+export function addTicketTypeGroup (params) {
+  return http.post('/admin/ticketTypeGroup/add', { data: params })
+}
+
+// 编辑分组
+export function updateTicketTypeGroup (params) {
+  return http.post('/admin/ticketTypeGroup/update', { data: params })
+}
+
+// 删除分组
+export function deleteTicketTypeGroup (idList) {
+  return http.post('/admin/ticketTypeGroup/delete', { data: { idList } })
+}
+
+// 批量修改分组内票种列表
+export function batchAddTicketType (params) {
+  return http.post('/admin/ticketTypeGroup/batchAdd', { data: params })
+}

+ 1 - 1
src/globalData.vue

@@ -14,7 +14,7 @@ export default {
   created () {
     getIp()
     // 获取打印模板
-    this.getReceiptPrintModel()
+    // this.getReceiptPrintModel()
     this.getTicketPrintModelList()
 
     this.$store.dispatch('setTravelAgencyType')

+ 3 - 1
src/pages/sellManage/common/TicketList.vue

@@ -155,11 +155,12 @@
               停售
             </div>
             <div class="title">
+              <i class="el-icon-info" style="cursor: pointer; margin-right: 8px;" @click.stop="showTicketInfo(item)"></i>
               <el-tooltip
                 :content="getTicketTip(item)"
                 placement="top"
                 effect="dark">
-                <span @click.stop="showTicketInfo(item)">
+                <span>
                   {{ item.name }}
                 </span>
               </el-tooltip>
@@ -819,6 +820,7 @@ export default {
           .title {
             width: calc(100% - 80px);
             margin-top: 4px;
+            line-height: 1;
 
             span {
               overflow: hidden;

+ 4 - 7
src/pages/ticketSetting/ticket.vue

@@ -369,14 +369,11 @@ export default {
       })
     },
     // 获取景点列表
-    getScenicList () {
-      getScenic().then(res => {
-        this.scenicList = res?.data.records || []
-      })
+    async getScenicList () {
+      const res = await getScenic()
+      this.scenicList = res?.data.records || []
     },
-    showDialog (type, item) {
-      this.$store.dispatch('getScenicList')
-
+    async showDialog (type, item) {
       if (type === 'priceCalendar') {
         this.$refs.priceCalendarRef.show(item)
         return

+ 36 - 8
src/pages/ticketSetting/ticket/Dialog.vue

@@ -589,6 +589,19 @@
             </el-radio>
           </el-form-item>
 
+          <el-form-item
+            label="绑定打印模板"
+            prop="printTemplateId">
+            <el-select v-model="form.printTemplateId" placeholder="请选择打印模板">
+              <el-option
+                v-for="item in printTemplateList"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name">
+              </el-option>
+            </el-select>
+          </el-form-item>
+
           <template v-if="form.category === 'batch' && dialogType !== 'add'">
             <div class="block-title">
               时间场次
@@ -773,7 +786,7 @@
                     <el-select
                       v-model="scope.row.scenicId">
                       <el-option
-                        v-for="item in scenicList"
+                        v-for="item in localScenicList"
                         :key="item.id"
                         :label="item.name"
                         :value="item.id"
@@ -842,7 +855,7 @@
                       v-model="scope.row.scenicId"
                       multiple>
                       <el-option
-                        v-for="item in scenicList"
+                        v-for="item in localScenicList"
                         :key="item.id"
                         :label="item.name"
                         :value="item.id"
@@ -1074,7 +1087,7 @@
                     <el-select
                       v-model="scope.row.scenicId">
                       <el-option
-                        v-for="item in scenicList"
+                        v-for="item in localScenicList"
                         :key="item.id"
                         :label="item.name"
                         :value="item.id"
@@ -1190,9 +1203,10 @@
 </template>
 
 <script>
-import { addTicket, updateTicket, queueList, batchUpdateTicketBatch, delTicketBatch } from '@/api/ticketType'
+import { addTicket, updateTicket, queueList, batchUpdateTicketBatch, delTicketBatch, getScenic } from '@/api/ticketType'
 import { getGhSubMerchantList } from '@/api/payChannel'
 import { getInvoiceSellerList } from '@/api/invoice'
+import { getTicketPrintModelList } from '@/api/printModal'
 import { getLabelName } from '@/utils'
 import { cloneDeep } from 'lodash'
 import moment from 'moment'
@@ -1243,7 +1257,8 @@ const defaultForm = {
   payGonghangTicketTypeMerchantList: [], // 工行子商户列表
   ticketTypeInvoiceSellerList: [], // 发票销售方列表
   ticketTypeScenicSplitRequests: [], // 子分账景点
-  teamIndividual: 0 // 团散:0-不限 1-散客 2-团队
+  teamIndividual: 0, // 团散:0-不限 1-散客 2-团队
+  printTemplateId: null // 绑定打印模板ID
 }
 
 export default {
@@ -1287,6 +1302,7 @@ export default {
       dialogType: 'add',
       projectName: process.env.VUE_APP_PROJECT,
       visible: false,
+      localScenicList: [],
       pickerOptions: {
         disabledDate: (theTime) => {
           let startTime = moment().format('YYYY-MM-DD') + ' 00:00:00'
@@ -1318,7 +1334,8 @@ export default {
       currGroupScenicInfo: [],
       groupScenicMap: new Map(),
       ghSubMerchantList: [], // 工行子商户列表
-      invoiceSellerList: [] // 发票销售方列表
+      invoiceSellerList: [], // 发票销售方列表
+      printTemplateList: [] // 打印模板列表
     }
   },
   watch: {
@@ -1343,7 +1360,10 @@ export default {
     }
   },
   methods: {
-    show (ticketItem, type) {
+    async show (ticketItem, type) {
+      // 每次打开时拉取最新景点列表
+      const res = await getScenic()
+      this.localScenicList = res?.data?.records || []
       // this.reset()
       this.ticketItem = ticketItem
       this.dialogType = type
@@ -1437,6 +1457,7 @@ export default {
 
       this.getGhSubMerchantList()
       this.getInvoiceSellerList()
+      this.loadPrintTemplates()
 
       this.visible = true
     },
@@ -1450,6 +1471,13 @@ export default {
       const res = await getInvoiceSellerList()
       this.invoiceSellerList = res.data || []
     },
+    async loadPrintTemplates () {
+      const res = await getTicketPrintModelList(this.form)
+      this.printTemplateList = res?.data?.records || []
+      if (this.printTemplateList.length > 0 && this.form.printTemplateId == null) {
+        this.form.printTemplateId = this.printTemplateList[0].id
+      }
+    },
     updateDiscountRule: function () {
       let man = parseInt(this.man)
       let jian = parseInt(this.jian)
@@ -1547,7 +1575,7 @@ export default {
       } else {
         this.currGroupScenicInfo = []
         item.scenicId.forEach(id => {
-          const scene = this.scenicList.find((scene) => scene.id === id)
+          const scene = this.localScenicList.find((scene) => scene.id === id)
 
           if (scene) {
             this.currGroupScenicInfo.push({

+ 123 - 0
src/pages/ticketSetting/ticketTypeGroup/Dialog.vue

@@ -0,0 +1,123 @@
+<template>
+  <el-dialog
+    :title="title"
+    :visible.sync="visible"
+    width="600px"
+    @close="handleClose">
+    <el-form
+      :model="form"
+      ref="form"
+      class="form-wrap"
+      label-width="100px">
+      <div class="dialog-info">
+        <el-form-item
+          style="width:100%"
+          label="分组名称"
+          prop="name"
+          :rules="[{ required: true, message: '请输入分组名称', trigger: 'blur' }]"
+        >
+          <el-input v-model="form.name"></el-input>
+        </el-form-item>
+        <el-form-item
+          style="width:100%"
+          label="描述"
+          prop="description">
+          <el-input
+            type="textarea"
+            v-model="form.description"></el-input>
+        </el-form-item>
+      </div>
+    </el-form>
+
+    <div slot="footer">
+      <el-button @click="handleClose">
+        取 消
+      </el-button>
+      <el-button
+        type="primary"
+        :loading="loading"
+        @click="handleSubmit">
+        确 定
+      </el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { addTicketTypeGroup, updateTicketTypeGroup } from '@/api/ticketTypeGroup'
+
+const defaultForm = {
+  id: '',
+  name: '',
+  description: ''
+}
+
+export default {
+  data () {
+    return {
+      title: '',
+      type: '',
+      visible: false,
+      loading: false,
+      form: {
+        ...defaultForm
+      }
+    }
+  },
+  methods: {
+    show (type, row) {
+      this.type = type
+      this.title = type === 'edit' ? '编辑分组' : '新增分组'
+      this.visible = true
+
+      this.$nextTick(() => {
+        this.reset()
+        if (type === 'edit' && row) {
+          this.form = Object.assign({}, this.form, JSON.parse(JSON.stringify(row)))
+        } else {
+          this.form = JSON.parse(JSON.stringify(defaultForm))
+        }
+      })
+    },
+    reset () {
+      this.$refs.form.resetFields()
+    },
+    handleClose () {
+      this.visible = false
+    },
+    handleSubmit () {
+      this.$refs.form.validate(valid => {
+        if (valid) {
+          let params = JSON.parse(JSON.stringify(this.form))
+          this.loading = true
+          if (this.type === 'edit') {
+            updateTicketTypeGroup(params).then(res => {
+              this.$message.success('修改成功')
+              this.$emit('update')
+              this.handleClose()
+            }).finally(() => {
+              this.loading = false
+            })
+          } else {
+            addTicketTypeGroup(params).then(res => {
+              this.$message.success('新增成功')
+              this.$emit('update')
+              this.handleClose()
+            }).finally(() => {
+              this.loading = false
+            })
+          }
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.form-wrap {
+  .el-input,.el-select,.el-date-editor,.el-date-editor.el-input,.el-input-number {
+    width: 100%;
+  }
+}
+</style>

+ 107 - 0
src/pages/ticketSetting/ticketTypeGroup/TicketAssignDialog.vue

@@ -0,0 +1,107 @@
+<template>
+  <el-dialog
+    :title="title"
+    :visible.sync="visible"
+    width="700px"
+    @close="handleClose"
+  >
+    <div style="margin-bottom: 10px;">
+      当前分组:{{ groupName }}
+    </div>
+    <el-form :model="form" ref="form" label-width="100px">
+      <el-form-item
+        label="选择票种"
+        prop="ticketTypeIds"
+        :rules="[{ required: true, message: '请至少选择一个票种', trigger: 'change' }]"
+      >
+        <el-select
+          v-model="form.ticketTypeIds"
+          multiple
+          filterable
+          placeholder="请选择票种"
+          style="width: 100%"
+        >
+          <el-option
+            v-for="item in ticketTypeList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          >
+          </el-option>
+        </el-select>
+      </el-form-item>
+    </el-form>
+
+    <div slot="footer">
+      <el-button @click="handleClose">
+        取 消
+      </el-button>
+      <el-button
+        type="primary"
+        :loading="loading"
+        @click="handleSubmit"
+      >
+        确 定
+      </el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { batchAddTicketType } from '@/api/ticketTypeGroup'
+import { getTicketTypeList } from '@/api/ticketType'
+
+export default {
+  data () {
+    return {
+      title: '分配票种',
+      visible: false,
+      loading: false,
+      groupId: '',
+      groupName: '',
+      form: {
+        ticketTypeIds: []
+      },
+      ticketTypeList: []
+    }
+  },
+  methods: {
+    show (row) {
+      this.groupId = row.id
+      this.groupName = row.name
+      this.visible = true
+      this.form.ticketTypeIds = (row.ticketTypes || []).map(t => t.id)
+      this.getTicketTypeList()
+    },
+    getTicketTypeList () {
+      getTicketTypeList({
+        pageNum: 1,
+        pageSize: 1000
+      }).then(res => {
+        this.ticketTypeList = res?.data?.records || []
+      })
+    },
+    handleClose () {
+      this.visible = false
+      this.form.ticketTypeIds = []
+    },
+    handleSubmit () {
+      if (this.form.ticketTypeIds.length === 0) {
+        this.$message.warning('请至少选择一个票种')
+        return
+      }
+      this.loading = true
+      batchAddTicketType({
+        groupId: this.groupId,
+        ticketTypeIds: this.form.ticketTypeIds
+      }).then(res => {
+        this.$message.success('分配成功')
+        this.$emit('update')
+        this.handleClose()
+      }).finally(() => {
+        this.loading = false
+      })
+    }
+  }
+}
+</script>

+ 172 - 0
src/pages/ticketSetting/ticketTypeGroup/index.vue

@@ -0,0 +1,172 @@
+<template>
+  <div class="form-wrap">
+    <div class="table-box">
+      <div class="block-title">
+        票种分组管理
+        <el-button
+          type="primary"
+          style="margin-right:10px"
+          @click="showDialog('add')"
+        >
+          新增
+        </el-button>
+      </div>
+
+      <el-table
+        border
+        stripe
+        v-loading="loading"
+        :data="tableData"
+      >
+        <el-table-column
+          prop="id"
+          label="编号"
+          width="80"
+        >
+        </el-table-column>
+        <el-table-column
+          prop="name"
+          label="分组名称"
+        >
+        </el-table-column>
+        <el-table-column
+          prop="description"
+          label="描述"
+        >
+        </el-table-column>
+        <el-table-column
+          label="票种数量"
+          width="100"
+        >
+          <template slot-scope="scope">
+            {{ scope.row.ticketTypes ? scope.row.ticketTypes.length : 0 }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="createTime"
+          label="创建时间"
+          width="180"
+        >
+        </el-table-column>
+        <el-table-column
+          label="操作"
+          width="220"
+        >
+          <template slot-scope="scope">
+            <el-link
+              type="primary"
+              size="small"
+              @click="showDialog('edit', scope.row)"
+            >
+              编辑
+            </el-link>
+            <el-link
+              type="primary"
+              size="small"
+              @click="assignTickets(scope.row)"
+            >
+              分配票种
+            </el-link>
+            <el-link
+              type="primary"
+              size="small"
+              @click="handleDelete(scope.row.id)"
+            >
+              删除
+            </el-link>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        background
+        :current-page.sync="form.pageNum"
+        :page-sizes="[10, 20, 50, 100]"
+        :page-size="form.pageSize"
+        @size-change="handleSizeChange"
+        layout="total, sizes, prev, pager, next, jumper"
+        @current-change="getList()"
+        :total="total">
+      </el-pagination>
+    </div>
+
+    <Dialog
+      ref="dialog"
+      @update="getList"
+    ></Dialog>
+
+    <TicketAssignDialog
+      ref="ticketAssignDialog"
+      @update="getList"
+    ></TicketAssignDialog>
+  </div>
+</template>
+
+<script>
+import { getTicketTypeGroupList, deleteTicketTypeGroup } from '@/api/ticketTypeGroup'
+import Dialog from './Dialog'
+import TicketAssignDialog from './TicketAssignDialog'
+
+export default {
+  components: {
+    Dialog,
+    TicketAssignDialog
+  },
+  data () {
+    return {
+      form: {
+        pageNum: 1,
+        pageSize: 10
+      },
+      loading: false,
+      total: 0,
+      tableData: []
+    }
+  },
+  created () {
+    this.getList()
+  },
+  methods: {
+    getList () {
+      this.loading = true
+      let params = JSON.parse(JSON.stringify(this.form))
+      getTicketTypeGroupList(params).then(res => {
+        this.tableData = res?.data?.records || []
+        this.total = res?.data?.total || 0
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    handleSizeChange (size) {
+      this.form.pageSize = size
+      this.getList()
+    },
+    showDialog (type, row) {
+      this.$refs.dialog.show(type, row)
+    },
+    assignTickets (row) {
+      this.$refs.ticketAssignDialog.show(row)
+    },
+    handleDelete (id) {
+      this.$confirm('确认删除该分组吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        deleteTicketTypeGroup([id]).then(res => {
+          this.$message.success('删除成功')
+          this.getList()
+        })
+      }).catch(() => {})
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.form-wrap {
+  .el-input,.el-select,.el-date-editor,.el-date-editor.el-input,.el-input-number {
+    width: 300px;
+  }
+}
+</style>

+ 6 - 0
src/router/index.js

@@ -144,6 +144,12 @@ let routerMap = [
         component: require('@/pages/ticketSetting/ticket').default,
         meta: { title: '票种管理', permissionName: 'ticket-manage:ticketType-manage' }
       },
+      {
+        path: 'ticketTypeGroup',
+        name: 'ticketTypeGroup',
+        component: require('@/pages/ticketSetting/ticketTypeGroup/index').default,
+        meta: { title: '票种分组管理', permissionName: 'ticket-manage:ticketTypeGroup-manage' }
+      },
       {
         path: 'scenic',
         name: 'scenic',