绿满眶商城微信小程序-uniapp
peng
2025-07-17 c034d4cdaf348594c443b4acd5f4c3b166e4d420
pages/mine/activity/addActivity.vue
New file
@@ -0,0 +1,739 @@
<template>
   <view class="add-user-container">
      <!-- 表单区域 -->
      <view class="form-card">
         <u-form :model="form" ref="uForm" label-width="150rpx" :rules="rules" style="width: 100%;">
            <!-- 活动名称 -->
            <u-form-item label="活动名称" prop="activityName" borderBottom required>
               <u-input v-model="form.activityName" placeholder="请输入活动名称" border="none" />
            </u-form-item>
            <!-- 活动类型 -->
            <u-form-item label="活动类型" prop="activityType" borderBottom required>
               <u-radio-group v-model="form.activityType" placement="column">
                  <u-radio :customStyle="{marginBottom: '8px'}" v-for="(item, index) in activityTypes"
                     :key="index" :label="item.value" :name="item.value" @change="activityTypeRadioChange">
                     <span>{{item.label}}</span>
                  </u-radio>
               </u-radio-group>
            </u-form-item>
            <!-- 报名时间范围 -->
            <u-form-item label="报名开始时间" prop="reportStartTime" borderBottom required label-width="200rpx">
               <uni-datetime-picker type="datetime" v-model="form.reportStartTime"
                  @change="handleReportStartTimeChange" />
            </u-form-item>
            <u-form-item label="报名结束时间" prop="reportEndTime" borderBottom required label-width="200rpx">
               <uni-datetime-picker type="datetime" v-model="form.reportEndTime" :start="form.reportStartTime"
                  @change="handleReportEndTimeChange" />
            </u-form-item>
            <!-- 活动时间范围 -->
            <u-form-item label="活动开始时间" prop="startTime" borderBottom required label-width="200rpx">
               <uni-datetime-picker type="datetime" v-model="form.startTime" :start="form.reportEndTime"
                  @change="handleStartTimeChange" />
            </u-form-item>
            <u-form-item label="活动结束时间" prop="endTime" borderBottom required label-width="200rpx">
               <uni-datetime-picker type="datetime" v-model="form.endTime" :start="form.startTime"
                  @change="handleEndTimeChange" />
            </u-form-item>
            <!-- 封面类型 -->
            <u-form-item label="封面类型" prop="coverType" borderBottom required>
               <u-radio-group v-model="coverType" placement="column">
                  <u-radio :customStyle="{marginBottom: '8px'}" v-for="(item, index) in coverTypes" :key="index"
                     :label="item.value" :name="item.value" @change="coverTypeRadioChange">
                     <span>{{item.label}}</span>
                  </u-radio>
               </u-radio-group>
            </u-form-item>
            <!-- 活动封面 -->
            <u-form-item label="活动封面" prop="cover" borderBottom required>
               <u-input v-if="coverType === 'text'" v-model="form.cover" placeholder="请输入封面文字" border="none" />
               <u-upload v-else :fileList="coverList" @delete="deleteCover" name="cover" :maxCount="1"
                  uploadText="上传封面" :action="uploadUrl" @success="handleSuccess"></u-upload>
            </u-form-item>
            <!-- 人数限制 -->
            <u-form-item label="人数限制" prop="limitUserNum" borderBottom required>
               <u-number-box v-model="form.limitUserNum" :min="0" :max="1000" integer></u-number-box>
               <text style="margin-left: 20rpx;color: #999">0表示不限制</text>
            </u-form-item>
            <!-- 活动地点 -->
            <u-form-item label="活动地点" prop="activityLocation" borderBottom v-if="form.activityType === 'offline'"
               required>
               <u-input v-model="form.activityLocation" placeholder="请输入活动地点" border="none" />
            </u-form-item>
            <!-- 活动内容 -->
            <u-form-item label="活动内容" prop="activityContent" borderBottom required>
            </u-form-item>
            <sp-editor style="max-width: 70%;" :toolbar-config="{
                          excludeKeys: ['direction', 'date', 'lineHeight', 'letterSpacing', 'listCheck'],
                          iconSize: '15px'
                        }" @init="initEditor" @input="inputOver" @overMax="overMax" @upinImage="upinImage"
               @upinVideo="upinVideo"></sp-editor>
         </u-form>
         <!-- 提交按钮 -->
         <view class="submit-btn">
            <u-button type="primary" shape="circle" @click="submitForm" :loading="loading">提交</u-button>
         </view>
      </view>
   </view>
