zxl
2025-07-15 e8746fe5bb553c17e0052958d9c39a5e2bc6f6a1
manager/src/views/activity/index.vue
@@ -1,29 +1,30 @@
<template>
  <div>
    <card>
  <div class="activity-management">
    <Card>
      <!-- 搜索表单 -->
      <Form
        ref="searchForm"
        @keydown.enter.native="handleSearch"
        :model="searchForm"
        inline
        :label-width="70"
        :label-width="80"
        class="search-form"
      >
        <Form-item label="活动名" prop="activityName">
        <FormItem label="活动名称" prop="activityName">
          <Input
            type="text"
            v-model="searchForm.activityName"
            placeholder="请输入活动名称"
            clearable
            @on-clear="handleSearch"
            @on-change="handleSearch"
            style="width: 160px"
            style="width: 180px"
          />
        </Form-item>
        <Form-item label="活动类型" prop="activityType">
        </FormItem>
        <FormItem label="活动类型" prop="activityType">
          <Select
            v-model="searchForm.activityType"
            style="width:260px"
            class="custom-select"
            placeholder="请选择活动类型"
            style="width: 180px"
            clearable
            @on-clear="handleSearch"
            @on-change="handleSearch"
@@ -33,97 +34,155 @@
              :value="item.value"
              :key="item.id"
            >
              {{ item.value }}
              {{ item.label }}
            </Option>
          </Select>
        </Form-item>
        <Form-item label="活动报名开始时间" prop="reportStartTime">
          <DatePicker :value="searchForm.reportStartTime"
                      type="datetime" placeholder="选择日期"
                      @on-change="handleSearch('reportStart',$event)"
                      @on-clear="handleSearch"
                      ></DatePicker>
        </Form-item>
        <Form-item label="活动报名结束时间" prop="reportEndTime">
          <DatePicker :value="searchForm.reportEndTime"
                      type="datetime" placeholder="选择日期"
                      @on-clear="handleSearch"
                      @on-change="handleSearch('reportEnd',$event)"></DatePicker>
        </Form-item>
<!--        <Form-item label="状态" prop="status">-->
<!--          <Input-->
<!--            type="text"-->
<!--            v-model="searchForm.status"-->
<!--            clearable-->
<!--            @on-clear="handleSearch"-->
<!--            @on-change="handleSearch"-->
<!--            style="width: 160px"-->
<!--          />-->
<!--        </Form-item>-->
        </FormItem>
        <FormItem label="审核情况" prop="audit">
          <Select
            v-model="searchForm.audit"
            placeholder="请选择审核情况"
            style="width: 180px"
            clearable
            @on-clear="handleSearch"
            @on-change="handleSearch"
          >
            <Option
              v-for="item in auditSelect"
              :value="item.value"
              :key="item.id"
            >
              {{ item.label }}
            </Option>
          </Select>
        </FormItem>
        <FormItem label="报名开始时间" prop="reportStartTime">
          <DatePicker
            :value="searchForm.reportStartTime"
            type="datetime"
            placeholder="选择开始时间"
            style="width: 180px"
            @on-change="handleSearch('reportStart', $event)"
            @on-clear="handleSearch"
          ></DatePicker>
        </FormItem>
        <FormItem label="报名结束时间" prop="reportEndTime">
          <DatePicker
            :value="searchForm.reportEndTime"
            type="datetime"
            placeholder="选择结束时间"
            style="width: 180px"
            @on-clear="handleSearch"
            @on-change="handleSearch('reportEnd', $event)"
          ></DatePicker>
        </FormItem>
        <Button
          @click="handleSearch"
          type="primary"
          icon="ios-search"
          class="search-btn"
        >搜索</Button
        >
        >搜索</Button>
        <Button
          @click="resetSearch"
          icon="md-refresh"
          style="margin-left: 8px"
        >重置</Button>
      </Form>
      <Row class="operation padding-row">
        <Button @click="openAdd" type="info">添加</Button>
        <Button @click="delBatch" type="error">批量删除</Button>
      <!-- 操作按钮 -->
      <Row class="operation">
        <Button @click="openAdd" type="primary" icon="md-add">新增活动</Button>
        <Button @click="delBatch" type="error" icon="md-trash" :disabled="selectCount === 0">批量删除</Button>
      </Row>
      <!-- 活动表格 -->
      <Table
        :loading="loading"
        border
        :columns="columns"
        :data="activityList"
        ref="table"
        sortable="custom"
        @on-sort-change="changeSort"
        @on-selection-change="showSelect"
        class="activity-table"
      >
        <template  slot-scope="{ row }" slot="url">
        <!-- 封面展示插槽 -->
        <template slot-scope="{ row }" slot="url">
          <div class="media-container">
            <!-- 图片类型 -->
            <template v-if="row.coverType === '图片'">
                <img width="300" height="300"
                  :src="row.url"
                  alt="封面"
                  class="thumbnail"
                  @click="previewImage(row.url)"
                  @error="handleImageError"
                >
            <template v-if="row.coverType === 'image'">
              <img
                :src="row.url"
                alt="活动封面"
                class="thumbnail"
                @click="previewImage(row.url)"
              >
            </template>
            <!-- 视频类型 -->
            <template v-else-if="row.coverType === '视频'">
                <video width="300"
                       height="300"
                  :src="row.url"
                  class="video-player"
                  controls
                  @error="handleVideoError"
                ></video>
            <template v-else-if="row.coverType === 'video'">
              <video
                :src="row.url"
                class="video-player"
                controls
              ></video>
            </template>
            <!-- 文字类型 -->
            <template v-else-if="row.coverType === text">
                {{ row.cover || '暂无文字内容' }}
            <template v-else>
              <div class="text-cover">{{ row.cover || '暂无封面内容' }}</div>
            </template>
          </div>
        </template>
        <template slot-scope="{ row, index }" slot="action">
          <Button type="info" size="small" style="margin-right: 5px" @click="changeRecommend(row,row.recommend === true ? '取消推荐' : '推荐')">
            {{ row.recommend === true ? '取消推荐' : '推荐' }}
          </Button>
          <Button type="info" size="small" style="margin-right: 5px" @click="changeStatus(row,row.status === '已发布' ? '下架' : '发布')">
            {{ row.status === '已发布' ? '下架' : '发布' }}
          </Button>
          <Button type="info" size="small" style="margin-right: 5px" @click="openEdit(row)">编辑标签</Button>
          <Button type="error" size="small" style="margin-right: 5px" @click="delById(row)">删除</Button>
          <Button type="info" size="small" style="margin-right: 5px" @click="openMembersModal(row)">
            报名人员列表
          </Button>
        <!-- 操作按钮插槽 -->
        <template slot-scope="{ row }" slot="action">
          <div class="action-btns">
            <Button
              type="primary"
              size="small"
              @click="changeRecommend(row, row.recommend ? '取消推荐' : '推荐')"
              :loading="row.recommendLoading"
            >
              {{ row.recommend ? '取消推荐' : '推荐' }}
            </Button>
            <Button
              type="primary"
              size="small"
              @click="changeStatus(row, row.publish ? '下架' : '发布')"
              :loading="row.statusLoading"
            >
              {{ row.publish  ? '下架' : '发布' }}
            </Button>
            <Button
              type="info"
              size="small"
              @click="detail(row)"
            >详情</Button>
            <Button
              type="info"
              size="small"
              @click="openEdit(row)"
            >编辑</Button>
            <Button
              type="error"
              size="small"
              @click="delById(row)"
            >删除</Button>
            <Button
              type="success"
              size="small"
              @click="openMembersModal(row)"
            >报名人员</Button>
            <Button
              type="success"
              size="small"
              :disabled="row.auditStatus !== 0"
              @click="openAuditModal(row)"
            >审核活动</Button>
          </div>
        </template>
      </Table>
      <Row type="flex" justify="end" class="mt_10">
      <!-- 分页 -->
      <Row type="flex" justify="end" class="page-footer">
        <Page
          :current="searchForm.pageNumber"
          :total="total"
