zxl
2025-08-21 bd57e65508781d967dedff5bb535dfefdb56e9e2
抽奖活动,奖品
2个文件已修改
2个文件已添加
1645 ■■■■■ 已修改文件
manager/src/api/activity-prize.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
manager/src/api/prize.js 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
manager/src/views/activity-prize/index.vue 661 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
manager/src/views/activity-prize/prize.vue 923 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
manager/src/api/activity-prize.js
@@ -33,3 +33,22 @@
    method: "DELETE",
  })
}
export const addActivityRefPrizeList = (id,params) =>{
  return service({
    url:"/lmk/activity-prize/addActivityRefPrizeList/" +id,
    method: "POST",
    data:params
  })
}
export const getActivityRefPrizeByActivityId = (params) =>{
  return service({
    url:"/lmk/activity-prize/getActivityRefPrizeByActivityId/"+params,
    method: "GET",
  })
}
export const publishPrizeActivity =(params)=> {
  return service({
    url:"/lmk/activity-prize/publishPrizeActivity/" +params,
    method:"PUT"
  })
}
manager/src/api/prize.js
New file
@@ -0,0 +1,42 @@
import service from "../libs/axios";
export const getPage = (params) =>{
  return service({
    url: "/lmk/prizeDraw",
    method: "GET",
    params: params
  })
}
export const detail = (params) =>{
  return service({
    url: "/lmk/prizeDraw/" +params,
    method: "GET",
  })
}
export const edit = (params) =>{
  return service({
    url: "/lmk/prizeDraw",
    method: "PUT",
    data: params
  })
}
export const add = (params) =>{
  return service({
    url: "/lmk/prizeDraw",
    method: "POST",
    data: params
  })
}
export const del = (params) =>{
  return service({
    url: "/lmk/prizeDraw/" +params,
    method: "DELETE",
  })
}
export const canUpDatePrizeDraw = (params) =>{
  return service({
    url: "/lmk/prizeDraw/canUpDatePrizeDraw/" +params,
    method: "GET",
  })
}
manager/src/views/activity-prize/index.vue
@@ -75,7 +75,7 @@
            <Button
              type="primary"
              size="small"
              @click="changeStatus(row, row.enableStatus === 'on' ? '关闭' : '开启')"
              @click="publishPrizeActivity(row)"
              :loading="row.statusLoading"
            >
              {{row.enableStatus === 'on' ? '关闭' : '开启'}}
@@ -188,6 +188,7 @@
            <Col span="24">
              <FormItem label="活动图片:" prop="activityImg">
                <Upload
                  v-if="!imgTempUrl"
                  :before-upload="(file) => handleBeforeUpload(file, 'content')"
                  :format="['jpg','jpeg','png','gif']"
                  :max-size="20480"
@@ -195,11 +196,9 @@
                  accept="image/*"
                >
                  <Button icon="ios-cloud-upload-outline">上传封面图片</Button>
                  <div class="upload-tip">支持图片,最大20MB</div>
                </Upload>
                <div v-if="activityImg" class="upload-file-info">
                  <p>已选文件: {{ activityImg.name }}</p>
                  <img :src="activityImg.activityImgUrl" alt="活动图片" style="max-width: 100%; max-height: 200px;">
                <div v-else class="upload-file-info">
                  <img :src="imgTempUrl" alt="活动图片" class="preview-image-limit">
                  <Button type="text" @click="handleRemove('content')">删除</Button>
                </div>
@@ -209,6 +208,7 @@
            <Col span="24">
              <FormItem label="活动封面:" prop="activityCover">
                <Upload
                  v-if="!coverTempUrl"
                  :before-upload="(file) => handleBeforeUpload(file, 'cover')"
                  :format="['jpg','jpeg','png','gif']"
                  :max-size="20480"
@@ -216,11 +216,9 @@
                  accept="image/*"
                >
                  <Button icon="ios-cloud-upload-outline">上传封面图片</Button>
                  <div class="upload-tip">支持图片,最大20MB</div>
                </Upload>
                <div v-if="activityCover" class="upload-file-info">
                  <p>已选文件: {{ activityCover.name }}</p>
                  <img :src="activityCover.activityCoverUrl" alt="活动图片" style="max-width: 100%; max-height: 200px;">
                <div v-else class="upload-file-info">
                  <img :src="coverTempUrl" alt="活动图片" class="preview-image-limit">
                  <Button type="text" @click="handleRemove('cover')">删除</Button>
                </div>
@@ -292,8 +290,8 @@
          <Col span="24">
            <div class="detail-item">
              <label>活动图片:</label>
              <div v-if="detailData.activityImg" class="detail-image">
                <img :src="detailData.activityImg" alt="活动图片" style="max-width: 100%; max-height: 200px;">
              <div v-if="detailData.activityImgUrl" class="detail-image">
                <img :src="detailData.activityImgUrl" alt="活动图片" class="preview-image-limit">
              </div>
              <span v-else>-</span>
            </div>
@@ -301,10 +299,10 @@
          <Col span="24">
            <div class="detail-item">
              <label>活动封面:</label>
              <div v-if="detailData.activityCover" class="detail-image">
              <div v-if="detailData.activityCoverUrl" class="detail-image">
                <img
                  :src="detailData.activityCover" alt="活动封面"
                  style="max-width: 100%; max-height: 200px;"
                  :src="detailData.activityCoverUrl" alt="活动封面"
                  class="preview-image-limit"
                >
              </div>
              <span v-else>-</span>