</template>
<script>
   import {
      convertImgStylesToAttributes,
      handleHtmlWithVideo
   } from '@/uni_modules/sp-editor/utils'
   import '@/components/uview-components/uview-ui';
   import {
      addActivityByBuyer
   } from '@/api/activity.js'
   export default {
      data() {
         return {
            uploadUrl: 'http://127.0.0.1:8890/common/lmk/file/upload',
            coverType: 'file',
            isImage: false, // 是否是图片
            editorIns: null,
            loading: false,
            coverList: [],
            activityTypes: [{
                  label: '线上',
                  value: 'online'
               },
               {
                  label: '线下',
                  value: 'offline'
               }
            ],
            coverTypes: [{
                  label: '文件',
                  value: 'file'
               },
               {
                  label: '文字',
                  value: 'text'
               }
            ],
            form: {
               activityName: '',
               activityType: 'online', // 默认线上
               reportStartTime: '',
               reportEndTime: '', // 默认明天
               startTime: '', // 默认后天
               endTime: '', // 默认大后天
               cover: '',
               coverType: 'file',
               limitUserNum: 0,
               activityLocation: '',
               activityContent: ''
            },
            rules: {
               activityName: [{
                     required: true,
                     message: '请输入活动名称',
                     trigger: 'blur'
                  },
                  {
                     min: 2,
                     max: 50,
                     message: '长度在2到50个字符',
                     trigger: 'blur'
                  }
               ],
               activityType: [{
                  required: true,
                  message: '请选择活动类型',
                  trigger: 'change'
               }],
               coverType: [{
                  required: true,
                  message: '请选择封面类型',
                  trigger: 'change'
               }],
               cover: [{
                  required: true,
                  message: '请上传活动封面',
                  trigger: 'change'
               }],
               reportStartTime: [{
                  required: true,
                  message: '请选择报名开始时间',
                  trigger: 'blur'
               }],
               reportEndTime: [{
                     required: true,
                     message: '请选择报名结束时间',
                     trigger: 'change'
                  },
                  {
                     validator: this.validateReportTime,
                     trigger: 'change'
                  }
               ],
               startTime: [{
                     required: true,
                     message: '请选择活动开始时间',
                     trigger: 'change'
                  },
                  {
                     validator: this.validateStartTime,
                     trigger: 'change'
                  }
               ],
               endTime: [{
                     required: true,
                     message: '请选择活动结束时间',
                     trigger: 'change'
                  },
                  {
                     validator: this.validateEndTime,
                     trigger: 'change'
                  }
               ],
               limitUserNum: [{
                     required: true,
                     message: '请输入人数',
                     trigger: 'change',
                     validator: this.numValidate,
                  },
               ],
               activityContent: [{
                     required: true,
                     message: '请输入活动内容',
                     trigger: 'blur'
                  },
                  {
                     min: 10,
                     message: '至少输入10个字符',
                     trigger: 'blur'
                  }
               ]
            },
         }
      },
      onReady() {
         // 微信小程序需要用此写法
      },
      onLoad() {
         setTimeout(() => {
            this.$refs.uForm.setRules(this.rules)
         }, 500)
      },
      methods: {
         /**
          * 直接运行示例工程插入图片无法正常显示的看这里
          * 因为插件默认采用云端存储图片的方式
          * 以$emit('upinImage', tempFiles, this.editorCtx)的方式回调
          * @param {Object} tempFiles
          * @param {Object} editorCtx
          */
         async upinImage(tempFiles, editorCtx) {
            /**
             * 本地临时插入图片预览
             * 注意:这里仅是示例本地图片预览,因为需要将图片先上传到云端,再将图片插入到编辑器中
             * 正式开发时,还请将此处注释,并解开下面 使用 uniCloud.uploadFile 上传图片的示例方法 的注释
             * @tutorial https://uniapp.dcloud.net.cn/api/media/editor-context.html#editorcontext-insertimage
             */
            let data = await this.upload(tempFiles);
            console.log(data)
            // #ifdef MP-WEIXIN
            // 注意微信小程序的图片路径是在tempFilePath字段中
            editorCtx.insertImage({
               src: data.data.url, //富文本内容
               width: '80%', // 默认不建议铺满宽度100%,预留一点空隙以便用户编辑
               alt: data.data.url +'|'+data.data.fileKey,
               success: function() {}
            })
            // #endif
            // #ifndef MP-WEIXIN
            editorCtx.insertImage({
               src: data.data.url, //富文本内容
               width: '80%', // 默认不建议铺满宽度100%,预留一点空隙以便用户编辑
               success: function() {}
            })
            // #endif
         },
         async upinVideo(tempFiles, editorCtx) {
            let data = await this.uploadVideo(tempFiles);
            await editorCtx.insertImage({
               src: 'https://img.zcool.cn/community/01055859b8e37fa8012075341db67f.gif', //富文本内容
               width: '80%', // 默认不建议铺满宽度100%,预留一点空隙以便用户编辑
               alt: data.data.url +'|'+data.data.fileKey,
            })
         },
         async upload(tempFiles) {
            uni.showLoading({
               title: '加载上传中...',
               mask: true
            });
            try {
               const res = await new Promise((resolve, reject) => {
                  uni.uploadFile({
                     url: 'http://127.0.0.1:8890/common/lmk/file/upload',
                     filePath: tempFiles[0].tempFilePath,
                     name: 'file',
                     success: (uploadRes) => {
                        if (uploadRes.statusCode === 200) {
                           try {
                              const parsedData = JSON.parse(uploadRes.data);
                              resolve(parsedData);
                           } catch (e) {
                              reject(new Error('解析响应数据失败'));
                           }
                        } else {
                           reject(new Error(`上传失败: ${uploadRes.statusCode}`));
                        }
                     },
                     fail: (err) => {
                        const errMsg = typeof err === 'object' ? JSON.stringify(err) :
                           String(err);
                        reject(new Error(`上传失败: ${errMsg}`));
                     }
                  });
               });
               uni.hideLoading();
               return res;
            } catch (error) {
               uni.hideLoading();
               uni.showToast({
                  title: error.message,
                  icon: 'none'
               });
               throw error; // 可以选择继续抛出错误或返回null
            }
         },
         async uploadVideo(tempFiles) {
            uni.showLoading({
               title: '加载上传中...',
               mask: true
            });
            try {
               const res = await new Promise((resolve, reject) => {
                  uni.uploadFile({
                     url: 'http://127.0.0.1:8890/common/lmk/file/upload',
                     filePath: tempFiles,
                     name: 'file',
                     success: (uploadRes) => {
                        if (uploadRes.statusCode === 200) {
                           try {
                              const parsedData = JSON.parse(uploadRes.data);
                              resolve(parsedData);
                           } catch (e) {
                              reject(new Error('解析响应数据失败'));
                           }
                        } else {
                           reject(new Error(`上传失败: ${uploadRes.statusCode}`));
                        }
                     },
                     fail: (err) => {
                        const errMsg = typeof err === 'object' ? JSON.stringify(err) :
                           String(err);
                        reject(new Error(`上传失败: ${errMsg}`));
                     }
                  });
               });
               uni.hideLoading();
               return res;
            } catch (error) {
               uni.hideLoading();
               uni.showToast({
                  title: error.message,
                  icon: 'none'
               });
               throw error; // 可以选择继续抛出错误或返回null
            }
         },
         /**
          * 获取输入内容
          * @param {Object} e {html,text} 内容的html文本,和text文本
          */
         inputOver(e) {
            // 可以在此处获取到编辑器已编辑的内容
            console.log('==== inputOver :', e)
            this.form.activityContent = e.html;
         },
         /**
          * 超出最大内容限制
          * @param {Object} e {html,text} 内容的html文本,和text文本
          */
         overMax(e) {
            // 若设置了最大字数限制,可在此处触发超出限制的回调
            console.log('==== overMax :', e)
         },
         /**
          * 编辑器就绪
          * @param {Object} editor 编辑器实例,你可以自定义调用editor实例的方法
          * @tutorial editor组件 https://uniapp.dcloud.net.cn/component/editor.html#editor-%E7%BB%84%E4%BB%B6
          * @tutorial 相关api https://uniapp.dcloud.net.cn/api/media/editor-context.html
          */
         initEditor(editor) {
            this.editorIns = editor // 保存编辑器实例
            // 保存编辑器实例后,可以在此处获取后端数据,并赋值给编辑器初始化内容
            this.preRender()
         },
         preRender() {
            setTimeout(() => {
               // 异步获取后端数据后,初始化编辑器内容
               this.editorIns.setContents({
                  html: ``
               })
            }, 1000)
         },
         // 时间选择变化处理
         handleReportStartTimeChange(val) {
            this.form = {
               ...this.form,
               reportStartTime: val
            }; // 解构赋值触发响应式
            // 如果报名结束时间早于开始时间,则重置
            if (new Date(this.form.reportEndTime) < new Date(val)) {
               this.form.reportEndTime = '';
            }
         },
         handleReportEndTimeChange(val) {
            this.form.reportEndTime = val;
            // 如果活动开始时间早于报名结束时间,则重置
            if (new Date(this.form.startTime) < new Date(val)) {
               this.form.startTime = '';
            }
         },
         handleStartTimeChange(val) {
            this.form.startTime = val;
            // 如果活动结束时间早于开始时间,则重置
            if (new Date(this.form.endTime) < new Date(val)) {
               this.form.endTime = '';
            }
         },
         handleEndTimeChange(val) {
            this.form.endTime = val;
         },
         activityTypeRadioChange(n) {
            console.log('radioChange', n);
            this.form.activityType = n;
         },
         coverTypeRadioChange(n) {
            console.log('radioChange', n);
            this.coverType = n;
         },
         // 图片上传处理
         // 新增图片
         afterRead(event) {
            const {
               file
            } = event;
            console.log('文件类型:', file.type);
         },
         // 删除封面
         deleteCover() {
            this.coverList = []
            this.form.cover = ''
         },
         handleSuccess(data, index, lists, fileType) {
            //表单赋值
            this.form.coverType = fileType;
            this.form.cover = data.data.fileKey;
         },
         // 验证报名时间
         validateReportTime(rule, value, callback) {
            if (!value) {
               callback(new Error('请选择报名结束时间'))
            } else if (new Date(value) <= new Date(this.form.reportStartTime)) {
               callback(new Error('报名结束时间必须晚于开始时间'))
            } else {
               callback()
            }
         },
         // 验证活动开始时间
         validateStartTime(rule, value, callback) {
            if (!value) {
               callback(new Error('请选择活动开始时间'))
            } else if (new Date(value) <= new Date(this.form.reportEndTime)) {
               callback(new Error('活动开始时间必须晚于报名结束时间'))
            } else {
               callback()
            }
         },
         numValidate(rule, value, callback) {
            console.log(value)
            if (value <= 0 || value > 1000) {
               callback(new Error('人数范围1-1000'));
            } else {
               callback();
            }
         },
         // 验证活动结束时间
         validateEndTime(rule, value, callback) {
            if (!value) {
               callback(new Error('请选择活动结束时间'))
            } else if (new Date(value) <= new Date(this.form.startTime)) {
               callback(new Error('活动结束时间必须晚于开始时间'))
            } else {
               callback()
            }
         },
// "<p><img src="https://img.zcool.cn/community/01055859b8e37fa8012075341db67f.gif" alt="https://lmk-1356772813.cos.ap-chengdu.myqcloud.com/video/2025071011124230197.mp4|video/2025071011124230197.mp4" width="80%"><img src="https://lmk-1356772813.cos.ap-chengdu.myqcloud.com/image/2025071011130152914.jpg" alt="https://lmk-1356772813.cos.ap-chengdu.myqcloud.com/image/2025071011130152914.jpg|image/2025071011130152914.jpg" width="80%"></p><p><br></p><p><br></p>"
         buildTag(html) {
           return html.replace(/<img([^>]*)>/gi, (imgTag) => {
             const srcMatch = imgTag.match(/src="([^"]*)"/i);
             const altMatch = imgTag.match(/alt="([^"]*)"/i);
             const widthMatch = imgTag.match(/width="([^"]*)"/i);
             if (!srcMatch && !altMatch) return imgTag;
             // 分割 alt 内容(格式:左边|右边)
             let newSrc = srcMatch ? srcMatch[1] : '';
             let newAlt = altMatch ? altMatch[1] : '';
             if (altMatch && altMatch[1].includes('|')) {
               const [leftPart, rightPart] = altMatch[1].split('|');
               newSrc = leftPart.trim();  // | 左侧作为 src
               newAlt = rightPart.trim(); // | 右侧作为 alt
             }
             const width = widthMatch ? widthMatch[1] : "80%";
             // 检测是否为视频(基于新的 src)
             const isVideo = /\.(mp4|webm|ogg|mov)$/i.test(newSrc);
             if (isVideo) {
               const extension = newSrc.split('.').pop().toLowerCase();
               return `<video width="${width}" alt="${newAlt}" src="${newSrc}" height="auto" controls="controls"></video>`;
             }
             // 非视频:更新 src 和 alt
             return `<img src="${newSrc}" alt="${newAlt}" width="${width}">`;
           });
         },
         escapeStringHTML(str) {
             if (!str) return str;
             str = str.replace(/&lt;/g, '<');
             str = str.replace(/&gt;/g, '>');
             return str;
           },
         // 提交表单
         async submitForm() {
            // let html = '"<p><img src="https://img.zcool.cn/community/01055859b8e37fa8012075341db67f.gif" alt="video/2025070811142279476.mp4" width="80%"><img src="https://lmk-1356772813.cos.ap-chengdu.myqcloud.com/image/2025070811142783043.jpg" alt="image/2025070811142783043.jpg" width="80%">测试活动内容</p><p><br></p><p><br></p>"'
            // const temphtml2  = this.buildTag(html)
            const temphtml2 = this.buildTag(this.form.activityContent)
            this.form.activityContent = temphtml2;
            this.$refs.uForm.validate(valid => {
               if (valid) {
                  console.log("通过")
                  if (this.coverType === 'text') {
                     this.form.coverType = 'text';
                  }
                  this.loading = true
                  // 调用API
                  addActivityByBuyer(this.form).then(res => {
                     console.log(res)
                     if (res.statusCode === 200) {
                        this.loading = false
                        uni.showToast({
                           title: res.data.msg, // 提示文字
                           icon: 'none', // 图标类型(success/loading/none)
                           mask: true // 是否显示透明蒙层(防止触摸穿透)
                        });
                        setTimeout(() => {
                           uni.$emit('refreshParentPage');
                           uni.navigateBack();
                        }, 1500);
                     }
                  })
               }
            })
         }
      }
   }
