BookingCalendarDialog.vue 9.97 KB
<template>
  <el-dialog
    :visible.sync="visibleProxy"
    :show-close="false"
    width="90%"
    :close-on-click-modal="false"
    custom-class="booking-calendar-dialog"
    append-to-body
  >
    <div class="dialog-inner">
      <div class="dialog-header">
        <div class="dialog-title">预约日历</div>
        <span class="dialog-close" @click="visibleProxy = false"><i class="el-icon-close"></i></span>
      </div>

      <div class="dialog-search">
        <el-form @submit.native.prevent :inline="true" size="small">
          <el-form-item label="预约状态">
            <el-select v-model="query.F_Status" placeholder="预约状态" clearable style="width:140px">
              <el-option label="已确认" value="已确认" />
              <el-option label="已取消" value="已取消" />
              <el-option label="已预约" value="已预约" />
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="search">查询</el-button>
            <el-button @click="reset">重置</el-button>
          </el-form-item>
        </el-form>
      </div>

      <div class="dialog-content" v-loading="loading">
        <FullCalendar
          ref="fullCalendar"
          class="store-calendar"
          defaultView="dayGridMonth"
          :header="calendarHeader"
          :plugins="calendarPlugins"
          :weekends="true"
          :events="calendarEvents"
          locale="zh-cn"
          :buttonText="buttonText"
          :height="calendarHeight"
          :eventLimit="true"
          allDayText="全天"
          :editable="false"
          @datesRender="datesRender"
        />
      </div>
    </div>
  </el-dialog>
</template>

<script>
import FullCalendar from '@fullcalendar/vue'
import dayGridPlugin from '@fullcalendar/daygrid'
import timeGridPlugin from '@fullcalendar/timegrid'
import interactionPlugin from '@fullcalendar/interaction'

const MOCK_BOOKING = [
  { id: '1', storeName: '保利', yyrName: '贾琳', gkxm: '范佳佳', yysj: '2026-02-11T11:00:00', yyjs: '2026-02-11T11:30:00', F_Status: '已预约', yyjksName: '贾琳' },
  { id: '2', storeName: '保利', yyrName: '贾琳', gkxm: '黄仕碧', yysj: '2026-02-11T15:00:00', yyjs: '2026-02-11T15:30:00', F_Status: '已预约', yyjksName: '贾琳' },
  { id: '3', storeName: '保利', yyrName: '贾琳', gkxm: '赵丽', yysj: '2026-02-11T17:00:00', yyjs: '2026-02-11T17:30:00', F_Status: '已确认', yyjksName: '贾琳' },
  { id: '4', storeName: '468', yyrName: '刘恬恬', gkxm: '王英', yysj: '2026-02-11T14:00:00', yyjs: '2026-02-11T14:30:00', F_Status: '已预约', yyjksName: '刘恬恬' },
  { id: '5', storeName: '保利', yyrName: '贾琳', gkxm: '罗建琼', yysj: '2026-02-11T10:30:00', yyjs: '2026-02-11T11:00:00', F_Status: '已确认', yyjksName: '贾琳' },
  { id: '6', storeName: '保利', yyrName: '贾琳', gkxm: '沈丽', yysj: '2026-02-10T17:00:00', yyjs: '2026-02-10T17:30:00', F_Status: '已预约', yyjksName: '贾琳' },
  { id: '7', storeName: '静居寺', yyrName: '董顺秀', gkxm: '陈晴', yysj: '2026-02-11T09:30:00', yyjs: '2026-02-11T10:00:00', F_Status: '已取消', yyjksName: '董顺秀' },
  { id: '8', storeName: '静居寺', yyrName: '董顺秀', gkxm: '胡蝶', yysj: '2026-02-10T17:00:00', yyjs: '2026-02-10T17:30:00', F_Status: '已预约', yyjksName: '董顺秀' },
  { id: '9', storeName: '静居寺', yyrName: '董顺秀', gkxm: '魏海燕', yysj: '2026-02-10T14:00:00', yyjs: '2026-02-10T14:30:00', F_Status: '已确认', yyjksName: '董顺秀' },
  { id: '10', storeName: '静居寺', yyrName: '董顺秀', gkxm: '肖丛娇', yysj: '2026-02-10T11:00:00', yyjs: '2026-02-10T11:30:00', F_Status: '已预约', yyjksName: '董顺秀' }
]