@@ -138,68 +197,70 @@
        ></Page>
      </Row>
      <!-- 活动编辑/新增模态框 -->
      <Modal
        v-model="modelShow"
        :title="modelTitle"
        @close="modelClose()"
        @on-cancel="modelClose"
        width="800"
        :mask-closable="false"
      >
        <Form ref="form" :model="activityFrom" :label-width="70" :rules="rules" >
        <Form ref="form" :model="activityFrom" :label-width="100" :rules="rules">
          <Row :gutter="16">
            <Col span="12">
              <FormItem label="活动名称" prop="activityName">
                <Input
                  type="text"
                  v-model="activityFrom.activityName"
                  placeholder="请输入活动名称"
                  clearable
                  style="width: 300px"
                />
              </FormItem>
            </Col>
            <Col span="12">
              <FormItem label="活动类型" prop="activityType" >
              <FormItem label="活动类型" prop="activityType" :label-width="100">
                <Select
                  v-model="activityFrom.activityType"
                  class="custom-select"
                  placeholder="请选择活动类型"
                  clearable
                  style="width: 100px;"
                >
                  <Option
                    v-for="item in typeSelect"
                    :value="item.value"
                    :key="item.id"
                  >
                    {{ item.value }}
                    {{ item.label }}
                  </Option>
                </Select>
              </FormItem>
            </Col>
            <Col span="12">
              <FormItem label="报名时间段" prop="reportTime">
                <DatePicker v-model="activityFrom.reportTime" style="width: 300px"
                            :value="activityFrom.reportTime"
                            @on-change="activityFrom.reportTime =$event"
                            format="yyyy-MM-dd HH:mm:ss"
                            type="datetimerange"  placeholder="请选择"></DatePicker>
                <DatePicker
                  v-model="activityFrom.reportTime"
                  type="datetimerange"
                  format="yyyy-MM-dd HH:mm"
                  placeholder="请选择报名时间段"
                  style="width: 100%"
                ></DatePicker>
              </FormItem>
            </Col>
            <Col span="12">
              <FormItem label="活动时间段" prop="time">
                <DatePicker v-model="activityFrom.time" style="width: 300px"
                            :value="activityFrom.time"
                            @on-change="activityFrom.time =$event"
                            format= "yyyy-MM-dd HH:mm:ss"
                            type="datetimerange"  placeholder="请选择"></DatePicker>
                <DatePicker
                  v-model="activityFrom.time"
                  type="datetimerange"
                  format="yyyy-MM-dd HH:mm"
                  placeholder="请选择活动时间段"
                  style="width: 100%"
                ></DatePicker>
              </FormItem>
            </Col>
            <Col span="24">
              <FormItem label="封面" prop="cover">
            <Col span="12">
              <FormItem label="封面类型" prop="coverType" :labelWidth="100">
                <Select
                  v-model="coverType"
                  class="custom-select"
                  clearable
                  style="width: 150px;"
                  placeholder="请选择封面类型"
                  @on-change="handleCoverTypeChange"
                >
                  <Option
                    v-for="item in coverTypeOptions"
@@ -209,66 +270,109 @@
                    {{ item.value }}
                  </Option>
                </Select>
                <template>
                  <div v-if="coverTypeJudgment('text')">
                    <template>
                      <Input
                        type="text"
                        v-model="activityFrom.cover"
                        clearable
                        style="width: 300px"
                        placeholder="输入文本或选择文件"
                      />
                    </template>
                  </div>
                  <div v-if="coverTypeJudgment('file')">
                    <Upload
                      :before-upload="handleBeforeUpload"
                      action="">
                      <Button icon="ios-cloud-upload-outline">上传</Button>
                    </Upload>
                    <div v-if="file !== null">
                      Upload file: {{ file.name }}
                      <Button type="text" @click="handleRemove" :loading="loadingStatus">
                        {{ loadingStatus ? '上传中' : '删除' }}
                      </Button>
                    </div>
                  </div>
                </template>
            </FormItem>
            </Col>
            <Col span="24">
              <FormItem label="活动内容" prop="activityContent">
                <Input v-model="activityFrom.activityContent" type="textarea" :autosize="{minRows: 2,maxRows: 5}" placeholder="输入" />
              </FormItem>
            </Col>
          </ROW>
            <Col span="24" v-if="coverType === '输入文字封面'">
              <FormItem label="封面文字" prop="cover">
                <Input
                  v-model="activityFrom.cover"
                  type="textarea"
                  :rows="2"
                  placeholder="请输入封面文字"
                  style="width: 50%"
                />
              </FormItem>
            </Col>
            <Col span="24" v-if="coverType === '选择文件封面'">
              <FormItem label="上传封面" prop="cover">
                <Upload
                  :before-upload="handleBeforeUpload"
                  :format="['jpg','jpeg','png','gif','mp4','mov']"
                  :max-size="20480"
                  action=""
                  accept="image/*,video/*"
                >
                  <Button icon="ios-cloud-upload-outline">上传封面文件</Button>
                  <div class="upload-tip">支持图片或视频文件,最大20MB</div>
                </Upload>
                <div v-if="file" class="upload-file-info">
                  已选文件: {{ file.name }}
                  <Button type="text" @click="handleRemove">删除</Button>
                </div>
              </FormItem>
            </Col>
            <!-- 这两个表单项在同一Row内,会显示在同一行 -->
            <Col span="12">
              <FormItem label="人数限制" prop="limitUserNum">
                <InputNumber
                  v-model="activityFrom.limitUserNum"
                  :min="1"
                  placeholder="请输入最大人数"
                  style="width: 100%"
                />
              </FormItem>
            </Col>
            <Col span="12" v-if="activityFrom.activityType === 'offline'">
              <FormItem label="活动地点" prop="activityLocation" >
                <Input
                  v-model="activityFrom.activityLocation"
                  placeholder="请输入活动地点"
                />
              </FormItem>
            </Col>
            <Col span="24">
              <FormItem label="活动内容:" prop="activityContent">
                  <!-- 基于elementUi的上传组件 el-upload begin-->
                  <Upload
                    :show-upload-list="false"
                    ref="upload"
                    style="display: none"
                    :before-upload="handleUploadEdit"
                    :format="['jpg','jpeg','png','gif','mp4','mov']"
                    :max-size="20480"
                    action=""
                    accept="image/*,video/*"
                  >
                  </Upload>
                  <!-- 基于elementUi的上传组件 el-upload end-->
                  <quill-editor
                    v-model="activityFrom.activityContent"
                    ref="QuillEditor"
                    class="editor"
                    :options="editorOption"
                    @blur="onEditorBlur($event)"
                    @focus="onEditorFocus($event)"
                    @ready="onEditorReady($event)"
                  >
                  </quill-editor>
              </FormItem>
            </Col>
          </Row>
        </Form>
        <div slot="footer">
          <Button type="text" @click="modelClose">取消</Button>
          <Button @click="modelClose">取消</Button>
          <Button type="primary" :loading="submitLoading" @click="saveOrUpdate">提交</Button>
        </div>
      </Modal>
      <!-- 报名人员模态框 -->
      <Modal
        v-model="membersModelShow"
        :title="membersModelTitle"
        @close="membersModelClose()"
        width="1000">
        @on-cancel="membersModelClose"
        width="1000"
        class="members-modal"
      >
        <Table
          :loading="membersLoading"
          border
          :columns="membersColumns"
          :data="membersList"
          ref="table"
        >
        </Table>
        <Row type="flex" justify="end" class="mt_10">
          class="members-table"
        ></Table>
        <Row type="flex" justify="end" class="page-footer">
          <Page
            :current="memberForm.pageNumber"
            :total="memberTotal"