@@ -313,23 +311,185 @@
          <Col span="24">
            <div class="detail-item">
              <label>奖品:</label>
              <div style="width: 100%; overflow-y: auto; padding-bottom: 8px; box-sizing: border-box;height: 150px">
                <div v-if="detailData.prizes && detailData.prizes.length > 0" class="prize-list">
                  <div v-for="(prize, index) in detailData.prizes" :key="index" class="prize-item">
                    <div class="prize-header">
                      <span class="prize-name">{{  prize.prizeName }}</span>
                      <Tag v-if="prize.prizeType">
                        {{ replaceText(prize.prizeType) }}
                      </Tag>
                    </div>
                    <div class="prize-content">
                      <p v-if="prize.prizeContent">{{ prize.prizeContent }}</p>
                      <Row :gutter="16" class="prize-info">
                        <Col span="12">
                          <span>总数: {{ prize.prizeNum}}</span>
                        </Col>
                        <Col span="12">
                    <span>剩余:
                      <span>
                        {{ prize.remainNum }}
                      </span>
                    </span>
                        </Col>
                        <Col span="12">
                          <span>概率: {{ prize.probability}}%</span>
                        </Col>
                      </Row>
                    </div>
                  </div>
                </div>
                <div v-else class="no-prize">
                  <span>暂无奖品信息</span>
                </div>
              </div>
            </div>
          </Col>
        </Row>
      </div>
      </Modal>
      <Modal
        v-model="prizeSettingShow"
        title="奖品设置"
        @on-cancel="prizeSettingModelClose"
        width="1200"
        :mask-closable="false">
      <!-- 奖品部分 -->
        <Form  ref="activityPrizeForm"  :label-width="100" >
          <Col span="24">
            <FormItem label="奖品列表:">
              <Table
                :loading="prizeLoading"
                border
                :columns="prizeColumns"
                :data="prizeList"
                ref="table"
                class="prize-table"
              >
                <template slot-scope="{ row }" slot="prizeCoverUrl">
                  <img
                    :src="row.prizeCoverUrl"
                    alt="奖品封面"
                    class="thumbnail"
                  >
                </template>
                <template slot-scope="{ row }" slot="action">
                  <Button
                    @click="choicePrize(row)"
                    :disabled="isPrizeChosen(row.id)"
                    type="primary"
                    icon="md-add"
                  >
                    {{ isPrizeChosen(row.id) ? '已添加' : '添加' }}
                  </Button>
                </template>
              </Table>
              <Row type="flex" justify="end" class="page-footer">
                <Page
                  :current="prizeSearchForm.pageNumber"
                  :total="prizeTotal"
                  :page-size="prizeSearchForm.pageSize"
                  @on-change="prizeChangePage"
                  @on-page-size-change="prizeChangePageSize"
                  :page-size-opts="[10, 20, 50]"
                  size="small"
                  show-total
                  show-elevator
                  show-sizer
                ></Page>
              </Row>
            </FormItem>
          </Col>
          <Col span="24">
            <FormItem label="已选奖品:">
              <!-- 遍历已选奖品,并可以设置数量等属性 -->
              <Table
                :data="choiceData"
                :columns="choiceColumns"
                border
                style="margin-top: 10px;"
                :loading="choiceLoading"
              >
                <!-- 奖品图片 slot -->
                <template slot-scope="{ row }" slot="prizeCoverUrl">
                  <img :src="row.prizeCoverUrl" alt="奖品封面" style="width: 50px; height: 50px; object-fit: cover;">
                </template>
                <!-- 数量 slot -->
                <template slot-scope="{ row, index }" slot="maxPreDay">
                  <InputNumber
                    v-model="row.maxPreDay"
                    :min="1"
                    :max="999999"
                    placeholder="请输入每日最大中奖数"
                    style="width: 100%"
                    @on-change="(val) => handleMaxPreDayChange(val, index)"
                  ></InputNumber>
                </template>
                <template slot-scope="{ row, index }" slot="prizeNum">
                  <InputNumber
                    v-model="row.prizeNum"
                    :min="1"
                    :max="999999"
                    placeholder="请输入每日最大中奖数"
                    style="width: 100%"
                    @on-change="(val) => handlePrizeNumChange(val, index)"
                  ></InputNumber>
                </template>
                <!-- 概率 slot -->
                <template slot-scope="{ row, index }" slot="prizeProbability">
                  <Input
                    v-model="row.prizeProbability"
                    placeholder="0.01"
                    style="width: 100%"
                    @on-blur="(e) => handleProbabilityInput(e, index)"
                    @on-enter="(e) => handleProbabilityInput(e, index)"
                  ></Input>
                </template>
                <!-- 操作 slot -->
                <template slot-scope="{ row, index }" slot="action">
                  <Button type="error" size="small" icon="md-close" @click="removePrize(index)">移除</Button>
                </template>
              </Table>
              <!-- 如果没有选择任何奖品时的提示 -->
              <div v-if="choiceData.length === 0" style="text-align: center; color: #999; padding: 20px;">
                尚未选择任何奖品
              </div>
            </FormItem>
          </Col>
        </Form>
        <div slot="footer">
          <Button @click="prizeSettingModelClose">取消</Button>
          <Button type="primary" :loading="choiceSubmitLoading" @click="choiceSubmit">提交</Button>
        </div>
      </Modal>
    </Card>
  </div>
