form16.vue 12 KB
<template>
  <div class="NCC-common-layout">
    <div class="NCC-common-layout-center">
      <!-- 筛选条件 -->
      <el-row class="NCC-common-search-box" :gutter="16">
        <el-form @submit.native.prevent>
          <el-col :span="6">
            <el-form-item label="开始时间">
              <el-date-picker 
                v-model="query.startTime" 
                type="datetime" 
                value-format="timestamp" 
                format="yyyy-MM-dd HH:mm:ss" 
                placeholder="开始时间"
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="结束时间">
              <el-date-picker 
                v-model="query.endTime" 
                type="datetime" 
                value-format="timestamp" 
                format="yyyy-MM-dd HH:mm:ss" 
                placeholder="结束时间"
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="门店">
              <el-select
                v-model="query.storeIds"
                placeholder="请选择门店"
                multiple
                filterable
                clearable
                :style='{"width":"100%"}'>
                <el-option
                  v-for="store in storeOptions"
                  :key="store.id"
                  :label="store.dm"
                  :value="store.id">
                </el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="拓客活动">
              <el-select
                v-model="query.eventId"
                placeholder="请选择拓客活动"
                filterable
                clearable
                :style='{"width":"100%"}'>
                <el-option
                  v-for="event in eventOptions"
                  :key="event.id"
                  :label="event.eventName"
                  :value="event.id">
                </el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item>
              <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
              <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
            </el-form-item>
          </el-col>
        </el-form>
      </el-row>

      <!-- 数据表格 -->
      <div class="NCC-common-layout-main NCC-flex-main">
        <el-table v-loading="listLoading" :data="list" border stripe @sort-change="handleSortChange">
          <el-table-column prop="StoreName" label="门店名称" min-width="150">
            <template slot-scope="scope">
              <i class="el-icon-shop" style="margin-right: 4px; color: #409EFF;"></i>
              <span>{{ scope.row.StoreName || '无' }}</span>
            </template>
          </el-table-column>
          <el-table-column prop="TotalCount" label="总数量" min-width="120" sortable="custom" >
            <template slot-scope="scope">
              <i class="el-icon-s-data" style="margin-right: 4px; color: #67C23A;"></i>
              <span>{{ scope.row.TotalCount !== null && scope.row.TotalCount !== undefined ? scope.row.TotalCount : 0 }}</span>
            </template>
          </el-table-column>
          <el-table-column prop="TkMemberCount" label="拓客会员数" min-width="130" sortable="custom" >
            <template slot-scope="scope">
              <i class="el-icon-user-solid" style="margin-right: 4px; color: #409EFF;"></i>
              <span>{{ scope.row.TkMemberCount !== null && scope.row.TkMemberCount !== undefined ? scope.row.TkMemberCount : 0 }}</span>
            </template>
          </el-table-column>
          <el-table-column prop="InviteCount" label="邀约数" min-width="120" sortable="custom" >
            <template slot-scope="scope">
              <i class="el-icon-phone-outline" style="margin-right: 4px; color: #67C23A;"></i>
              <span>{{ scope.row.InviteCount !== null && scope.row.InviteCount !== undefined ? scope.row.InviteCount : 0 }}</span>
            </template>
          </el-table-column>
          <el-table-column prop="AppointmentCount" label="预约数" min-width="120" sortable="custom" >
            <template slot-scope="scope">
              <i class="el-icon-date" style="margin-right: 4px; color: #67C23A;"></i>
              <span>{{ scope.row.AppointmentCount !== null && scope.row.AppointmentCount !== undefined ? scope.row.AppointmentCount : 0 }}</span>
            </template>
          </el-table-column>
          <el-table-column prop="ConsumeCount" label="消耗数" min-width="120" sortable="custom" >
            <template slot-scope="scope">
              <i class="el-icon-shopping-cart-2" style="margin-right: 4px; color: #67C23A;"></i>
              <span>{{ scope.row.ConsumeCount !== null && scope.row.ConsumeCount !== undefined ? scope.row.ConsumeCount : 0 }}</span>
            </template>
          </el-table-column>
          <el-table-column prop="BillingCount" label="开单数" min-width="120" sortable="custom" >
            <template slot-scope="scope">
              <i class="el-icon-document" style="margin-right: 4px; color: #67C23A;"></i>
              <span>{{ scope.row.BillingCount !== null && scope.row.BillingCount !== undefined ? scope.row.BillingCount : 0 }}</span>
            </template>
          </el-table-column>
          <el-table-column prop="BillingAmount" label="开单金额" min-width="130" sortable="custom" >
            <template slot-scope="scope">
              <i class="el-icon-money" style="margin-right: 4px; color: #67C23A;"></i>
              <span class="amount-paid">¥{{ formatMoney(scope.row.BillingAmount) }}</span>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </div>
  </div>
</template>

<script>
import request from '@/utils/request'
import { getLqEventList } from '@/api/extend/lqevent'