export default {
  name: 'BookingCalendarDialog',
  components: { FullCalendar },
  props: { visible: { type: Boolean, default: false } },
  data() {
    return {
      loading: false,
      mockData: MOCK_BOOKING,
      query: { F_Status: undefined },
      calendarPlugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
      calendarEvents: [],
      calendarHeader: { left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,timeGridDay' },
      buttonText: { today: '今日', month: '月', week: '周', day: '日' },
      startTime: null,
      endTime: null,
      calendarHeight: 600
    }
  },
  computed: {
    visibleProxy: {
      get() { return this.visible },
      set(v) { this.$emit('update:visible', v) }
    }
  },
  watch: {
    visible(v) {
      if (v) {
        this.$nextTick(() => { this.calcHeight(); this.initData() })
      }
    }
  },
  mounted() {
    window.addEventListener('resize', this.calcHeight)
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.calcHeight)
  },
  methods: {
    calcHeight() {
      this.$nextTick(() => {
        const el = this.$el && this.$el.querySelector('.dialog-content')
        if (el) {
          this.calendarHeight = Math.max(el.clientHeight - 20, 500)
        }
      })
    },
    datesRender(info) {
      const view = info.view
      this.startTime = view.activeStart
      this.endTime = view.activeEnd
      this.initData()
    },
    initData() {
      this.loading = true
      setTimeout(() => {
        let filtered = [...this.mockData]
        if (this.query.F_Status) filtered = filtered.filter(r => r.F_Status === this.query.F_Status)
        if (this.startTime && this.endTime) {
          filtered = filtered.filter(r => {
            const t = new Date(r.yysj).getTime()
            return t >= this.startTime.getTime() && t < this.endTime.getTime()
          })
        }
        this.calendarEvents = filtered.map(item => {
          let color = '#409EFF'
          if (item.F_Status === '已确认') color = '#67C23A'
          else if (item.F_Status === '已取消') color = '#F56C6C'
          let title = `${item.gkxm || '无'} - ${item.yyrName || '无'}`
          if (item.yyjksName) title += ` (${item.yyjksName})`
          return {
            id: item.id,
            title,
            start: item.yysj ? new Date(item.yysj).toISOString() : new Date().toISOString(),
            end: item.yyjs ? new Date(item.yyjs).toISOString() : new Date().toISOString(),
            color,
            editable: false,
            allDay: false
          }
        })
        this.loading = false
      }, 300)
    },
    search() { this.initData() },
    reset() { this.query.F_Status = undefined; this.initData() }
  }
}
</script>

<style lang="scss">
@import '~@fullcalendar/core/main.css';
@import '~@fullcalendar/daygrid/main.css';
@import '~@fullcalendar/timegrid/main.css';

.booking-calendar-dialog .store-calendar {
  .fc-toolbar.fc-header-toolbar { padding: 16px 20px; margin-bottom: 0; border-bottom: 2px solid #f1f5f9; background: linear-gradient(135deg, rgba(59,130,246,0.02) 0%, rgba(96,165,250,0.02) 100%); }
  .fc-toolbar-title { font-size: 18px; font-weight: 700; color: #1e293b; }
  .fc-button-primary { background-color: #3b82f6; border-color: #3b82f6; border-radius: 10px; font-size: 13px; font-weight: 500; height: 34px; line-height: 34px; padding: 0 14px; transition: all 0.2s; &:hover { background: #2563eb; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(59,130,246,0.3); } }
  .fc-button-primary:not(:disabled):active, .fc-button-primary:not(:disabled).fc-button-active { background-color: #2563eb; border-color: #2563eb; box-shadow: 0 4px 12px rgba(59,130,246,0.3); }
  .fc-day-today { background: linear-gradient(135deg, rgba(59,130,246,0.05) 0%, rgba(96,165,250,0.05) 100%); .fc-daygrid-day-number { background: linear-gradient(135deg, #3b82f6, #2563eb); color: #fff; border-radius: 6px; box-shadow: 0 2px 8px rgba(59,130,246,0.3); } }
  .fc-event { cursor: pointer; border-radius: 6px; padding: 3px 6px; font-size: 12px; font-weight: 500; border: none; box-shadow: 0 2px 4px rgba(0,0,0,0.1); transition: all 0.2s; &:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); } }
  .fc-day-header { font-size: 14px !important; color: #64748b !important; font-weight: 700 !important; background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%) !important; border-bottom: 2px solid #e2e8f0 !important; padding: 12px 8px !important; }
  .fc-unthemed th, .fc-unthemed td { border-color: #f1f5f9; }
}
</style>

<style lang="scss" scoped>
::v-deep .booking-calendar-dialog { max-width: 1600px; margin-top: 3vh !important; border-radius: 20px; padding: 0; background: radial-gradient(circle at 0 0, rgba(255,255,255,0.96) 0, rgba(248,250,252,0.98) 40%, rgba(241,245,249,0.98) 100%); box-shadow: 0 24px 48px rgba(15,23,42,0.18), 0 0 0 1px rgba(255,255,255,0.9); backdrop-filter: blur(22px); -webkit-backdrop-filter: blur(22px); }
::v-deep .el-dialog__header { display: none; }
::v-deep .el-dialog__body { padding: 0; }
.dialog-inner { display: flex; flex-direction: column; max-height: 92vh; height: 88vh; }
.dialog-header { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; margin: 18px 22px 0; padding: 10px 14px; border-radius: 14px; background: rgba(219,234,254,0.96); }
.dialog-title { font-size: 17px; font-weight: 600; color: #0f172a; }
.dialog-close { cursor: pointer; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 999px; color: #64748b; transition: all 0.15s; &:hover { background: rgba(0,0,0,0.06); color: #0f172a; } }
.dialog-search { flex-shrink: 0; padding: 12px 22px 4px; }
.dialog-content { flex: 1; min-height: 0; overflow: hidden; padding: 0 22px 14px; }
::v-deep .booking-calendar-dialog .el-input__inner { border-radius: 999px; height: 32px; line-height: 32px; border-color: #e5e7eb; background-color: #f9fafb; &:focus { border-color: #2563eb; } }
::v-deep .booking-calendar-dialog .el-button--primary { border-radius: 999px; background: #2563eb; border-color: #2563eb; box-shadow: 0 4px 10px rgba(37,99,235,0.35); }
::v-deep .booking-calendar-dialog .el-button--default { border-radius: 999px; }
::v-deep .booking-calendar-dialog .el-form-item { margin-bottom: 8px; }
::v-deep .booking-calendar-dialog .el-form-item__label { white-space: nowrap; }
</style>