</template>
<script>
import {
  getPage as getPrizePage
} from '@/api/prize.js'
import {
  getPage,
  detail,
  edit,
  add,
  del
  del,
  addActivityRefPrizeList,
  getActivityRefPrizeByActivityId,
  publishPrizeActivity
} from '@/api/activity-prize.js'
import {delByKey, uploadFileByLmk} from "../../api/common";
@@ -338,6 +498,112 @@
  data() {
    return {
      //奖品设置弹窗
      choiceLoading:false,
      choiceSubmitLoading:false,
      prizeTotal:0,
      choiceData:[],
      choiceColumns:[
        {
          title: '奖品图片',
          slot: 'prizeCoverUrl',
          width: 80,
          align: 'center'
        },
        {
          title: '奖品名称',
          key: 'prizeName',
          minWidth: 120
        },
        {
          title: '奖品类型',
          slot: 'prizeType',
          width: 100,
          render: (h, params) => {
            if (params.row.prizeType === "coupon") {
              return h("Tag", {props: {color: "red"}}, "优惠券");
            }  else {
              return h("Tag", {props: {color: "purple"}}, "未知");
            }
          }
        },
        {
          title: '每日最大中奖数',
          slot: 'maxPreDay',
          width: 120
        },
        {
          title: '数量',
          slot: 'prizeNum',
          width: 120
        },
        {
          title: '中将概率不能为空',
          slot: 'prizeProbability',
          width: 120
        },
        {
          title: '操作',
          slot: 'action',
          width: 80,
          align: 'center'
        }
      ],
      prizeLoading:false,
      prizeList:[],
      prizeSettingShow:false,
      prizeColumns:[
        {
          title: '奖品封面',
          key: 'prizeCoverUrl',
          slot:'prizeCoverUrl',
          align: 'center',
          minWidth: 100,
        },
        {
          title: '奖品名称',
          key: 'prizeName',
          minWidth: 100,
        },
        {
          title: '奖品类型',
          key: 'prizeType',
          minWidth: 100,
          render: (h, params) => {
            if (params.row.prizeType === "coupon") {
              return h("Tag", {props: {color: "red"}}, "优惠券");
            }  else {
              return h("Tag", {props: {color: "purple"}}, "未知");
            }
          }
        },
        {
          title: '奖品内容',
          key: 'prizeContent',
          minWidth: 100,
        },
        {
          title: '奖品描述',
          key: 'prizeDes',
          minWidth: 100,
        },
        {
          title:'操作',
          slot: 'action',
          width: 200,
          align:'center',
          fixed:'right'
        }
      ],
      activityPrizeId:null,
      prizeSearchForm:{
        prizeName:'',
        prizeType:'',
        pageSize:10,
        pageNumber:1,
      },
      infoModalShow:false,
      detailData: {},
      modelShow:false,
@@ -462,11 +728,14 @@
        activityName:'',
        endTime:'',
        beginTime:'',
      },
      submitLoading:false,
      activityCover:null,
      activityImg:null,
      coverTempUrl:null,
      imgTempUrl:null,
    }
  },
  // 在组件创建前注册
@@ -477,6 +746,125 @@
    this.init();
  },
  methods: {
    handleProbabilityInput(event, index) {
      const inputValue = event.target.value;
      let numericValue = parseFloat(inputValue);
      if (isNaN(numericValue)) {
        this.choiceData[index].prizeProbabilityDisplay = '0.01';
        this.choiceData[index].prizeProbability = 0.01;
        this.$Message.warning('请输入有效的数字');
        return;
      }
      // 限制范围
      if (numericValue < 0.01) {
        numericValue = 0.01;
        this.$Message.warning('中奖概率不能小于0.01');
      } else if (numericValue > 100) {
        numericValue = 100;
        this.$Message.warning('中奖概率不能大于100');
      }
      // 保持2位小数
      const fixedValue = parseFloat(numericValue.toFixed(2));
      this.choiceData[index].prizeProbabilityDisplay = fixedValue.toString();
      this.choiceData[index].prizeProbability = fixedValue;
      // 更新后端字段
      this.choiceData[index].probability = (fixedValue / 100).toFixed(4);
    },
    choiceSubmit(){
      addActivityRefPrizeList(this.activityPrizeId,this.choiceData).then(res =>{
        if (res.code === 200){
          this.$Message.success(res.msg)
          this.prizeSettingModelClose()
        }else{
          this.$Message.error(res.msg)
        }
      })
    },
    // 格式化奖品类型
    replaceText(type) {
      console.log(type)
      if (type === "coupon") {
        return "优惠券";
      }
      return type; // 如果不是目标词,返回原文本
    },
    // 数量变化处理
    handlePrizeNumChange(value, index) {
      if (value === null || value < 1) {
        this.choiceData[index].prizeNum = 1;
        this.$Message.warning('数量不能小于1');
      } else {
        this.choiceData[index].prizeNum = value;
      }
    },
    // 数量变化处理
    handleMaxPreDayChange(value, index) {
      if (value === null || value < 1) {
        this.choiceData[index].maxPreDay = 1;
        this.$Message.warning('数量不能小于1');
      } else {
        this.choiceData[index].maxPreDay = value;
      }
    },
    // 移除已选奖品
    removePrize(index) {
      this.choiceData.splice(index, 1);
      this.$Message.info('已移除奖品');
    },
    // 检查奖品是否已被选择
    isPrizeChosen(prizeId) {
      return this.choiceData.some(item => item.id === prizeId);
    },
    choicePrize(row){
      //判断数组长度
      if(this.choiceData.length >= 5){
        this.$Message.warning("最多添加5个奖品")
        return;
      }
      if (this.isPrizeChosen(row.id)){
        this.$Message.warning("已添加")
        return;
      }
      this.choiceLoading = true;
      const prizeToAdd = {
        prizeCoverUrl:row.prizeCoverUrl,
        prizeType: row.prizeType,
        prizeName: row.prizeName,
        prizeNum: 1, // 默认数量为1
        maxPreDay:1,
        prizeProbability:0.01,
        prizeId:row.id,
        prizeContent:row.prizeContent
        // 可以添加其他扩展属性,如:
        // sort: this.choiceData.length + 1 // 排序
      };
      this.choiceData.push(prizeToAdd);
      this.choiceLoading = false;
      this.$Message.success('添加成功!');
    },
    getPrizeList(){
      this.prizeLoading = true;
      getPrizePage(this.prizeSearchForm).then(res =>{
        this.prizeLoading = false;
        if (res.code === 200){
          this.prizeList = res.data;
          this.prizeTotal = res.total
        }else {
          this.$Message.error(res.msg)
        }
      })
    },
    //规则
    validateBeginTime(rule, value, callback) {
      console.log("触发验证")
      // 校验必填
@@ -503,9 +891,35 @@
      }
      callback(); // 验证通过
    },
    //奖品设置
    setPrize(row){
      //判断是否启动,若启动则,无法编辑
      if("on" === row.enableStatus){
        this.$Message.error("抽奖已开启,不能编辑!")
        return
      }
      this.activityPrizeId = row.id;
      this.getPrizeList();
      this.prizeSettingShow = true;
      this.choiceLoading = true;
      getActivityRefPrizeByActivityId(row.id).then(res =>{
        this.choiceLoading = false;
        if (res.code === 200){
          this.choiceData = res.data
        }
      })
    },
    prizeSettingModelClose(){
      this.activityPrizeId = null;
      this.prizeSettingShow = false;
      this.choiceData = [];
    },
    //文本转换
    formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
      if (!date) return '';