export default {
  name: 'StoreStatistics',
  data() {
    return {
      query: {
        startTime: undefined,
        endTime: undefined,
        storeIds: [],
        eventId: undefined
      },
      storeOptions: [],
      eventOptions: [],
      list: [],
      originalList: [], // 保存原始数据,用于取消排序时恢复
      listLoading: false,
      sortProp: '',
      sortOrder: ''
    }
  },
  created() {
    this.setDefaultTimeRange()
    this.initStoreOptions()
    this.initEventOptions()
    this.search()
  },
  methods: {
    // 设置默认时间范围(本月1号到现在)
    setDefaultTimeRange() {
      const now = new Date()
      const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
      
      this.query.startTime = firstDayOfMonth.getTime()
      this.query.endTime = now.getTime()
    },

    // 初始化门店选项
    initStoreOptions() {
      request({
        url: '/api/Extend/LqMdxx',
        method: 'GET',
        data: {
          currentPage: 1,
          pageSize: 1000
        }
      }).then(res => {
        if (res.data && res.data.list) {
          this.storeOptions = res.data.list
        }
      }).catch(() => {
        this.storeOptions = []
      })
    },

    // 初始化拓客活动选项
    initEventOptions() {
      getLqEventList({
        page: 1,
        rows: 1000
      }).then(res => {
        if (res.code == 200 && res.data && res.data.list) {
          this.eventOptions = res.data.list
        } else {
          this.eventOptions = []
        }
      }).catch(() => {
        this.eventOptions = []
      })
    },

    // 处理排序变化
    handleSortChange({ column, prop, order }) {
      if (!order) {
        // 取消排序,恢复原始顺序
        this.sortProp = ''
        this.sortOrder = ''
        if (this.originalList.length > 0) {
          this.list = JSON.parse(JSON.stringify(this.originalList))
        }
        return
      }
      
      this.sortProp = prop
      this.sortOrder = order === 'ascending' ? 'asc' : 'desc'
      
      this.sortList()
    },

    // 对列表进行排序
    sortList() {
      if (!this.sortProp || !this.sortOrder) {
        return
      }

      // 从原始数据开始排序,确保每次排序都是基于原始数据
      const sourceList = this.originalList.length > 0 ? JSON.parse(JSON.stringify(this.originalList)) : JSON.parse(JSON.stringify(this.list))
      
      // 创建新数组并排序
      const sortedList = sourceList.sort((a, b) => {
        let aVal = a[this.sortProp]
        let bVal = b[this.sortProp]

        // 处理空值
        if (aVal === null || aVal === undefined) aVal = 0
        if (bVal === null || bVal === undefined) bVal = 0

        // 数值类型直接比较
        if (typeof aVal === 'number' && typeof bVal === 'number') {
          return this.sortOrder === 'asc' ? aVal - bVal : bVal - aVal
        }

        // 字符串类型
        aVal = String(aVal || '')
        bVal = String(bVal || '')
        
        if (this.sortOrder === 'asc') {
          return aVal.localeCompare(bVal)
        } else {
          return bVal.localeCompare(aVal)
        }
      })
      
      // 重新赋值整个数组以触发响应式更新
      this.list = sortedList
      this.$nextTick(() => {
        this.$forceUpdate()
      })
    },

    // 查询数据
    search() {
      this.listLoading = true

      const params = {}

      if (this.query.startTime) {
        params.startTime = this.formatDateTimeISO(this.query.startTime)
      }

      if (this.query.endTime) {
        params.endTime = this.formatDateTimeISO(this.query.endTime)
      }

      if (this.query.storeIds && this.query.storeIds.length > 0) {
        params.storeIds = this.query.storeIds
      }

      if (this.query.eventId) {
        params.eventId = this.query.eventId
      }

      request({
        url: '/api/Extend/lqstatistics/get-store-statistics-list',
        method: 'POST',
        data: params
      }).then(res => {
        if (res.data && res.data.list) {
          this.list = res.data.list
          this.originalList = [...res.data.list] // 保存原始数据
        } else {
          this.list = []
          this.originalList = []
        }
        this.listLoading = false
      }).catch(err => {
        console.error('查询失败:', err)
        this.$message({
          type: 'error',
          message: '查询失败,请重试',
          duration: 1500
        })
        this.listLoading = false
        this.list = []
        this.originalList = []
      })
    },

    // 重置查询条件
    reset() {
      this.query = {
        startTime: undefined,
        endTime: undefined,
        storeIds: [],
        eventId: undefined
      }
      this.setDefaultTimeRange()
      this.sortProp = ''
      this.sortOrder = ''
      this.list = []
      this.originalList = []
      this.search()
    },

    // 格式化金额
    formatMoney(amount) {
      if (!amount && amount !== 0) return '0.00'
      return Number(amount).toFixed(2)
    },

    // 格式化日期时间(用于API传参,ISO格式,保持本地时间不转换时区)
    formatDateTimeISO(timestamp) {
      if (!timestamp) return ''
      const date = new Date(timestamp)
      // 生成本地时间的 ISO 格式字符串,不进行时区转换
      // 使用本地时间的各个部分,格式化为 ISO 格式,末尾加 Z 以符合接口要求
      const year = date.getFullYear()
      const month = String(date.getMonth() + 1).padStart(2, '0')
      const day = String(date.getDate()).padStart(2, '0')
      const hours = String(date.getHours()).padStart(2, '0')
      const minutes = String(date.getMinutes()).padStart(2, '0')
      const seconds = String(date.getSeconds()).padStart(2, '0')
      const milliseconds = String(date.getMilliseconds()).padStart(3, '0')
      // 使用本地时间的值,格式化为 ISO 格式(末尾加 Z 以符合接口格式要求)
      return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}Z`
    }
  }
}
</script>

<style lang="scss" scoped>
.amount-paid {
  color: #67C23A;
  font-weight: 500;
}
</style>