</script>
<style lang="scss" scoped>
   .add-user-container {
      width: 100%;
      padding: 20rpx;
      background-color: #f5f7fa;
      min-height: 100vh;
   }
   .form-card {
      background-color: #fff;
      border-radius: 24rpx;
      padding: 40rpx;
      box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
      margin-bottom: 40rpx;
      /* 表单标题样式 */
      .form-title {
         font-size: 36rpx;
         font-weight: 600;
         color: #333;
         margin-bottom: 40rpx;
         text-align: center;
         position: relative;
         &:after {
            content: '';
            display: block;
            width: 80rpx;
            height: 6rpx;
            background: linear-gradient(to right, #2979ff, #00bcd4);
            margin: 20rpx auto 0;
            border-radius: 3rpx;
         }
      }
   }
   /* 表单项样式 */
   .u-form-item {
      padding: 28rpx 0;
      margin-bottom: 10rpx;
      /* 标签样式 */
      ::v-deep .u-form-item__body__left__content__label {
         font-size: 30rpx;
         color: #606266;
         font-weight: 500;
      }
      /* 输入框样式 */
      .u-input {
         background-color: #f8f9fa;
         border-radius: 12rpx;
         padding: 20rpx 24rpx !important;
         &__content__field-wrapper__field {
            color: #333;
            font-size: 28rpx;
         }
      }
      /* 单选组样式 */
      .u-radio-group {
         margin-top: 10rpx;
      }
      .u-radio {
         margin-bottom: 16rpx;
         &__label {
            font-size: 28rpx;
            color: #606266;
         }
      }
      /* 数字输入框样式 */
      .u-number-box {
         margin-right: 20rpx;
      }
   }
   /* 时间选择器样式 */
   .uni-datetime-picker {
      width: 100%;
      ::v-deep .uniui-calendar {
         color: #2979ff;
      }
      ::v-deep .uni-datetime-picker-btn-cancel {
         color: #999;
      }
      ::v-deep .uni-datetime-picker-btn-confirm {
         color: #2979ff;
      }
   }
   /* 上传组件样式 */
   .u-upload {
      ::v-deep .u-upload__wrap {
         padding: 0;
      }
      ::v-deep .u-upload__button {
         background-color: #f8f9fa;
         border-radius: 12rpx;
         color: #2979ff;
         font-size: 28rpx;
         height: 160rpx;
         display: flex;
         align-items: center;
         justify-content: center;
         border: 1rpx dashed #dcdfe6;
         &:active {
            background-color: #f1f5f9;
         }
      }
      ::v-deep .u-upload__preview {
         border-radius: 12rpx;
         overflow: hidden;
      }
   }
   /* 提交按钮样式 */
   .submit-btn {
      margin-top: 80rpx;
      padding: 0 80rpx;
      .u-button {
         height: 90rpx;
         font-size: 32rpx;
         font-weight: 500;
         letter-spacing: 2rpx;
         background: linear-gradient(to right, #2979ff, #00bcd4);
         box-shadow: 0 8rpx 24rpx rgba(41, 121, 255, 0.3);
         transition: all 0.3s;
         &:active {
            transform: translateY(2rpx);
            box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.3);
         }
      }
   }
   /* 底部说明文字 */
   .form-tips {
      font-size: 24rpx;
      color: #999;
      text-align: center;
      margin-top: 40rpx;
      line-height: 1.6;
   }
</style>