@@ -534,87 +948,70 @@
      this.getPage()
    },
    handleBeforeUpload(file,type){
      if ("content" === type){
        this.activityImg = file
      if("content" === type){
        this.activityImg = file;
        this.imgTempUrl = URL.createObjectURL(file);
        return false;
      }else if ("cover" === type){
        this.activityCover = file;
        this.coverTempUrl = URL.createObjectURL(file);
        return false;
      }
    },
    // 删除文件
    handleRemove(type) {
      if ('content' === type){
        this.activityImg = null;
        this.imgTempUrl = null;
      }else if ('cover' === type){
        this.activityCover = null;
        this.coverTempUrl = null;
      }
    },
    async uploadFileByLmk(){
      if (this.activityImg){
        this.submitLoading = true
        const formData = new FormData()
        formData.append('file', this.activityImg)
        uploadFileByLmk(formData).then(res => {
        await uploadFileByLmk(formData).then(res => {
          this.submitLoading = false
          if (res.code === 200) {
            this.activityFrom.activityImg = res.data.fileKey;
            this.activityImg.activityImgUrl = res.data.url;
            this.$Message.success('上传成功')
            // this.$Message.success('上传成功')
          }else{
            this.$Message.error(res.msg)
            // this.$Message.error(res.msg)
          }
        }).catch(() => {
          this.submitLoading = false
        })
      }else if ("cover" === type){
        this.activityCover = file
      }
      if (this.activityCover){
        this.submitLoading = true
        const formData = new FormData()
        formData.append('file', this.activityCover)
        uploadFileByLmk(formData).then(res => {
        await uploadFileByLmk(formData).then(res => {
          this.submitLoading = false
          if (res.code === 200) {
            this.activityFrom.activityCover = res.data.fileKey
            this.activityCover.activityCoverUrl = res.data.url;
            this.$Message.success('上传成功')
            // this.$Message.success('上传成功')
          }else{
            this.$Message.error(res.msg)
            // this.$Message.error(res.msg)
          }
        }).catch(() => {
          this.submitLoading = false
        })
      }
    },
    // 删除文件
    handleRemove(type) {
      if ("content" === type){
        //点击关闭窗口时确保文件已被清除
        if (this.activityImg === null) {
          return;
        }
        if (!this.activityFrom.activityImg) {
          this.activityImg = null
          return
        }
        delByKey(this.activityFrom.activityImg).then(res => {
          if (res.code === 200) {
            this.activityImg = null
            this.activityFrom.activityImg = ''
          }else{
            this.$Message.error(res.msg)
          }
        })
      }else if ("cover" === type){
        if (this.activityCover === null) {
          return;
        }
        if (!this.activityFrom.activityCover) {
          this.activityCover = null
          return
        }
        delByKey(this.activityFrom.activityCover).then(res => {
          if (res.code === 200) {
            this.activityCover = null
            this.activityFrom.activityCover = ''
          }else{
            this.$Message.error(res.msg)
          }
        })
    async saveOrUpdate(){
      if (this.activityFrom.maxPrize < this.activityFrom.prizeNum){
        this.$Message.error("初始化次数因小于或等于最大抽奖次数")
        return
      }
      await this.uploadFileByLmk();
    },
    saveOrUpdate(){
      const submitData = {
        ...this.activityFrom,
        beginTime: this.formatDate(this.activityFrom.beginTime, 'YYYY-MM-DD HH:mm:ss'),
@@ -629,7 +1026,7 @@
            this.submitLoading = false
            if (res.code === 200) {
              this.$Message.success(res.msg)
              this.modelClose()
              this.modelShow = false;
              this.getPage()
            }else{
              this.$Message.error(res.msg)
@@ -646,8 +1043,6 @@
    modelClose(){
      this.modelShow = false
      this.submitLoading = false
      this.handleRemove("content");
      this.handleRemove("cover");
      this.$refs.form.resetFields()
    },
    getPage(){
@@ -656,6 +1051,7 @@
        this.loading = false;
        if (res.code === 200){
          this.activityList = res.data;
          this.total = res.total;
        }else {
          this.$Message.error(res.msg)
        }
@@ -664,6 +1060,13 @@
    detail(row){
      this.infoModalShow = true;
      this.detailData = {...row};
      getActivityRefPrizeByActivityId(row.id).then(res=>{
        if (res.code ===200){
          this.$set(this.detailData,'prizes', res.data);
        }
      })
      console.log(this.detailData)
    },
    openEdit(row){
      this.modelShow = true;
@@ -680,6 +1083,9 @@
        activityCover:row.activityCover,
        enableStatus:row.enableStatus,
      }
      this.coverTempUrl = row.activityCoverUrl;
      this.imgTempUrl = row.activityImgUrl;
    },
    handleSearch(type,value){
@@ -691,8 +1097,13 @@
      this.getPage()
    },
    openAdd(){
      this.$refs.form.resetFields()
      this.modelShow = true;
      this.modelTitle = "新增活动";
      this.coverTempUrl = null;
      this.prizeCover=null;
      this.prizeImg = null;
      this.imgTempUrl = null;
    },
    delById(row){
      del(row.id).then(res=>{
@@ -718,8 +1129,24 @@
      this.searchForm.pageNumber = page
      this.getPage()
    },
    changeStatus(){
    prizeChangePage(){
      this.prizeSearchForm.pageNumber = 1
      this.prizeSearchForm.pageSize = pageSize
      this.getPrizeList()
    },
    prizeChangePageSize(){
      this.prizeSearchForm.pageNumber = page
      this.getPrizeList()
    },
    publishPrizeActivity(row){
      publishPrizeActivity(row.id).then(res =>{
        if (res.code === 200){
          this.$Message.success(res.msg)
        }else{
          this.$Message.error(res.msg)
        }
      })
      this.getPage()
    },
  },
@@ -780,12 +1207,6 @@
  }
}
.activity-table {
  .media-container {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100px;
    .thumbnail {
      max-width: 100%;
      max-height: 100%;
@@ -798,21 +1219,32 @@
        box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
      }
    }
  .action-btns {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    .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;
    .ivu-btn {
      margin: 4px;
      font-size: 12px;
      padding: 2px 6px;
      min-width: 60px;
    }
  }
}
.prize-table {
    .thumbnail {
      max-width: 100px;
      max-height: 100px;
      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);
      }
    }
  .action-btns {
    display: flex;
@@ -827,4 +1259,47 @@
    }
  }
}
.preview-image-limit{
  max-width: 100%;
  max-height: 200px;
  object-fit: contain;
  border-radius: 4px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  display: block;
}
//详情商品样式
.prize-list {
  margin-top: 10px;
  max-height: 300px;
  overflow-y: auto;
}
.prize-item {
  padding: 12px;
  border: 1px solid #e8eaec;
  border-radius: 4px;
  margin-bottom: 10px;
  background: #f8f9fa;
}
.prize-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}
.prize-name {
  font-weight: bold;
  color: #17233d;
}
.prize-content {
  font-size: 13px;
}
.prize-info {
  margin-top: 8px;
  color: #808695;
}
</style>
manager/src/views/activity-prize/prize.vue
New file
@@ -0,0 +1,923 @@
<template>
  <div>
    <Card>
      <!-- 搜索表单 -->
      <Form
        ref="searchForm"
        @keydown.enter.native="handleSearch"
        :model="searchForm"
        inline
        :label-width="80"
        class="search-form"
      >
        <FormItem label="奖品名" prop="prizeName">
          <Input
            type="text"
            v-model="searchForm.prizeName"
            placeholder="请输入奖品名"
            clearable
            @on-clear="handleSearch"
            @on-change="handleSearch"
            style="width: 180px"
          />
        </FormItem>
        <FormItem label="奖品类型" prop="prizeType">
          <Select
            v-model="searchForm.prizeType"
            placeholder="请选择奖品类型"
            style="width: 180px"
            clearable
            @on-clear="handleSearch"
            @on-change="handleSearch"
          >
            <Option
              v-for="item in typeSelect"
              :value="item.value"
              :key="item.id"
            >
              {{ item.label }}
            </Option>
          </Select>
        </FormItem>
        <Button
          @click="handleSearch"
          type="primary"
          icon="ios-search"
          class="search-btn"
        >搜索</Button>
        <Button
          @click="resetSearch"
          icon="md-refresh"
          style="margin-left: 8px"
        >重置</Button>
      </Form>
      <!-- 操作按钮 -->
      <Row class="operation">
        <Button @click="openAdd" type="primary" icon="md-add">新增奖品</Button>
      </Row>
      <!-- 奖品表格 -->
      <Table
        :loading="loading"
        border
        :columns="columns"
        :data="prizeList"
        ref="table"
        class="prize-table"
      >
        <template slot-scope="{ row }" slot="prizeCoverUrl">
          <img
            :src="row.prizeCoverUrl"
            alt="奖品封面"
            class="thumbnail"
            @click="previewImage(row.prizeCoverUrl)"
          >
        </template>
        <!-- 操作按钮插槽 -->
        <template slot-scope="{ row }" slot="action">
          <div class="action-btns">
            <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>
          </div>
        </template>
      </Table>
      <!-- 分页 -->
      <Row type="flex" justify="end" class="page-footer">
        <Page
          :current="searchForm.pageNumber"
          :total="total"
          :page-size="searchForm.pageSize"
          @on-change="changePage"
          @on-page-size-change="changePageSize"
          :page-size-opts="[10, 20, 50]"
          size="small"
          show-total
          show-elevator
          show-sizer
        ></Page>
      </Row>
      <!-- 奖品编辑/新增模态框 -->
      <Modal
        v-model="modelShow"
        :title="modelTitle"
        @on-cancel="modelClose"
        width="1200"
        :mask-closable="false"
      >
        <Form ref="form" :model="prizeFrom" :label-width="100" :rules="rules">
          <Row :gutter="16">
            <Col span ="12">
              <FormItem label="奖品类型" prop="prizeType">
                <Select
                  v-model="prizeFrom.prizeType"
                  placeholder="请选择奖品类型"
                  style="width: 180px"
                  clearable
                >
                  <Option
                    v-for="item in typeSelect"
                    :value="item.value"
                    :key="item.id"
                  >
                    {{ item.label }}
                  </Option>
                </Select>
              </FormItem>
            </Col>
            <Col span ="24">
<!--              <div >-->
                <FormItem label="已选优惠劵" prop="couponId" v-if="prizeFrom.prizeType === 'coupon'" style="margin-bottom: 20px">
                  <Input :disabled="true" style="width: 30%;margin-bottom: 20px" v-model="showCoupon" placeholder="请点击选择表格内优惠劵"></Input>
                  <Table
                    :loading="couponLoading"
                    border
                    :columns="couponColumns"
                    :data="couponData"
                    ref="table"
                    @on-current-change="handleRowClick"
                    highlight-row
                  ></Table>
                  <Row type="flex" justify="center" class="mt_10">
                    <Page
                      :current="couponSearchForm.pageNumber"
                      :total="couponTotal"
                      :page-size="couponSearchForm.pageSize"
                      @on-change="couponChangePage"
                      @on-page-size-change="couponChangePageSize"
                      :page-size-opts="[10, 20, 50]"
                      size="small"
                      show-total
                      show-elevator
                      show-sizer
                    ></Page>
                  </Row>
                </FormItem>
<!--              </div>-->
            </Col>
            <Col span="24">
                <FormItem label="奖品名称" prop="prizeName">
                  <Input
                    v-model="prizeFrom.prizeName"
                    placeholder="请输入奖品名称"
                    clearable
                    style="width: 180px"
                  />
                </FormItem>
              </Col>
            <Col span="12">
                <FormItem label="奖品内容" prop="prizeContent" :label-width="100">
                  <Input v-model="prizeFrom.prizeContent"
                         style="width: 300px" type="textarea" :autosize="{minRows: 2,maxRows: 5}" placeholder="请输入"></Input>
                </FormItem>
            </Col>
            <Col span="12">
              <FormItem label="奖品描述" prop="prizeDes" :label-width="100">
                <Input v-model="prizeFrom.prizeDes"
                       style="width: 300px" type="textarea" :autosize="{minRows: 2,maxRows: 5}" placeholder="请输入"></Input>
              </FormItem>
            </Col>
            <Col span="24">
                <FormItem label="奖品封面:" prop="prizeCover">
                  <Upload
                    v-if="!coverTempUrl"
                    :before-upload="(file) => handleBeforeUpload(file, 'cover')"
                    :format="['jpg','jpeg','png','gif']"
                    :max-size="20480"
                    action=""
                    accept="image/*"
                  >
                    <Button icon="ios-cloud-upload-outline" >上传封面文件</Button>
                  </Upload>
                  <div v-else class="upload-file-info">
                    <img :src="coverTempUrl" alt="奖品封面" class="preview-image-limit">
                    <Button type="text" @click="handleRemove('cover')">删除</Button>
                  </div>
                </FormItem>
              </Col>
            <Col span="24">
                <FormItem label="奖品图片:" prop="prizeImg">
                  <Upload
                    v-if="!imgTempUrl"
                    :before-upload="(file) => handleBeforeUpload(file, 'content')"
                    :format="['jpg','jpeg','png','gif']"
                    :max-size="20480"
                    action=""
                    accept="image/*"
                  >
                    <Button icon="ios-cloud-upload-outline" >上传图片</Button>
                  </Upload>
                  <div v-if="imgTempUrl" class="upload-file-info">
                    <img :src="imgTempUrl" alt="奖品图片" class="preview-image-limit">
                    <Button type="text" @click="handleRemove('content')">删除</Button>
                  </div>
                  <!-- 基于elementUi的上传组件 el-upload end-->
                </FormItem>
            </Col>
          </Row>
        </Form>
        <div slot="footer">
          <Button @click="modelClose">取消</Button>
          <Button type="primary" :loading="submitLoading" @click="saveOrUpdate">提交</Button>
        </div>
      </Modal>
      <!-- 图片预览模态框 -->
      <Modal v-model="previewVisible" title="图片预览" footer-hide>
        <img :src="previewImageUrl" class="preview-image">
      </Modal>
      <!-- 详情-->
      <Modal
        v-model="infoModalShow"
        title="奖品详情"
        @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>{{ detailData.prizeName }}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>奖品类型:</label>
                <span>
                  {{replaceText(detailData.prizeType)}}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>奖品内容:</label>
                <span>{{ detailData.prizeContent}}</span>
              </div>
            </Col>
            <Col span="12">
              <div class="detail-item">
                <label>奖品描述:</label>
                <span>{{ detailData.prizeDes}}</span>
              </div>
            </Col>
            <Col span="24">
              <div class="detail-item">
                <label>奖品封面:</label>
                <div v-if="detailData.prizeCoverUrl" class="detail-image">
                  <img
                    :src="detailData.prizeCoverUrl" alt="奖品封面"
                    class="preview-image-limit"
                  >
                </div>
                <span v-else>-</span>
              </div>
            </Col>
            <Col span="24">
              <div class="detail-item">
                <label>奖品图片:</label>
                <div v-if="detailData.prizeImgUrl" class="detail-image">
                  <img :src="detailData.prizeImgUrl" alt="奖品图片" class="preview-image-limit">
                </div>
                <span v-else>-</span>
              </div>
            </Col>
          </Row>
        </div>
      </Modal>
    </Card>
  </div>
</template>
<script>
import {
  getPage,
  detail,
  edit,
  add,
  del,
  canUpDatePrizeDraw
} from '@/api/prize.js'
import {delByKey, uploadFileByLmk} from "../../api/common";
import {promotionsScopeTypeRender, promotionsStatusRender} from "../../utils/promotions";
import {getPlatformCouponList} from "../../api/promotion";
export default {
  name: "prize",
  watch: {
    'prizeFrom.prizeType'(newVal) {
      if (newVal === 'coupon') {
        this.getCouponDataList(); // 加载优惠券列表
      } else {
        this.prizeFrom.couponId = ''; // 清空已选的优惠券
      }
    }
  },
  data() {
    return {
      infoModalShow:false,
      detailData: {},
      showCoupon:'',
      coverTempUrl:null,
      imgTempUrl:null,
      // 图片预览
      previewVisible: false,
      previewImageUrl: '',
      //优惠劵
      selectedRowId:'',
      couponTotal:0,
      couponData:[],
      couponLoading:false,
      couponColumns: [
        // 表头
        {
          title: "优惠券名称",
          key: "couponName",
          width: 180,
          tooltip: true,
        },
        {
          title: "面额/折扣",
          key: "price",
          width: 150,
          render: (h, params) => {
            if (params.row.price) {
              return h("priceColorScheme", {props:{value:params.row.price,color:this.$mainColor}} );
            } else {
              return h("div", params.row.couponDiscount + "折");
            }
          },
        },
        {
          title: "获取方式",
          width: 120,
          key: "getType",
          render: (h, params) => {
            if (params.row.getType === "FREE") {
              return h("Tag", { props: { color: "red" } }, "免费获取");
            } else if (params.row.getType === "ACTIVITY") {
              return h("Tag", { props: { color: "volcano" } }, "活动获取");
            } else if (params.row.getType === "INSIDE") {
              return h("Tag", { props: { color: "lime" } }, "内购");
            } else if (params.row.getType === "IGAME") {
              return h("Tag", { props: { color: "lime" } }, "游戏人生");
            } else {
              return h("Tag", { props: { color: "purple" } }, "未知");
            }
          },
        },
        {
          title: "优惠券类型",
          key: "couponType",
          width: 150,
          render: (h, params) => {
            let text = "";
            if (params.row.couponType === "DISCOUNT") {
              return h("Tag", { props: { color: "blue" } }, "打折");
            } else if (params.row.couponType === "PRICE") {
              return h("Tag", { props: { color: "geekblue" } }, "减免现金");
            } else {
              return h("Tag", { props: { color: "purple" } }, "未知");
            }
          },
        },
        {
          title: "品类描述",
          key: "scopeType",
          width: 120,
          render: (h, params) => {
            return promotionsScopeTypeRender(h, params);
          },
        },
        {
          title: "活动时间",
          render: (h, params) => {
            if (
              params?.row?.getType === "ACTIVITY" &&
              params?.row?.rangeDayType === "DYNAMICTIME"
            ) {
              return h("div", "长期有效");
            } else if (params?.row?.startTime && params?.row?.endTime) {
              return h("div", {
                domProps: {
                  innerHTML:
                    params.row.startTime + "<br/>" + params.row.endTime,
                },
              });
            }
          },
        },
        {
          title: "状态",
          width: 100,
          key: "promotionStatus",
          align: "center",
          fixed: "right",
          render: (h, params) => {
            return promotionsStatusRender(h, params);
          },
        },
      ],
      typeSelect:[
        {
          id:1,
          value:'coupon',
          label:'优惠劵'
        }
      ],
      modelShow:false,
      modelTitle:'',
      rules: {
        couponId:[{
          required: true,
        }],
        prizeName:[
          { required: true, message: '请输入奖品名称', trigger: 'blur' },
          { max: 50, message: '长度不能超过50个字符', trigger: 'blur' }
        ],
        prizeType: [
          { required: true, message: '请选择奖品类型', trigger: 'change' },
        ],
        prizeContent: [
          { required: true, message: '请输入奖品内容', trigger: 'blur' },
          { max: 100, message: '长度不能超过100个字符', trigger: 'blur' }
        ],
        prizeDes: [
          { required: true, message: '请输入奖品描述', trigger: 'blur' },
          { max: 100, message: '长度不能超过100个字符', trigger: 'blur' }
        ],
        prizeCover: [
          {required: true, message: '请选择奖品封面', trigger: 'blur'}
        ],
        prizeImg: [
          {required: true, message: '请选择奖品图片', trigger: 'blur'}
        ],
      },
      prizeFrom:{
        id:'',
        prizeName:'',
        prizeType:'',
        couponId:'',
        prizeContent:'',
        prizeCover:'',
        prizeDes:'',
        prizeImg:'',
      },
      loading:false,
      columns:[
        {
          title: '奖品封面',
          key: 'prizeCoverUrl',
          slot:'prizeCoverUrl',
          align: 'center',
          minWidth: 100,
        },
        {
          title: '奖品名称',
          key: 'prizeName',
          minWidth: 100,
        },
        {
          title: '奖品类型',
          key: 'prizeType',
          minWidth: 100,
          render: (h, params) => {
            if (params.row.prizeType === "coupon") {
              return h("Tag", {props: {color: "red"}}, "优惠券");
            }  else {
              return h("Tag", {props: {color: "purple"}}, "未知");
            }
          }
        },
        {
          title: '奖品内容',
          key: 'prizeContent',
          minWidth: 100,
        },
        {
          title: '奖品描述',
          key: 'prizeDes',
          minWidth: 100,
        },
        {
          title:'操作',
          slot: 'action',
          width: 200,
          align:'center',
          fixed:'right'
        }
      ],
      prizeList:[],
      total:0,
      searchForm:{
        prizeName:'',
        prizeType:'',
        pageSize:10,
        pageNumber:1,
      },
      couponSearchForm: {
        // 搜索框初始化对象
        pageNumber: 1, // 当前页数
        pageSize: 10, // 页面大小
        sort: "create_time", // 默认排序字段
        order: "desc", // 默认排序方式
        getType: "", // 默认排序方式
        promotionStatus:"START",
      },
      submitLoading:false,
      prizeCover:null,
      prizeImg:null,
    }
  },
  // 在组件创建前注册
  beforeCreate() {
  },
  mounted() {
    this.init();
  },
  methods: {
    replaceText(text) {
    if (text === "coupon") {
      return "优惠券";
    }
    return text; // 如果不是目标词,返回原文本
    },
    // 预览图片
    previewImage(url) {
      this.previewImageUrl = url
      this.previewVisible = true
    },
    // 处理行点击事件
    handleRowClick(currentRow ,oldCurrentRow){
      this.prizeFrom.couponId = currentRow.id;
      this.showCoupon = currentRow.couponName;
    },
    getCouponDataList() {
      // 获取数据
      this.couponLoading = true;
      getPlatformCouponList(this.couponSearchForm).then((res) => {
        this.couponLoading = false;
        if (res.success) {
          console.log(res);
          this.couponData = res.result.records;
          this.couponTotal = res.result.total;
        }
      });
      this.couponTotal = this.couponData.length;
      this.couponLoading = false;
    },
    resetSearch(){
      this.$refs.searchForm.resetFields()
      this.searchForm.pageNumber = 1
      this.getPage()
    },
    handleBeforeUpload(file,type) {
      if("content" === type){
        this.prizeImg = file;
        this.imgTempUrl = URL.createObjectURL(file);
        return false;
      }else if ("cover" === type){
        this.prizeCover = file;
        this.coverTempUrl = URL.createObjectURL(file);
        return false;
      }
    },
    async uploadFileByLmk(){
      if (this.prizeImg){
            this.submitLoading = true
            const formData = new FormData()
            formData.append('file', this.prizeImg)
            await uploadFileByLmk(formData).then(res => {
              this.submitLoading = false
              if (res.code === 200) {
                this.prizeFrom.prizeImg = res.data.fileKey;
                // this.$Message.success('上传成功')
              }else{
                // this.$Message.error(res.msg)
              }
            }).catch(() => {
              this.submitLoading = false
            })
          }
     if (this.prizeCover){
            this.submitLoading = true
            const formData = new FormData()
            formData.append('file', this.prizeCover)
       await uploadFileByLmk(formData).then(res => {
              this.submitLoading = false
              if (res.code === 200) {
                this.prizeFrom.prizeCover = res.data.fileKey
                // this.$Message.success('上传成功')
              }else{
                // this.$Message.error(res.msg)
              }
            }).catch(() => {
              this.submitLoading = false
            })
          }
    },
    // 删除文件
    handleRemove(type) {
      if ('content' === type){
        this.prizeImg = null;
        this.imgTempUrl = null;
      }else if ('cover' === type){
        this.prizeCover = null;
        this.coverTempUrl = null;
      }
    },
    async saveOrUpdate(){
      await this.uploadFileByLmk();
      if (this.prizeFrom.prizeType === 'coupon') {
        if (this.prizeFrom.couponId === null || this.prizeFrom.couponId === ''){
          this.$Message.error("请选择表格中优惠卷")
          return;
        }
      }
      this.$refs.form.validate(valid => {
        if (valid) {
          this.submitLoading = true
          const api = this.prizeFrom.id ? edit : add
          api(this.prizeFrom).then(res => {
            this.submitLoading = false
            if (res.code === 200) {
              this.$Message.success(res.msg)
              this.modelShow = false;
              this.getPage()
            }else{
              this.$Message.error(res.msg)
            }
          }).catch(() => {
            this.submitLoading = false
          })
        }
      })
    },
    modelClose(){
      this.modelShow = false
      this.submitLoading = false
      this.showCoupon = '';
      this.$refs.form.resetFields()
    },
    getPage(){
      this.loading = true;
      getPage(this.searchForm).then(res =>{
        this.loading = false;
        if (res.code === 200){
          this.prizeList = res.data;
          this.total = res.total
        }else {
          this.$Message.error(res.msg)
        }
      })
    },
    detail(row){
      this.infoModalShow = true;
      this.detailData = {...row};
    },
    infoModelClose(){
      this.infoModalShow = false;
    },
    openEdit(row){
      //需要判断是否可以编辑
      canUpDatePrizeDraw(row.id).then(res =>{
        if (res.code === 200){
          if (res.state){
            this.modelShow = true;
            this.modelTitle = "编辑奖品";
            this.prizeFrom ={
              id:row.id,
              prizeName: row.prizeName,
              prizeType: row.prizeType,
              couponId: row.couponId,
              prizeContent: row.prizeContent,
              prizeCover: row.prizeCover,
              prizeDes: row.prizeDes,
              prizeImg: row.prizeImg,
            }
            this.coverTempUrl = row.prizeCoverUrl;
            this.imgTempUrl = row.prizeImgUrl;
            this.showCoupon = res.data.couponName;
          }else {
            this.$Message.error("该奖品在抽奖活动中无法编辑!")
          }
        }
      })
    },
    handleSearch(){
      this.getPage()
    },
    openAdd(){
      this.$refs.form.resetFields()
      this.modelShow = true;
      this.modelTitle = "新增奖品";
      //清除表单状态.
      this.coverTempUrl = null;
      this.prizeCover=null;
      this.prizeImg = null;
      this.imgTempUrl = null;
      this.showCoupon = null;
    },
    delById(row){
      del(row.id).then(res=>{
        if (res.code === 200){
          this.$Message.success(res.msg)
        }else {
          this.$Message.error(res.msg)
        }
        this.getPage()
      })
    },
    // 获取富文本编辑器的内容
    // 初始化数据
    init() {
      this.getPage()
    },
    changePage(){
      this.searchForm.pageNumber = 1
      this.searchForm.pageSize = pageSize
      this.getPage()
    },
    changePageSize(){
      this.searchForm.pageNumber = page
      this.getPage()
    },
    couponChangePage(v) {
      // 改变页码
      this.couponSearchForm.pageNumber = v;
      this.getCouponDataList();
    },
    couponChangePageSize(v) {
      // 改变页数
      this.couponSearchForm.pageNumber = 1;
      this.couponSearchForm.pageSize = v;
      this.getCouponDataList();
    },
  },
}
</script>
<style lang="scss" scoped>
.detail-container {
  padding: 16px;
}
.detail-item {
  margin-bottom: 16px;
  line-height: 1.5;
}
.detail-item label {
  display: inline-block;
  width: 120px;
  font-weight: bold;
  color: #515a6e;
  vertical-align: top;
}
.detail-item span {
  display: inline-block;
  max-width: calc(100% - 120px);
}
.detail-image {
  display: inline-block;
  margin-top: 8px;
  border: 1px dashed #dcdee2;
  padding: 4px;
  border-radius: 4px;
  background: #f8f8f9;
}
.search-form {
  padding: 16px;
  background: #f8f8f9;
  border-radius: 4px;
  margin-bottom: 16px;
  .ivu-form-item {
    margin-bottom: 16px;
    margin-right: 16px;
  }
  .search-btn {
    margin-left: 8px;
  }
}
.operation {
  margin-bottom: 16px;
  .ivu-btn {
    margin-right: 8px;
  }
}
/* 选中行样式 */
.selected-row {
  background-color: #e6f7ff; /* 浅蓝色背景 */
  border-left: 3px solid #1890ff; /* 左侧蓝色边框 */
}
/* 选中行hover样式(可选) */
.selected-row:hover {
  background-color: #d1eaff !important;
}
.preview-image{
  width: 200px;
  height: auto;
  object-fit: contain
}
.preview-image {
  max-width: 100%;
  max-height: 70vh;
  object-fit: contain;
  border-radius: 4px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  display: block;
  margin: 0 auto;
}
.preview-image-limit{
  max-width: 100%;
  max-height: 200px;
  object-fit: contain;
  border-radius: 4px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  display: block;
}
.prize-table {
    .thumbnail {
      max-width: 100%;
      max-height: 100px;
      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);
      }
    }
  .action-btns {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    .ivu-btn {
      margin: 4px;
      font-size: 12px;
      padding: 2px 6px;
      min-width: 60px;
    }
  }
}
</style>