<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(/</g, '<');
|
str = str.replace(/>/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>
|