@@ -284,114 +388,414 @@
        </Row>
      </Modal>
      <Modal
        v-model="infoModelShow"
        :title="modelTitle"
        @on-cancel="infoModelClose"
        width="800"
        :mask-closable="false"
      >
        <div class="detail-container">
          <Row :gutter="16">
            <Col span="12">
              <div class="detail-item">
                <label>活动名称:</label>
                <span>{{ activityInfo.activityName || '-' }}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>活动类型:</label>
                <span>{{activityInfo.activityType === 'online' ? '线上':'线下'}}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>报名时间段:</label>
                <span>{{ activityInfo.reportStartTime }} - {{ activityInfo.reportEndTime }}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>活动时间段:</label>
                <span>{{ activityInfo.startTime }} - {{ activityInfo.endTime }}</span>
              </div>
            </Col>
    </card>
            <Col span="24" v-if="coverType === '输入文字封面'">
              <div class="detail-item">
                <label>封面文字:</label>
                <span>{{ activityInfo.cover || '-' }}</span>
              </div>
            </Col>
            <Col span="24" v-if="coverType === '选择文件封面'">
              <div class="detail-item">
                <label>上传封面:</label>
                <span>{{ activityInfo.cover }}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>人数限制:</label>
                <span>{{ activityInfo.limitUserNum || '无限制' }}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>活动地点:</label>
                <span>{{ activityInfo.activityLocation || '-' }}</span>
              </div>
            </Col>
            <Col span="24">
              <div class="detail-item">
                <label>活动内容:</label>
                <div
                  class="activity-content"
                  v-html="activityInfo.activityContent || '无内容'"
                ></div>
              </div>
            </Col>
          </Row>
        </div>
        <div slot="footer">
          <Button @click="infoModelClose">关闭</Button>
        </div>
      </Modal>
      <Modal
        v-model="auditModelShow"
        :title="modelTitle"
        @on-cancel="closeAuditModel"
        width="800"
        :mask-closable="false"
      >
        <div class="detail-container">
          <Row :gutter="16">
            <Col span="12">
              <div class="detail-item">
                <label>活动名称:</label>
                <span>{{ activityInfo.activityName || '-' }}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>活动类型:</label>
                <span>{{activityInfo.activityType === 'online' ? '线上':'线下'}}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>报名时间段:</label>
                <span>{{ activityInfo.reportStartTime }} - {{ activityInfo.reportEndTime }}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>活动时间段:</label>
                <span>{{ activityInfo.startTime }} - {{ activityInfo.endTime }}</span>
              </div>
            </Col>
            <Col span="24" v-if="coverType === '输入文字封面'">
              <div class="detail-item">
                <label>封面文字:</label>
                <span>{{ activityInfo.cover || '-' }}</span>
              </div>
            </Col>
            <Col span="24" v-if="coverType === '选择文件封面'">
              <div class="detail-item">
                <label>上传封面:</label>
                <span>{{ activityInfo.cover }}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>人数限制:</label>
                <span>{{ activityInfo.limitUserNum || '无限制' }}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>活动地点:</label>
                <span>{{ activityInfo.activityLocation || '-' }}</span>
              </div>
            </Col>
            <Col span="24">
              <div class="detail-item">
                <label>活动内容:</label>
                <div
                  class="activity-content"
                  v-html="activityInfo.activityContent || '无内容'"
                  ref="“activityHTMLContent"
                ></div>
              </div>
            </Col>
            <Col span="24">
              <Form :model="auditForm" :rules="auditRules" ref="auditForm" class="audit-form">
                <FormItem label="审核结果:" prop="audit">
                  <RadioGroup v-model="auditForm.audit">
                    <Radio label="1">通过</Radio>
                    <Radio label="2">未通过</Radio>
                  </RadioGroup>
                </FormItem>
                <FormItem label="审核备注:" prop="remarks">
                  <Input
                    v-model="auditForm.remarks"
                    type="textarea"
                    :rows="4"
                    placeholder="请输入审核备注(选填)"
                    :disabled="!auditForm.audit"
                  />
                </FormItem>
              </Form>
            </Col>
          </Row>
        </div>
        <!-- 审核表单 -->
        <div slot="footer">
          <Button @click="closeAuditModel">关闭</Button>
          <Button
            type="primary"
            @click="handleAuditSubmit(activityInfo.id)"
            :disabled="!auditForm.audit"
          >提交审核</Button>
        </div>
      </Modal>
      <!-- 图片预览模态框 -->
      <Modal v-model="previewVisible" title="图片预览" footer-hide>
        <img :src="previewImageUrl" style="width: 100%">
      </Modal>
    </Card>
  </div>
