Files
estate/src/views/system/Activity/addActivity.vue
2026-06-22 08:14:35 +08:00

601 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="ResidentMainBox p-2 w-full">
<PageHeader title="活动"></PageHeader>
<div class="ResidentMainBody">
<el-card>
<template #header>
<div class="title">基本信息</div>
</template>
<div class="addBuildingFormBody">
<div class="formbox">
<el-form ref="formRef" :model="form" :rules="rules" label-width="150px">
<el-row>
<el-col :span="13">
<el-form-item label="活动标题" prop="title">
<el-input v-model.trim="form.title" placeholder="请输入活动标题" />
</el-form-item>
</el-col>
<el-col :span="11">
<el-form-item label="活动作者" prop="author">
<el-input v-model.trim="form.author" placeholder="请输入活动作者" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="13">
<el-form-item label="活动时间" prop="startTime">
<el-date-picker
v-model="activityTime"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
@change="
(data: string[] | null) => {
if (data) {
form.startTime = data[0];
form.endTime = data[1];
} else {
form.startTime = '';
form.endTime = '';
}
}
"
type="datetimerange"
range-separator=""
start-placeholder="开始活动"
end-placeholder="结束活动"
/>
</el-form-item>
</el-col>
<el-col :span="11">
<el-form-item label="活动地点" prop="address">
<el-input v-model.trim="form.address" placeholder="请输入活动地点" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="11">
<el-form-item label="报名截止日期" prop="deadline">
<el-date-picker
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
v-model="deadline"
@update:model-value="
(data) => {
if (data) {
form.applyDeadline = data;
} else {
form.applyDeadline = '';
}
}
"
type="date"
placeholder="请选择报名截止日期"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="主办方" prop="organizer">
<el-input v-model.trim="form.organizer" placeholder="请输入主办方" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="联系人" prop="contactName">
<el-input v-model.trim="form.contactName" placeholder="请输入联系人" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系方式" prop="contactName">
<el-input v-model.trim="form.contactPhone" placeholder="请输入联系方式" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="活动名额" prop="quota">
<div class="flex items-center">
<el-input-number v-model.trim.number="form.quota" :min="0" placeholder="0代表无限" />
<span class="ml-2 text-gray-500 text-sm" style="font-size: 12px">(填 <b>0</b> 代表<u>不限制名额</u></span>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="活动封面" prop="title">
<el-upload
ref="upload"
class="avatar-uploader"
:action="uploadUrl"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:on-exceed="handleExceed"
:on-error="handleError"
:limit="1"
>
<img alt="活动封面" v-if="form.coverImage" :src="form.coverImage" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="招募要求">
<el-input :row="8" type="textarea" v-model.trim="form.requirements" placeholder="请输入招募要求" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item label="活动详情" prop="content">
<editor v-model="form.content" :height="500" :min-height="300" :read-only="false" :file-size="5" :type="'url'" />
</el-form-item>
</el-col>
</el-row>
<!-- <el-row>
<el-col>
<el-form-item label="活动签到">
<el-switch
@change="
(val) => {
if (val) {
initSignMap();
}
}
"
v-model="isSignin"
active-text="签到"
inactive-text="不签到"
:active-value="true"
:inactive-value="false"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="13">
<el-form-item label="签到时间" prop="signinStartTime">
<el-date-picker
v-model="SigninTime"
type="datetimerange"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
range-separator=""
@update:model-value="
(data) => {
if (data && data.length > 0) {
form.signinStartTime = data[0];
form.signinEndTime = data[1];
} else {
form.signinStartTime = '';
form.signinEndTime = '';
}
}
"
start-placeholder="开始签到"
end-placeholder="结束签到"
/>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="isSignin">
<el-col>
<el-form-item label="签到设置">
<div>
<div>
<el-button type="primary" v-if="isDraw === 0" @click="startDrawCircle">开始设置签到范围</el-button>
<el-button type="success" v-if="isDraw === 1" @click="editCircle">编辑签到范围</el-button>
<el-button type="warning" v-if="isDraw === 2" @click="stopDrawCircle">结束设置签到范围</el-button>
<el-button type="danger" @click="clearCircle">清空签到范围</el-button>
</div>
<div v-loading="mapReady" class="SignMapbox">
<div id="SignMap" style="height: 100%; width: 100%"></div>
</div>
</div>
</el-form-item>
</el-col>
</el-row> -->
<el-form-item>
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="handleBack">返回</el-button>
</el-form-item>
</el-form>
</div>
</div>
</el-card>
</div>
</div>
</template>
<script setup lang="ts">
import editor from '@/components/Editor/index.vue';
import { ref } from 'vue';
import PageHeader from '@/components/Pageheader/index.vue';
import { addActivity, getActivity, updateActivity } from '@/api/system/Activity';
import { genFileId, UploadInstance, UploadProps, UploadRawFile } from 'element-plus';
import AMapLoader from '@amap/amap-jsapi-loader';
import { Plus } from '@element-plus/icons-vue';
const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/activity/uploadImage';
const aloaderkey = import.meta.env.VITE_GCJ02_KEY_GD;
const VITE_GCJ02_JS_CODE_GD = import.meta.env.VITE_GCJ02_JS_CODE_GD;
const route = useRoute();
const router = useRouter();
const formRef = ref();
const form = ref({
'id': null,
// 标题
'title': '',
// 作者
'author': '',
// 内容
'content': '',
// 发布状态 0未发布 1己发布
'status': '0',
// 报名截止日期
'applyDeadline': '',
// 活动时间
'startTime': '',
'endTime': '',
// 活动地点
'address': '',
// 主办方
'organizer': '',
// 联系人
'contactName': '',
// 联系方式
'contactPhone': '',
// 开始签到时间
'signinStartTime': '',
// 结束签到时间
'signinEndTime': '',
// 活动名额
'quota': 0,
// 积分奖励
'pointsReward': 0,
// 活动封面
'coverImage': '',
// 招募要求
'requirements': ''
// 'SignInfo': {
// lng: null, // 经度
// lat: null, // 纬度
// radius: null // 半径 m
// }
});
const rules = {
title: [{ required: true, message: '请输入活动标题', trigger: 'blur' }],
author: [{ required: true, message: '请输入作者', trigger: 'blur' }],
applyDeadline: [{ required: true, message: '请输入报名截止日期', trigger: 'blur' }],
startTime: [{ required: true, message: '请选择开始日期', trigger: 'blur' }],
endTime: [{ required: true, message: '请选择结束日期', trigger: 'blur' }],
address: [{ required: true, message: '请输入活动地点', trigger: 'blur' }],
organizer: [{ required: true, message: '请输入主办方', trigger: 'blur' }],
contactName: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
contactPhone: [{ required: true, message: '请输入联系方式', trigger: 'blur' }],
coverImage: [{ required: true, message: '请上传活动封面', trigger: 'blur' }],
content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
};
const userid = ref();
// 报名截止日期
const deadline = ref([]);
// 有效签到时间
const SigninTime = ref([]);
// 活动时间
const activityTime = ref([]);
// 上传文件
const upload = ref<UploadInstance>();
// 上传成功
const handleAvatarSuccess: UploadProps['onSuccess'] = (response) => {
if (response.data && response.data.url) {
form.value.coverImage = response.data.url;
}
};
// 超出文件限制
const handleExceed: UploadProps['onExceed'] = (files) => {
upload.value!.clearFiles();
const file = files[0] as UploadRawFile;
file.uid = genFileId();
upload.value!.handleStart(file);
upload.value?.submit();
};
// 上传失败
const handleError: UploadProps['onError'] = (error: Error) => {
console.log(error);
};
// ! 投诉 ====================================================================================================
// ! 签到 =======================================
// const isSignin = ref(false);
let AMapLi = null;
let map: any = null;
let circle: any = null;
let circleEditor: any = null;
let geocoder: any = null;
const mapReady = ref(false);
let mouseTool = null;
// 0 开始 1编辑 2结束
const isDraw = ref(0);
onMounted(() => {
if (route.query.id) {
userid.value = route.query.id;
init();
}
});
async function initSignMap() {
try {
mapReady.value = true;
window._AMapSecurityConfig = { securityJsCode: VITE_GCJ02_JS_CODE_GD };
console.log('开始执行 AMapLoader.load');
const AMap = await AMapLoader.load({
key: aloaderkey,
version: '2.0',
plugins: ['AMap.MouseTool', 'AMap.CircleEditor', 'AMap.Geolocation', 'AMap.Geocoder']
});
mapReady.value = false;
console.log('AMapLoader.load 执行完毕');
AMapLi = AMap;
map = new AMap.Map('SignMap', {
viewMode: '3D',
zoom: 11,
center: [116.397428, 39.90923]
});
// 解析本地存储的城市信息
const villageInfoStr = localStorage.getItem('villageInfo') || '{}';
let city = '';
try {
const villageInfo = JSON.parse(villageInfoStr);
city = (villageInfo?.city || '').trim();
} catch (err) {
console.warn('解析 villageInfo 失败', err);
}
// 初始化地理编码工具
if (city.trim() !== '') {
geocoder = new AMap.Geocoder({
city: city.trim(),
radius: 1000
});
getGeoCode(city.trim(), map);
}
// 鼠标绘制工具
mouseTool = new AMap.MouseTool(map);
mouseTool.on('draw', (e: any) => {
// 移除旧围栏
if (circle) {
map.remove(circle);
}
circle = e.obj;
});
} catch (error) {
console.error('高德地图初始化失败:', error);
}
}
// 获取 城市经纬度并定位中心
const getGeoCode = (address, map) => {
geocoder.getLocation(address, (status: string, result: any) => {
if (status === 'complete' && result.geocodes.length > 0) {
const geo = result.geocodes[0];
const lng = geo.location.lng;
const lat = geo.location.lat;
map.setCenter([lng, lat]);
map.setZoom(15);
} else {
console.log('未找到该地址');
}
});
};
// 开始绘制圆形
const startDrawCircle = () => {
resetCircle();
if (!mouseTool) return;
isDraw.value = 1; // 编辑
mouseTool.circle({
strokeColor: '#1890ff',
strokeWeight: 3,
fillColor: 'rgba(24, 144, 255, 0.2)',
fillOpacity: 0.4
});
};
// 停止绘制圆形
const stopDrawCircle = () => {
if (!mouseTool) return;
if (mouseTool) {
mouseTool.close(false);
}
if (circleEditor) {
circleEditor.close();
circleEditor = null;
}
isDraw.value = 0; // 开始
mouseTool.close(false);
if (circle) {
const center = circle.getCenter();
const radius = circle.getRadius();
console.log('中心点:', [center.lng, center.lat]);
console.log('半径:', radius, typeof radius);
// form.value.SignInfo.lat = center.lat;
// form.value.SignInfo.lng = center.lng;
// form.value.SignInfo.radius = center.radius;
}
};
// 编辑 圆形
const editCircle = () => {
if (!circle) return;
if (mouseTool) {
mouseTool.close(false);
}
isDraw.value = 2; // 结束
circleEditor = new AMapLi.CircleEditor(map, circle);
circleEditor.open();
};
// 删除圆形
const clearCircle = () => {
// resetCircle();
isDraw.value = 0; // 开始
ElMessage.success('全部清除');
};
// 重置
const resetCircle = () => {
// 不能绘画
if (mouseTool) {
mouseTool.close(false);
}
// 清除圆形
if (circle) {
map.remove(circle);
circle = null;
}
// 不能编辑
if (circleEditor) {
circleEditor.close();
circleEditor = null;
}
};
function init() {
getActivity(userid.value).then((res) => {
form.value = {
...form.value,
...res.data
};
activityTime.value = [res.data.startTime, res.data.endTime];
SigninTime.value = [res.data.signinStartTime, res.data.signinEndTime];
deadline.value = [res.data.applyDeadline];
// if (res.data?.SignInfo !== null) {
// isSignin.value = true;
// }
});
}
// ! 提交========================================
// 提交
const submitForm = () => {
console.log(form.value);
// if (!isSignin.value) {
// form.value.SignInfo = null;
// }
formRef.value?.validate(async (valid: boolean) => {
if (valid) {
if (userid.value) {
updateActivity(form.value).then(() => {
ElMessage.success('编辑成功');
handleBack();
});
} else {
addActivity(form.value).then(() => {
ElMessage.success('添加成功');
handleBack();
});
}
}
});
};
// 返回
function handleBack() {
router.push({
path: '/repair/Activity'
});
}
/**
* 销毁地图及所有工具实例(防内存泄漏、重复初始化)
*/
// function destroyMapInstance() {
// if (map) {
// map.destroy();
// map = null;
// }
// mouseTool = null;
// geocoder = null;
// circle = null;
// AMapLi = null;
// mapReady.value = false;
// }
onUnmounted(() => {
// destroyMapInstance();
});
</script>
<style scoped lang="scss">
.SignMapbox {
height: 500px;
width: 500px;
margin-top: 10px;
border: 1px solid #ccc;
}
.ResidentMainBox {
display: flex;
flex-direction: column;
.avatar-uploader {
border: 1px solid #ccc;
border-radius: 6px;
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 158px;
height: 158px;
text-align: center;
}
.avatar-uploader .avatar {
width: 158px;
height: 158px;
display: block;
object-fit: cover;
}
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.ResidentMainBody {
margin-top: 20px;
flex: 1;
.formbox {
width: 1000px;
margin: 0 auto;
}
&:deep(div.el-step__head.is-process) {
& > div.el-step__line {
width: 0;
border: 1px dashed gray;
background: transparent;
}
}
&:deep(.el-step.is-vertical .el-step__line) {
top: 20px;
}
&:deep(div.el-step__head.is-finish) {
& > div.el-step__line {
width: 0;
border: 1px solid var(--el-color-primary);
background: transparent;
}
}
}
}
</style>