</template>
<script>
import JsonExcel from "vue-json-excel";
import {
  getActivityList,
  addActivity,
  editActivity,
  delActivityById,
  delActivityBatch,
  activityChangeStatus,
  activityChangeRecommend,
  activityMembersPage,
  auditActivity
} from "@/api/activity.js"
import { uploadFileByLmk, delByKey } from "@/api/common.js"
import {getActivityList,addActivity,editActivity,delActivityById,activityChangeStatus,activityChangeRecommend,activityMembersPage} from "@/api/activity.js"
import {uploadFileByLmk,delByKey,getUrl} from "@/api/common.js"
import { quillEditor } from 'vue-quill-editor'
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.snow.css';
import 'quill/dist/quill.bubble.css';
import * as Quill from 'quill' //引入编辑器
import VideoBlot from './video.js';
const toolbarOptions = [
  ['bold', 'italic', 'underline', 'strike'],        // 加粗,斜体,下划线,删除线
  ['blockquote', 'code-block'],                     //引用,代码块
  [{ 'header': 1 }, { 'header': 2 }],               // 几级标题
  [{ 'list': 'ordered' }, { 'list': 'bullet' }],    // 有序列表,无序列表
  [{ 'script': 'sub' }, { 'script': 'super' }],     // 下角标,上角标
  [{ 'indent': '-1' }, { 'indent': '+1' }],         // 缩进
  [{ 'direction': 'rtl' }],                         // 文字输入方向
  [{ 'size': ['small', false, 'large', 'huge'] }],  // 字体大小
  [{ 'header': [1, 2, 3, 4, 5, 6, false] }],        // 标题
  [{ 'color': [] }, { 'background': [] }],          // 颜色选择
  // [{ 'font': [] }], // 字体
  [{ 'align': [] }],    // 居中
  ['clean'],            // 清除样式,
  ['link'],
  ['myUploadBtn'],
]
// toolbar标题
const titleConfig = [
  { Choice: '.ql-insertMetric', title: '跳转配置' },
  { Choice: '.ql-bold', title: '加粗' },
  { Choice: '.ql-italic', title: '斜体' },
  { Choice: '.ql-underline', title: '下划线' },
  { Choice: '.ql-header', title: '段落格式' },
  { Choice: '.ql-strike', title: '删除线' },
  { Choice: '.ql-blockquote', title: '块引用' },
  { Choice: '.ql-code', title: '插入代码' },
  { Choice: '.ql-code-block', title: '插入代码段' },
  { Choice: '.ql-font', title: '字体' },
  { Choice: '.ql-size', title: '字体大小' },
  { Choice: '.ql-list[value="ordered"]', title: '编号列表' },
  { Choice: '.ql-list[value="bullet"]', title: '项目列表' },
  { Choice: '.ql-direction', title: '文本方向' },
  { Choice: '.ql-header[value="1"]', title: 'h1' },
  { Choice: '.ql-header[value="2"]', title: 'h2' },
  { Choice: '.ql-align', title: '对齐方式' },
  { Choice: '.ql-color', title: '字体颜色' },
  { Choice: '.ql-background', title: '背景颜色' },
  { Choice: '.ql-image', title: '图像' },
  { Choice: '.ql-video', title: '视频' },
  { Choice: '.ql-link', title: '添加链接' },
  { Choice: '.ql-formula', title: '插入公式' },
  { Choice: '.ql-clean', title: '清除字体格式' },
  { Choice: '.ql-script[value="sub"]', title: '下标' },
  { Choice: '.ql-script[value="super"]', title: '上标' },
  { Choice: '.ql-indent[value="-1"]', title: '向左缩进' },
  { Choice: '.ql-indent[value="+1"]', title: '向右缩进' },
  { Choice: '.ql-header .ql-picker-label', title: '标题大小' },
  { Choice: '.ql-header .ql-picker-item[data-value="1"]', title: '标题一' },
  { Choice: '.ql-header .ql-picker-item[data-value="2"]', title: '标题二' },
  { Choice: '.ql-header .ql-picker-item[data-value="3"]', title: '标题三' },
  { Choice: '.ql-header .ql-picker-item[data-value="4"]', title: '标题四' },
  { Choice: '.ql-header .ql-picker-item[data-value="5"]', title: '标题五' },
  { Choice: '.ql-header .ql-picker-item[data-value="6"]', title: '标题六' },
  { Choice: '.ql-header .ql-picker-item:last-child', title: '标准' },
  { Choice: '.ql-size .ql-picker-item[data-value="small"]', title: '小号' },
  { Choice: '.ql-size .ql-picker-item[data-value="large"]', title: '大号' },
  { Choice: '.ql-size .ql-picker-item[data-value="huge"]', title: '超大号' },
  { Choice: '.ql-size .ql-picker-item:nth-child(2)', title: '标准' },
  { Choice: '.ql-align .ql-picker-item:first-child', title: '居左对齐' },
  { Choice: '.ql-align .ql-picker-item[data-value="center"]', title: '居中对齐' },
  { Choice: '.ql-align .ql-picker-item[data-value="right"]', title: '居右对齐' },
  { Choice: '.ql-align .ql-picker-item[data-value="justify"]', title: '两端对齐' }
]
export default {
  name:"activity",
  components:{
    "download-excel": JsonExcel,
  },
  data(){
    return{
      loading: false, // 表单加载状态
      typeSelect:[
        {
          id:1,
          value:'线上'
        },
        {
          id:2,
          value:'线下'
        }
      ],
      coverTypeOptions:[
        {
          id:1,
          value: '输入文字封面'
        },
        {
          id:2,
          value: '选择文件封面'
        },
      ],
      membersLoading:false, //参加活动人员表单
      membersModelTitle:'',
      membersModelShow:false,
      membersList:[],
      membersColumns: [
        {
          type: 'selection',
          width: 60,
          align: 'center'
        },
        {
          title:'用户名',
          key: 'username',
          minWidth: 60,
          tooltip: true,
        },
        {
          title:'昵称',
          key: 'nickName',
          minWidth: 60,
          tooltip: true,
        },
        {
          title:'性别',
          key: 'sex',
          tooltip: true,
          render: (h, params) => {
            const sexText = params.row.sex === 1 ? '男' : '女';
            return h('span', sexText);
          }
        },
        {
          title:'地址',
          key: 'region',
          minWidth: 60,
          tooltip: true,
        },
        {
          title:'状态',
          key: 'disabled',
          tooltip: true,
          render: (h, params) => {
            const sexText = params.row.disabled === true ? '禁用' : '正常';
            return h('span', sexText);
          }
        },
      ],
      memberForm:{
        id:'',
        pageNumber: 1, // 当前页数
        pageSize: 10, // 页面大小
  name: "ActivityManagement",
  components: { quillEditor},
  data() {
    return {
      auditForm: {
        activityId: '',
        audit: null,
        remarks: ''
      },
      memberTotal:0,
      auditRules: {
        audit: [
          {required: true, message: '请选择审核结果', trigger: 'change'}
        ]
      },
      infoModelShow: false,
      auditModelShow: false,
      coverType:'',
      //查询活动列表请求参数
      searchForm:{
        activityName:'',// 活动名称
        activityType:'',// 活动类型
        recommend:'',// 推荐
        reportStartTime:'',// 活动报名时间
        reportEndTime:'',// 活动结束时间
        pageNumber: 1, // 当前页数
        pageSize: 10, // 页面大小
      loading: false,
      membersLoading: false,
      submitLoading: false,
      // 搜索表单
      searchForm: {
        audit: '',
        activityName: '',
        activityType: '',
        reportStartTime: '',
        reportEndTime: '',
        pageNumber: 1,
        pageSize: 10
      },
      // 活动列表数据
      activityList: [],
      total: 0,
      selectList: [],
      selectCount: 0,
      // 活动类型选项
      typeSelect: [
        {id: 1, value: 'online', label: '线上'},
        {id: 2, value: 'offline', label: '线下'}
      ],
      auditSelect: [
        {id: 1, value: '1', label: '已审核'},
        {id: 2, value: '0', label: '未审核'}
      ],
      //活动列表表头
      // 封面类型选项
      coverTypeOptions: [
        {id: 1, value: '输入文字封面'},
        {id: 2, value: '选择文件封面'}
      ],
      coverType: '',
      file: null,
      // 活动表单
      activityFrom: {
        id: '',
        activityName: '',
        activityType: '',
        reportTime: [],
        time: [],
        activityContent: '',
        cover: '',
        coverType: '',
        status: '',
        reportStartTime: '',
        reportEndTime: '',
        startTime: '',
        endTime: '',
        recommend: false,
        limitUserNum: 0,
        activityLocation: '',
      },
      activityInfo: {
        id: '',
        activityName: '',
        activityType: '',
        reportTime: [],
        time: [],
        activityContent: '',
        cover: '',
        coverType: '',
        status: '',
        reportStartTime: '',
        reportEndTime: '',
        startTime: '',
        endTime: '',
        recommend: false,
        limitUserNum: 0,
        activityLocation: '',
      },
      // 表单验证规则
      rules: {
        activityName: [
          {required: true, message: '请输入活动名称', trigger: 'blur'},
          {max: 50, message: '长度不能超过50个字符', trigger: 'blur'}
        ],
        activityType: [
          {required: true, message: '请选择活动类型', trigger: 'change'}
        ],
        reportTime: [
          {type: 'array', required: true, message: '请选择报名时间段', trigger: 'change'},
          {validator: this.validateReportTime, trigger: 'change'}
        ],
        time: [
          {type: 'array', required: true, message: '请选择活动时间段', trigger: 'change'},
          {validator: this.validateActivityTime, trigger: 'change'}
        ],
        cover: [
          {required: true, message: '请输入封面内容', trigger: 'blur'}
        ],
        coverType: [
          {required: true, message: '请选择封面类型', trigger: 'blur'}
        ],
        limitUserNum: [
          {required: true, type: 'number', message: '请输入人数限制', trigger: 'blur'},
          {type: 'number', min: 1, message: '人数不能少于1人', trigger: 'blur'}
        ],
        activityLocation: [
          {required: true, message: '请输入活动地点', trigger: 'blur'},
          {max: 100, message: '长度不能超过100个字符', trigger: 'blur'}
        ],
        activityContent: [
          {required: false, message: '请输入活动内容', trigger: 'blur'}
        ]
      },
      // 表格列配置
      columns: [
        {
          type: 'selection',
@@ -399,519 +803,1130 @@
          align: 'center'
        },
        {
          title:'活动名',
          title: '活动名称',
          key: 'activityName',
          minWidth: 60,
          tooltip: true,
          minWidth: 120,
          tooltip: true
        },
        {
          title:'活动类型',
          title: '活动类型',
          key: 'activityType',
          tooltip: true,
        },
        // {
        //   title:'状态',
        //   key: 'status',
        //   tooltip: true,
        // },
        {
          title:'推荐',
          key: 'recommend',
          tooltip: true,
          width: 100,
          align: 'center',
          render: (h, params) => {
            const sexText = params.row.recommend === true ? '是' : '否';
            return h('span', sexText);
            return h('Tag', {}, params.row.activityType === 'online' ? '线上' : '线下')
          }
        },
        {
          title:'活动报名开始时间',
          key: 'reportStartTime',
          minWidth: 60,
          tooltip: true,
          title: '推荐',
          key: 'recommend',
          width: 80,
          align: 'center',
          render: (h, params) => {
            return h('Tag', {
              props: {
                color: params.row.recommend ? 'green' : 'default'
              }
            }, params.row.recommend ? '是' : '否')
          }
        },
        {
          title:'活动报名结束时间',
          key: 'reportEndTime',
          minWidth: 60,
          tooltip: true,
          title: '发布',
          key: 'publish',
          width: 100,
          align: 'center',
          render: (h, params) => {
            return h('Tag', {
              props: {
                color: params.row.publish ? 'green' : 'default'
              }
            }, params.row.publish ? '已发布' : '未发布')
          }
        },
        {
          title:'活动开始时间',
          key: 'reportEndTime',
          minWidth: 60,
          tooltip: true,
          title: '审核状态',
          key: 'auditStatus',
          width: 100,
          align: 'center',
          render: (h, params) => {
            const status = params.row.auditStatus;
            let tagText, tagColor;
            // 根据状态设置文案和颜色
            switch (status) {
              case 0:
                tagText = '审核中';
                tagColor = 'orange';  // 橙色表示进行中
                break;
              case 1:
                tagText = '已审核';
                tagColor = 'green';   // 绿色表示通过
                break;
              case 2:
                tagText = '未通过';
                tagColor = 'red';     // 红色表示拒绝
                break;
              default:
                tagText = '未知状态';
                tagColor = 'default'; // 默认灰色
            }
            return h('Tag', {
              props: {
                color: tagColor,
              },
            }, tagText);
          },
        },
        {
          title:'活动结束时间',
          key: 'reportEndTime',
          minWidth: 60,
          tooltip: true,
          title: '状态',
          key: 'status',
          width: 100,
          align: 'center',
          render: (h, params) => {
            const status = params.row.status;
            const statusMap = {
              'noStart': {text: '未开始', color: 'default'},
              'report': {text: '报名中', color: 'green'},
              'inProgress': {text: '进行中', color: 'cyan'},
              'end': {text: '已结束', color: 'red'}
            };
            const currentStatus = statusMap[status] || {text: status, color: 'default'};
            return h('Tag', {
              props: {
                color: currentStatus.color
              }
            }, currentStatus.text);
          }
        },
        {
          title:'封面',
          title: '活动报名时间段',
          key: 'activityReportTimeRange',
          width: 300,
          render: (h, params) => {
            return h('div', [
              h('div', `开始: ${this.formatDate(params.row.reportStartTime)}`),
              h('div', `结束: ${this.formatDate(params.row.reportEndTime)}`)
            ])
          }
        },
        {
          title: '活动时间段',
          key: 'activityTimeRange',
          width: 300,
          render: (h, params) => {
            return h('div', [
              h('div', `开始: ${this.formatDate(params.row.startTime)}`),
              h('div', `结束: ${this.formatDate(params.row.endTime)}`)
            ])
          }
        },
        {
          title: '封面',
          key: 'url',
          slot:"url",
          minWidth: 400,
          tooltip: true,
          slot: 'url',
          width: 150,
          align: 'center'
        },
        {
          title:'封面类型',
          title: '封面类型',
          key: 'coverType',
          tooltip: true,
          width: 100,
          align: 'center',
          render: (h, params) => {
            const typeMap = {
              text: '文本',
              video: '视频',
              image: '图片'
            };
            const text = typeMap[params.row.coverType] || params.row.coverType;
            return h('span', text);
          }
        },
        {
          title:'最大报名人数限制',
          title: '人数限制',
          key: 'limitUserNum',
          tooltip: true,
          width: 100,
          align: 'center'
        },
        {
          title:'活动地点',
          title: '活动地点',
          key: 'activityLocation',
          tooltip: true,
        },
        {
          title:'活动详细内容',
          key: 'activityContent',
          tooltip: true,
          minWidth: 120,
          tooltip: true
        },
        {
          title: '操作',
          key: 'action',
          slot: 'action',
          minWidth: 100,
          align: 'center'
          width: 280,
          align: 'center',
          fixed: 'right'
        }
      ],
      //活动列表数据
      activityList:[],
      total:0,
      selectCount: 0, // 已选数量
      selectList: [], // 已选数据列表
      //活动对话框---
      modelShow:false,
      submitLoading:false,
      modelTitle:'',
      activityFrom:{
        id:'',
        activityName:'',
        activityType:'',
        reportTime:[],
        time:[],
        activityContent:'', // 活动内容
        cover:'', //image/2025052014565362541.jpg
        coverType:'',
        status:'', //活动状态
        reportStartTime:'',
        reportEndTime:'',
        startTime:'',
        endTime:'',
        recommend:'',
        // 报名条件
        // 报名费用
      // 报名人员相关
      membersModelShow: false,
      membersModelTitle: '',
      membersList: [],
      memberForm: {
        id: '',
        pageNumber: 1,
        pageSize: 10
      },
      rules: {
        activityName: {required: true, message: "活动名称", trigger: "blur"},
        activityType: {required: true, message: "活动类型", trigger: "blur"},
        reportTime: [{type: 'array',
          required: true,
          message: "报名日期", trigger: "change"
      memberTotal: 0,
      membersColumns: [
        {
          type: 'selection',
          width: 60,
          align: 'center'
        },
          {
            validator: (rule, value, callback) => {
              // 获取活动时间(通过闭包访问表单数据)
              const activityTime = this.activityFrom.time;
              // 解析时间(增强健壮性)
              const parseTime = (timeStr) => {
                const date = new Date(timeStr);
                return isNaN(date.getTime()) ? NaN : date.getTime();
              };
              const reportStart = parseTime(value[0]);
              const reportEnd = parseTime(value[1]);
              const activityStart = parseTime(activityTime[0]);
              const activityEnd = parseTime(activityTime[1]);
              // 核心验证逻辑
              if (reportStart > activityStart || reportEnd > activityStart) {
                callback(new Error('报名时间段必须在活动开始时间前'));
              } else {
                callback();
        {
          title: '用户名',
          key: 'username',
          minWidth: 100
        },
        {
          title: '昵称',
          key: 'nickName',
          minWidth: 100
        },
        {
          title: '性别',
          key: 'sex',
          width: 80,
          render: (h, params) => {
            return h('Tag', {
              props: {
                color: params.row.sex === 1 ? 'blue' : 'magenta'
              }
            },
            trigger: 'change'
            }, params.row.sex === 1 ? '男' : '女')
          }
        ],
        // time: [{type: 'array',
        //   required: true,
        //   fields: {
        //     0: {type: 'date', required: true, message: '请选择起止日期'},
        //     1: {type: 'date', required: true, message: '请选择起止日期'}
        //   }
        // }],
        time: [{type: 'array',
          required: true,
          message: "活动日期", trigger: "change"
        }],
        activityContent: {required: true, message: "活动内容", trigger: "blur"},
        cover: {required: true, message: "封面", trigger: "blur"},
      },
      // 上传
      file: null,
      loadingStatus: false,
      text:'文字',
        },
        {
          title: '地区',
          key: 'region',
          minWidth: 120
        },
        {
          title: '状态',
          key: 'disabled',
          width: 100,
          render: (h, params) => {
            return h('Tag', {
              props: {
                color: params.row.disabled ? 'green' : 'red' //true 正常 false被禁用
              }
            }, params.row.disabled ? '正常' : '禁用')
          }
        }
      ],
      // 图片预览
      previewVisible: false,
      previewImageUrl: '',
      // 模态框控制
      modelShow: false,
      modelTitle: '',
      //编辑器配置
      // 富文本编辑器配置
      Quill:'',
      defaultValue: '',
      editorOption: {
        placeholder: '请在这里输入',
        theme: 'snow', //主题 snow/bubble
        modules: {
          history: {
            delay: 1000,
            maxStack: 50,
            userOnly: false
          },
          toolbar: {
            container: toolbarOptions,
            handlers: {
              myUploadBtn: this.myMethod,
            }
          }
        }
      }
    }
  },
  mounted(){
    this.init();
  // 在组件创建前注册
  beforeCreate() {
    Quill.register(VideoBlot, true);
  },
  methods:{
    activityMembersPage(){
      this.membersLoading = true;
      activityMembersPage(this.memberForm).then(res =>{
        this.membersLoading = false;
        if (res.code === 200){
          this.membersList = res.data;
          this.memberTotal = res.total;
        }
      });
  mounted() {
    //初始化
    this.Quill=this.$refs.QuillEditor.quill
    this.init()
    this.initTitle()
    this.initButton();
  },
  methods: {
    myMethod(){
      this.$refs.upload.handleClick();
    },
    openMembersModal(row){
      this.memberForm.id = row.id
      this.membersModelTitle = "报名人员"
      this.membersModelShow = true;
      this.activityMembersPage();
    },
    membersModelClose(){
      this.membersModelShow = false;
    initButton(){
      const editorButton = document.querySelector('.ql-myUploadBtn')
      editorButton.innerHTML = '<svg t="1751966766109" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1530" fill="currentColor"  style="width: 1em; height: 1em; vertical-align: middle;"><path d="M1024 693.248q0 25.6-8.704 48.128t-24.576 40.448-36.864 30.208-45.568 16.384l1.024 1.024-17.408 0-4.096 0-4.096 0-675.84 0q-5.12 1.024-16.384 1.024-39.936 0-74.752-15.36t-60.928-41.472-40.96-60.928-14.848-74.752 14.848-74.752 40.96-60.928 60.928-41.472 74.752-15.36l1.024 0q-1.024-8.192-1.024-15.36l0-16.384q0-72.704 27.648-137.216t75.776-112.128 112.128-75.264 136.704-27.648 137.216 27.648 112.64 75.264 75.776 112.128 27.648 137.216q0 37.888-8.192 74.24t-22.528 69.12q5.12-1.024 10.752-1.536t10.752-0.512q27.648 0 52.736 10.752t43.52 29.696 29.184 44.032 10.752 53.76zM665.6 571.392q20.48 0 26.624-4.608t-8.192-22.016q-14.336-18.432-31.744-48.128t-36.352-60.416-38.4-57.344-37.888-38.912q-18.432-13.312-27.136-14.336t-25.088 12.288q-18.432 15.36-35.84 38.912t-35.328 50.176-35.84 52.224-36.352 45.056q-18.432 18.432-13.312 32.768t25.6 14.336l16.384 0q9.216 0 19.968 0.512t20.992 0.512l17.408 0q14.336 1.024 18.432 9.728t4.096 24.064q0 17.408-0.512 30.72t-0.512 25.6-0.512 25.6-0.512 30.72q0 7.168 1.536 15.36t5.632 15.36 12.288 11.776 21.504 4.608l23.552 0q9.216 0 27.648 1.024 24.576 0 28.16-12.288t3.584-38.912q0-23.552 0.512-42.496t0.512-51.712q0-23.552 4.608-36.352t19.968-12.8q11.264 0 32.256-0.512t32.256-0.512z" p-id="1531"></path></svg>'
    },
    changeRecommend(row,recommend){
      const form = {
        ...this.activityFrom
      };
      form.id = row.id;
      if (recommend === "取消推荐"){
        form.recommend = false
      }else if (recommend ==="推荐"){
        form.recommend = true
    initTitle() {
      document.getElementsByClassName('ql-editor')[0].dataset.placeholder = ''
      for (let item of titleConfig) {
        let tip = document.querySelector('.quill-editor ' + item.Choice)
        if (!tip) continue
        tip.setAttribute('title', item.title)
      }
      activityChangeRecommend(form).then(res =>{
        if (res.code === 200){
          this.getActivityList();
        }
      })
    },
    changeStatus(row,status){
      const form = {
        ...this.activityFrom
      };
      form.id = row.id;
      if (status === "发布"){
        form.status = "已发布"
      }else if (status ==="下架"){
        form.status = "已下架"
      }
      activityChangeStatus(form).then(res =>{
        if (res.code === 200){
          this.getActivityList();
        }
      })
    // 失去焦点
    onEditorBlur(editor) {
    },
    coverTypeJudgment(type){
      if (this.coverType === '输入文字封面' && type === 'text'){
        this.activityFrom.coverType = this.text;
        return true;
      }
      if (this.coverType === '选择文件封面' && type === 'file'){
        return true;
      }
    // 获得焦点
    onEditorFocus(editor) {
    },
    // 开始
    onEditorReady(editor) {
    },
  handleUploadEdit(file){
    const fileType = file.type
    const isImage = fileType.includes('image')
    const isVideo = fileType.includes('video')
    if (!isImage && !isVideo) {
      this.$Message.error('请上传图片或视频文件')
      return false
    },
    //自动上传 false
    handleBeforeUpload (file) {
      const fileCategory = this.getFileCategory(file.type);
      console.log('文件分类:', fileCategory); // 输出:图片/视频/文档 等
      // 2. 你的其他逻辑(如类型验证)
      if (fileCategory === '未知') {
        this.$Message.error('不支持的文件类型');
        return false;
      }
      this.file = file;
      this.activityFrom.coverType = fileCategory;
      this.upload();
    }
    if (file.size > 20 * 1024 * 1024) {
      this.$Message.error('文件大小不能超过20MB')
      return false
    },
    getFileCategory(mimeType) {
      // MIME类型前缀映射表
      const mimeMap = {
        'jpg' : '图片',
        'image': '图片',    // image/jpeg, image/png 等
        'video': '视频',    // video/mp4, video/quicktime 等
        'audio': '音频',    // audio/mpeg, audio/wav 等
        'application': '文档' // application/pdf, application/msword 等
      };
    }
      // 获取类型前缀(如 image/png → image)
      const typePrefix = mimeType.split('/')[0];
    this.file = file
    this.uploadFile2()
    return false
  },
// 上传文件
  uploadFile2() {
    if (!this.file) return
      // 返回对应分类或未知
      return mimeMap[typePrefix] || '未知';
    },
    this.submitLoading = true
    const formData = new FormData()
    formData.append('file', this.file)
    uploadFileByLmk(formData).then(res => {
      this.submitLoading = false
      if (res.code === 200) {
        let url = res.data.url;
        let fileKey = res.data.fileKey;
        let fileType = this.getFileType(this.file);
    //上传图片接口
    upload () {
      this.submitLoading = true;
      this.loadingStatus = true;
      const formData = new FormData()
      formData.append('file', this.file)
      uploadFileByLmk(formData).then(res =>{
        this.loadingStatus = false;
        this.submitLoading = false;
        if (res.code === 200){
          this.activityFrom.cover = res.data.fileKey;
          this.$Message.success(res.msg)
        const range = this.Quill.getSelection();
        const index = range ? range.index : this.Quill.getLength();
        if (fileType === 'video') {
          this.Quill.insertEmbed(index, 'video', {
            url:url,
            controls:'controls',
            width:'100%',
            height:'auto'
          });
        } else if (fileType === 'image') {
          this.Quill.insertEmbed(index, "image", url);
        } else {
          // 如果不是图片或视频,可以处理其他类型或给出提示
          this.$Message.warning('不支持的文件类型');
          return;
        }
      })
        console.log(this.activityFrom.activityContent)
        this.Quill.setSelection(index + 1);
        this.$Message.success('上传成功')
      }
    }).catch(() => {
      this.submitLoading = false
    })
  },
    getFileType(file) {
      // 获取文件类型或扩展名
      let type, extension;
      if (file instanceof File) {
        // 如果是File对象
        type = file.type;
        const name = file.name.toLowerCase();
        extension = name.substring(name.lastIndexOf('.') + 1);
      } else if (typeof file === 'string') {
        // 如果是字符串(文件名或URL)
        const name = file.toLowerCase();
        extension = name.substring(name.lastIndexOf('.') + 1);
        // 尝试从URL中提取MIME类型(如果有)
        const mimeMatch = file.match(/^data:(.+?);/);
        if (mimeMatch) {
          type = mimeMatch[1];
        }
      } else {
        return 'unknown';
      }
      // 常见图片和视频的MIME类型
      const imageTypes = [
        'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp', 'image/svg+xml'
      ];
      const videoTypes = [
        'video/mp4', 'video/webm', 'video/ogg', 'video/quicktime', 'video/x-msvideo', 'video/x-matroska'
      ];
      // 检查MIME类型
      if (type) {
        if (imageTypes.includes(type)) return 'image';
        if (videoTypes.includes(type)) return 'video';
      }
      // 常见图片和视频的扩展名
      const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg'];
      const videoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];
      // 检查文件扩展名
      if (extension) {
        if (imageExtensions.includes(extension)) return 'image';
        if (videoExtensions.includes(extension)) return 'video';
      }
      return 'unknown';
    },
    handleRemove(){
        this.file = null
        this.loadingStatus = true;
        delByKey(this.activityFrom.cover).then(res =>{
          this.loadingStatus = false;
          if (res.code === 200){
            this.activityFrom.cover = null;
  handleAuditSubmit(activityId) {
    console.log(this.auditForm)
    this.$refs.auditForm.validate(valid => {
      if (valid) {
        const form = {
          activityId: activityId,
          audit: this.auditForm.audit,
          remarks: this.auditForm.remarks,
        }
        // 提交时 auditForm.audit 已经是数字类型(0 或 1)
        auditActivity(form).then(res => {
          if (res.code === 200) {
            // 为每一行添加loading状态
            this.$Message.success(res.msg);
            this.getActivityList();
            this.closeAuditModel();
          }
        })
    },
    // 获得客户表格信息
    getActivityList(){
      this.loading = true;
      getActivityList(this.searchForm).then(res =>{
        this.loading = false;
        if (res.code === 200) {
          this.activityList = res.data;
          this.total = res.total;
        }
      })
    },
    init(){
      this.getActivityList();
    },
    changeSort(){
    },
    showSelect(){
      this.selectList = e.map(d => d.id);
      this.selectCount = e.length;
    },
    //
    openEdit(row){
      console.log(row)
      this.modelShow = true
      this.modelTitle = "编辑活动"
      this.activityFrom.id = row.id;
      this.activityFrom.activityName = row.activityName;
      this.activityFrom.activityType = row.activityType;
      this.activityFrom.activityContent = row.activityContent;
      // this.activityFrom.cover = row.cover;
      //判断封面类型赋值给前端
      if (row.coverType === this.text){
        this.coverType = '输入文字封面'
        this.activityFrom.cover = row.cover
        this.activityFrom.coverType = row.coverType
      }else{
        this.coverType = '选择文件封面'
        this.activityFrom.cover = row.cover
        this.activityFrom.coverType = row.coverType
      }
    });
      //转换格式
      this.activityFrom.reportTime=
        this.formatDate(new Date(row.reportStartTime))+' - '+ this.formatDate(new Date(row.reportEndTime));
     //转化格式
      this.activityFrom.time =
        this.formatDate(new Date(row.startTime))+' - '+ this.formatDate(new Date(row.endTime));
  },
  openAuditModal(row) {
    this.modelTitle = '活动审核'
    this.auditModelShow = true
    this.activityInfo = row
  },
  closeAuditModel() {
    this.$refs.auditForm.resetFields();
    this.auditModelShow = false
  },
    escapeStringHTML(str) {
      if (!str) return str;
      str = str.replace(/&lt;/g, '<');
      str = str.replace(/&gt;/g, '>');
      return str;
    },
    validateDateTime(dateTimeString) {
      const dateTimeRegex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]) ([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/;
      return dateTimeRegex.test(dateTimeString);
    },
    saveOrUpdate(){
      //判断正则格式 ,全部转换成本地时间 并且转换为指定格式
      if (!this.validateDateTime(this.activityFrom.reportTime[0])){
        this.activityFrom.reportStartTime = this.formatDate(this.activityFrom.reportTime[0]);
      }else {
        this.activityFrom.reportStartTime = this.activityFrom.reportTime[0]
      }
      if (!this.validateDateTime(this.activityFrom.reportTime[1])){
        this.activityFrom.reportEndTime =this.formatDate(this.activityFrom.reportTime[1]);
      }else {
        this.activityFrom.reportEndTime = this.activityFrom.reportTime[1]
      }
      if (!this.validateDateTime(this.activityFrom.time[0])){
        this.activityFrom.startTime = this.formatDate(this.activityFrom.time[0]);
      }else {
        this.activityFrom.startTime = this.activityFrom.time[0]
      }
      if (!this.validateDateTime(this.activityFrom.time[1])){
        this.activityFrom.endTime = this.formatDate(this.activityFrom.time[1]);
      }else {
        this.activityFrom.endTime = this.activityFrom.time[1]
      }
    // 提交
  detail(row) {
    this.modelTitle = '活动详情'
    this.infoModelShow = true
    this.activityInfo = row
    this.activityInfo.activityContent = this.escapeStringHTML(this.activityInfo.activityContent);
    this.$nextTick(() => {
      this.processVideos();
    });
      this.$refs.form.validate(valid => {
        if (valid) {
          this.submitLoading = true
          if (this.activityFrom.id) {
            editActivity(this.activityFrom).then(res =>{
              if (res.code === 200) {
                this.$Message.success(res.msg)
                this.modelClose();
                this.getActivityList();
              }
            })
          }else {
            addActivity(this.activityFrom).then(res => {
              if (res.code === 200) {
                this.$Message.success(res.msg)
                this.modelClose();
                this.getActivityList();
              }
            })
          }
        }
  },
    processVideos() {
      const videos = this.$el.querySelectorAll('video');
      videos.forEach(video => {
        // 确保视频元素有必要的属性
        video.setAttribute('controls', '');
        video.setAttribute('playsinline', ''); // 针对移动端
        video.load();
      });
    },
    formatDate(date) {
      if (date !== null && date !== undefined  && date !== ''){
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, '0'); // 补零
        const day = String(date.getDate()).padStart(2, '0');
        const hour = String(date.getHours()).padStart(2, '0');
        const minutes = String(date.getMinutes()).padStart(2, '0');
        const second = String(date.getSeconds()).padStart(2, '0');
        return `${year}-${month}-${day} ${hour}:${minutes}:${second}`;
  },
  // 获取富文本编辑器的内容
  // 初始化数据
  init() {
    this.getActivityList()
  },
  // 获取活动列表
  getActivityList() {
    this.loading = true
    getActivityList(this.searchForm).then(res => {
      this.loading = false
      if (res.code === 200) {
        // 为每一行添加loading状态
        this.activityList = res.data.map(item => ({
          ...item,
          recommendLoading: false,
          statusLoading: false
        }))
        this.total = res.total
      }
      return null
    },
    // 搜索
    handleSearch(type,$event){
      if(type === 'reportStart'){
        this.searchForm.reportStartTime = $event;
      }else if(type === 'reportEnd'){
        this.searchForm.reportEndTime = $event;
      }
    }).catch(() => {
      this.loading = false
    })
  },
      this.searchForm.pageNumber = 1;
      this.searchForm.pageSize = 10;
      this.getActivityList();
    },
    // 关闭弹窗
    modelClose() {
      this.file = null
      this.submitLoading = false
      this.modelShow = false
      this.coverType = null
      this.$refs.form.resetFields()
    },
    openAdd(){
      this.modelTitle = "新增活动"
      this.modelShow = true
    },
    // 删除
    delById(row){
      delActivityById(row.id).then(res =>{
        if (res.code === 200){
          this.$Message.success(res.msg)
          this.getActivityList();
        }
      })
    },
    // 批量删除
    delBatch(){
      if (this.selectCount <= 0) {
        this.$Message.warning("您还未选择要删除的数据");
        return;
      }
      this.$Modal.confirm({
        title: "确认删除",
        content: "您确认要删除所选的 " + this.selectCount + " 条数据?",
        loading: true,
        onOk: () => {
        }
      });
    },
    // 页码
    changePage(v){
      this.searchForm.pageNumber = v
      this.getActivityList()
    },
    // 修改size
    changePageSize(v){
      this.searchForm.pageNumber = 1;
      this.searchForm.pageSize = v;
      this.getActivityList()
    },
    memberChangePage(v){
      this.memberForm.pageNumber = v
      this.activityMembersPage();
    },
    // 修改size
    memberChangePageSize(v){
      this.memberForm.pageNumber = 1;
      this.memberForm.pageSize = v;
      this.activityMembersPage();
    },
    handleImageError(){
    },
    handleVideoError(){
    },
    previewImage(){
  // 搜索活动
  handleSearch(type, value) {
    if (type === 'reportStart') {
      this.searchForm.reportStartTime = value
    } else if (type === 'reportEnd') {
      this.searchForm.reportEndTime = value
    }
  }
    this.searchForm.pageNumber = 1
    this.getActivityList()
  },
  // 重置搜索
  resetSearch() {
    this.$refs.searchForm.resetFields()
    this.searchForm.pageNumber = 1
    this.getActivityList()
  },
  // 改变页码
  changePage(page) {
    this.searchForm.pageNumber = page
    this.getActivityList()
  },
  // 改变每页条数
  changePageSize(pageSize) {
    this.searchForm.pageNumber = 1
    this.searchForm.pageSize = pageSize
    this.getActivityList()
  },
  // 表格选择变化
  showSelect(selection) {
    this.selectList = selection.map(item => item.id)
    this.selectCount = selection.length
  },
  // 打开新增模态框
  openAdd() {
    this.modelTitle = '新增活动'
    this.modelShow = true
    this.coverType = '输入文字封面'
    this.file = null
    this.$refs.form.resetFields()
    this.activityFrom.id = ''
  },
  // 打开编辑模态框
  openEdit(row) {
    this.modelTitle = '编辑活动'
    this.modelShow = true
    this.$nextTick(() => {
      this.$refs.form.resetFields()
      console.log(row)
      // 填充表单数据
      this.activityFrom = {
        id: row.id,
        activityName: row.activityName,
        activityType: row.activityType,
        reportTime: [
          this.formatDate(row.reportStartTime, 'YYYY-MM-DD HH:mm:ss'),
          this.formatDate(row.reportEndTime, 'YYYY-MM-DD HH:mm:ss')
        ],
        time: [
          this.formatDate(row.startTime, 'YYYY-MM-DD HH:mm:ss'),
          this.formatDate(row.endTime, 'YYYY-MM-DD HH:mm:ss')
        ],
        activityContent: row.activityContent,
        cover: row.cover,
        coverType: row.coverType,
        status: row.status,
        reportStartTime: row.reportStartTime,
        reportEndTime: row.reportEndTime,
        startTime: row.startTime,
        endTime: row.endTime,
        recommend: row.recommend,
        limitUserNum: row.limitUserNum,
        activityLocation: row.activityLocation
      }
      // 设置封面类型
      this.coverType = row.coverType === 'text' ? '输入文字封面' : '选择文件封面'
    })
  },
  infoModelClose() {
    this.infoModelShow = false
  },
  // 关闭模态框
  modelClose() {
    this.modelShow = false
    this.file = null
    this.submitLoading = false
    this.handleRemove();
    this.$refs.form.resetFields()
  },
  // 保存或更新活动
  saveOrUpdate() {
    // 设置封面类型
    this.activityFrom.coverType = this.coverType === '输入文字封面' ? 'text' :
      this.file ? this.getFileCategory(this.file.type) :
        this.activityFrom.coverType
    this.$refs.form.validate(valid => {
      if (valid) {
        this.submitLoading = true
        // 处理时间数据
        if (this.activityFrom.reportTime && this.activityFrom.reportTime.length === 2) {
          this.activityFrom.reportStartTime = this.formatDate(this.activityFrom.reportTime[0], 'YYYY-MM-DD HH:mm:ss')
          this.activityFrom.reportEndTime = this.formatDate(this.activityFrom.reportTime[1], 'YYYY-MM-DD HH:mm:ss')
        }
        if (this.activityFrom.time && this.activityFrom.time.length === 2) {
          this.activityFrom.startTime = this.formatDate(this.activityFrom.time[0], 'YYYY-MM-DD HH:mm:ss')
          this.activityFrom.endTime = this.formatDate(this.activityFrom.time[1], 'YYYY-MM-DD HH:mm:ss')
        }
        const api = this.activityFrom.id ? editActivity : addActivity
        api(this.activityFrom).then(res => {
          this.submitLoading = false
          if (res.code === 200) {
            this.$Message.success(res.msg)
            this.modelClose()
            this.getActivityList()
          }
        }).catch(() => {
          this.submitLoading = false
        })
      }
    })
  },
  // 删除活动
  delById(row) {
    this.$Modal.confirm({
      title: '确认删除',
      content: `确定要删除活动 "${row.activityName}" 吗?`,
      onOk: () => {
        //TODO 先判断活动是否发布,发布则需要先下架
        delActivityById(row.id).then(res => {
          if (res.code === 200) {
            this.$Message.success(res.msg)
            this.getActivityList()
          }
        })
      }
    })
  },
  // 批量删除
  delBatch() {
    //TODO 先判断活动是否发布,发布则需要先下架
    if (this.selectCount === 0) {
      this.$Message.warning('请至少选择一条数据')
      return
    }
    this.$Modal.confirm({
      title: '确认删除',
      content: `确定要删除选中的 ${this.selectCount} 条数据吗?`,
      onOk: () => {
        delActivityBatch(this.selectList).then(res => {
          if (res.code === 200) {
            this.$Message.success(res.msg)
            this.selectList = []
            this.selectCount = 0
            this.getActivityList()
          } else {
            this.$Message.error(res.msg || '删除失败')
          }
        })
      }
    })
  },
  // 改变推荐状态
  changeRecommend(row, action) {
    row.recommendLoading = true
    const recommend = action === '推荐'
    activityChangeRecommend({
      id: row.id,
      recommend: recommend
    }).then(res => {
      row.recommendLoading = false
      if (res.code === 200) {
        this.$Message.success(res.msg)
        row.recommend = recommend
      }
    }).catch(() => {
      row.recommendLoading = false
    })
  },
  // 改变活动状态
  changeStatus(row, action) {
    //判断是否存在审核记录
    if (row.audit === null) {
      this.$Message.error("发布前请先审核!")
      return
    }
    row.statusLoading = true
    const publish = action === '发布'
    activityChangeStatus({
      id: row.id,
      publish: publish
    }).then(res => {
      row.statusLoading = false
      if (res.code === 200) {
        this.$Message.success(res.msg)
        row.publish = publish
      }
    }).catch(() => {
      row.statusLoading = false
    })
  },
  // 打开报名人员模态框
  openMembersModal(row) {
    this.membersModelTitle = `${row.activityName} - 报名人员`
    this.membersModelShow = true
    this.memberForm.id = row.id
    this.memberForm.pageNumber = 1
    this.activityMembersPage()
  },
  // 获取报名人员列表
  activityMembersPage() {
    this.membersLoading = true
    activityMembersPage(this.memberForm).then(res => {
      this.membersLoading = false
      if (res.code === 200) {
        this.membersList = res.data
        this.memberTotal = res.total
      }
    }).catch(() => {
      this.membersLoading = false
    })
  },
  // 改变报名人员页码
  memberChangePage(page) {
    this.memberForm.pageNumber = page
    this.activityMembersPage()
  },
  // 改变报名人员每页条数
  memberChangePageSize(pageSize) {
    this.memberForm.pageNumber = 1
    this.memberForm.pageSize = pageSize
    this.activityMembersPage()
  },
  // 关闭报名人员模态框
  membersModelClose() {
    this.membersModelShow = false
  },
  // 封面类型变化处理
  handleCoverTypeChange(type) {
    if (type === '选择文件封面') {
      this.activityFrom.cover = ''
    } else {
      this.file = null
    }
  },
  // 文件上传前处理
  handleBeforeUpload(file) {
    const fileType = file.type
    const isImage = fileType.includes('image')
    const isVideo = fileType.includes('video')
    if (!isImage && !isVideo) {
      this.$Message.error('请上传图片或视频文件')
      return false
    }
    if (file.size > 20 * 1024 * 1024) {
      this.$Message.error('文件大小不能超过20MB')
      return false
    }
    this.file = file
    this.uploadFile()
    return false
  },
  // 上传文件
  uploadFile() {
    if (!this.file) return
    this.submitLoading = true
    const formData = new FormData()
    formData.append('file', this.file)
    uploadFileByLmk(formData).then(res => {
      this.submitLoading = false
      if (res.code === 200) {
        this.activityFrom.cover = res.data.fileKey
        this.$Message.success('上传成功')
      }
    }).catch(() => {
      this.submitLoading = false
    })
  },
  // 删除文件
  handleRemove() {
    //点击关闭窗口时确保文件已被清除
    if (this.file === null) {
      return;
    }
    if (!this.activityFrom.cover) {
      this.file = null
      return
    }
    delByKey(this.activityFrom.cover).then(res => {
      if (res.code === 200) {
        this.file = null
        this.activityFrom.cover = ''
      }
    })
  },
  // 预览图片
  previewImage(url) {
    this.previewImageUrl = url
    this.previewVisible = true
  },
  // 获取文件分类
  getFileCategory(mimeType) {
    const typeMap = {
      'image': 'image',
      'video': 'video',
      'audio': 'audio',
      'application': 'application'
    }
    const typePrefix = mimeType.split('/')[0]
    return typeMap[typePrefix] || 'unknown'
  },
  // 格式化日期
  formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
    if (!date) return '';
    const d = new Date(date);
    if (isNaN(d.getTime())) return '';
    const padZero = (num) => String(num).padStart(2, '0'); // 更可靠的补零方法
    const year = d.getFullYear();
    const month = padZero(d.getMonth() + 1); // 月份 0-11 → +1
    const day = padZero(d.getDate());
    const hours = padZero(d.getHours());
    const minutes = padZero(d.getMinutes());
    const seconds = padZero(d.getSeconds());
    return format
      .replace('YYYY', year)
      .replace('MM', month)
      .replace('DD', day)
      .replace('HH', hours)
      .replace('mm', minutes)
      .replace('ss', seconds);
  },
  // 验证报名时间
  validateReportTime(rule, value, callback) {
    if (!value || value.length !== 2) {
      callback(new Error('请选择完整的报名时间段'))
      return
    }
    const [start, end] = value
    if (new Date(start) >= new Date(end)) {
      callback(new Error('报名结束时间必须晚于开始时间'))
      return
    }
    if (this.activityFrom.time && this.activityFrom.time.length === 2) {
      const activityStart = this.activityFrom.time[0]
      if (new Date(end) > new Date(activityStart)) {
        callback(new Error('报名结束时间不能晚于活动开始时间'))
        return
      }
    }
    callback()
  },
  // 验证活动时间
  validateActivityTime(rule, value, callback) {
    if (!value || value.length !== 2) {
      callback(new Error('请选择完整的活动时间段'))
      return
    }
    const [start, end] = value
    if (new Date(start) >= new Date(end)) {
      callback(new Error('活动结束时间必须晚于开始时间'))
      return
    }
    if (this.activityFrom.reportTime && this.activityFrom.reportTime.length === 2) {
      const reportEnd = this.activityFrom.reportTime[1]
      if (new Date(reportEnd) > new Date(start)) {
        callback(new Error('活动开始时间必须晚于报名结束时间'))
        return
      }
    }
    callback()
    }
  },
}
</script>
<style lang="scss" scoped>
.export {
  margin: 10px 20px 10px 0;
.quill-editor {
}
.export-excel-wrapper {
  display: inline;
.ql-editor .ql-video {
  width: 50%;
  height: auto; /* 根据你的需求调整高度 */
  max-width: 100%;
}
.order-tab {
  width: 950px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background-color: #f0f0f0;
  padding: 0 10px;
  margin-bottom: 10px;
  div {
    text-align: center;
    padding: 4px 12px;
.activity-management {
  .search-form {
    padding: 16px;
    background: #f8f8f9;
    border-radius: 4px;
    cursor: pointer;
    margin-bottom: 16px;
    .ivu-form-item {
      margin-bottom: 16px;
      margin-right: 16px;
    }
    .search-btn {
      margin-left: 8px;
    }
  }
  .current {
    background-color: #ffffff;
  .operation {
    margin-bottom: 16px;
    .ivu-btn {
      margin-right: 8px;
    }
  }
  .activity-table {
    .media-container {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100px;
      .thumbnail {
        max-width: 100%;
        max-height: 100%;
        object-fit: contain;
        cursor: pointer;
        transition: all 0.3s;
        &:hover {
          transform: scale(1.05);
          box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
        }
      }
      .video-player {
        max-width: 100%;
        max-height: 100px;
        background: #000;
      }
      .text-cover {
        padding: 8px;
        background: #f8f8f9;
        border-radius: 4px;
        max-width: 100%;
        word-break: break-all;
      }
    }
    .action-btns {
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      .ivu-btn {
        margin: 4px;
        font-size: 12px;
        padding: 2px 6px;
        min-width: 60px;
      }
    }
  }
  .page-footer {
    margin-top: 16px;
    padding: 8px 0;
  }
  .members-modal {
    .members-table {
      margin-bottom: 16px;
    }
  }
  .upload-file-info {
    margin-top: 8px;
    padding: 8px;
    background: #f8f8f9;
    border-radius: 4px;
  }
  .upload-tip {
    font-size: 12px;
    color: #999;
    margin-top: 4px;
  }
}
.detail-container {
  padding: 16px;
}
.detail-item {
  margin-bottom: 18px;
  line-height: 1.5;
  label {
    display: inline-block;
    width: 100px;
    color: #666;
    font-weight: bold;
    vertical-align: top;
  }
  span {
    display: inline-block;
    width: calc(100% - 110px);
  }
}
.activity-content {
  border: 1px solid #dcdee2;
  border-radius: 4px;
  padding: 12px;
  min-height: 100px;
  margin-top: 8px;
}
/*
  文字大小
*/
.ql-snow .ql-picker.ql-size{
  width: 70px;  // 菜单栏占比宽度
}
/*
  字体
*/
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=SimHei]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=SimHei]::before {
  content: "黑体";
  font-family: "SimHei";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Microsoft-YaHei]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Microsoft-YaHei]::before {
  content: "微软雅黑";
  font-family: "Microsoft YaHei";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=KaiTi]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=KaiTi]::before {
  content: "楷体";
  font-family: "KaiTi";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=FangSong]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=FangSong]::before {
  content: "仿宋";
  font-family: "FangSong";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Arial]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Arial]::before {
  content: "Arial";
  font-family: "Arial";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Times-New-Roman]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Times-New-Roman]::before {
  content: "Times New Roman";
  font-family: "Times New Roman";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=sans-serif]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=sans-serif]::before {
  content: "sans-serif";
  font-family: "sans-serif";
}
.ql-font-SimSun {
  font-family: "SimSun";
}
.ql-font-SimHei {
  font-family: "SimHei";
}
.ql-font-Microsoft-YaHei {
  font-family: "Microsoft YaHei";
}
.ql-font-KaiTi {
  font-family: "KaiTi";
}
.ql-font-FangSong {
  font-family: "FangSong";
}
.ql-font-Arial {
  font-family: "Arial";
}
.ql-font-Times-New-Roman {
  font-family: "Times New Roman";
}
.ql-font-sans-serif {
  font-family: "sans-serif";
}
</style>