Files
estate/src/views/community/AddCommunity.vue
2026-07-30 17:59:32 +08:00

547 lines
17 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="box">
<div class="header">
<span>智慧物业后台管理系统</span>
<navbar :Showhamburger="false" ref="navbarRef" />
</div>
<div class="container AddBuildingBox">
<el-form inline class="formBody" ref="formRef" :model="form" :rules="rules" label-width="100px">
<div class="AddBuildingBody">
<div class="title">基本信息</div>
<div class="addBuildingFormBody">
<el-form-item label="小区名称" prop="villageName">
<el-input maxlength="25" v-model.trim="form.villageName" placeholder="请输入小区名称" />
</el-form-item>
<el-form-item label="小区编号" prop="villageCode">
<el-input maxlength="20" v-model.trim="form.villageCode" placeholder="请输入小区编号" />
</el-form-item>
<el-form-item label="所在城市" prop="city">
<el-cascader :options="pcaTextArrList" v-model="city" @change="cityChange"> </el-cascader>
</el-form-item>
<el-form-item label="完整详细地址" prop="buildingArea">
<el-input type="textarea" :row="3" maxlength="100" show-word-limit v-model.trim="form.address" placeholder="请输入完整详细地址" />
</el-form-item>
<el-form-item label="建成时间" prop="buildingArea">
<el-date-picker v-model="builtTime" type="date" placeholder="选择建成时间" value-format="YYYY-MM-DD" @change="changeBuiltTime" />
</el-form-item>
<el-form-item label="房屋用途" prop="buildingArea">
<el-select v-model="form.houseUse" placeholder="请选择房屋用途" style="width: 240px">
<el-option v-for="item in houseUserList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="小区面积" prop="villageArea">
<el-input maxlength="8" v-model.trim="form.villageArea" placeholder="请输入小区面积"><template #append></template> </el-input>
</el-form-item>
<el-form-item label="车位数量" prop="parkingSpaceCount">
<el-input maxlength="8" v-model.trim="form.parkingSpaceCount" placeholder="请输入车位数量">
<template #append></template>
</el-input>
</el-form-item>
<el-form-item style="width: 100%" label="小区图片">
<el-upload
accept=".jpg, .png"
ref="uploadRef"
:action="uploadFileUrl"
:headers="headerToken()"
list-type="picture-card"
:file-list="villageImg"
:auto-upload="false"
:limit="9"
:on-exceed="handleExceed"
@change="handleUploadChange"
@before-upload="handleBeforeUpload"
>
<el-icon><Plus /></el-icon>
<template #file="{ file }">
<div>
<img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
<span class="el-upload-list__item-actions">
<span class="el-upload-list__item-delete" @click="handleRemove(file)">
<el-icon><Delete /></el-icon>
</span>
</span>
</div>
</template>
<template #tip>
<div class="el-upload__tip">支持jpg, png等格式文件上传文件大小不超过10MB</div>
</template>
</el-upload>
</el-form-item>
<el-form-item style="width: 100%" label="小区简介">
<el-input maxlength="200" show-word-limit v-model.trim="form.remark" :rows="5" type="textarea" placeholder="小区简介" />
</el-form-item>
</div>
</div>
<div class="AddBuildingBody">
<div class="title">负责人信息</div>
<div class="addBuildingFormBody addBuildingForm_User_Body">
<el-form-item label="姓名">
<el-input maxlength="20" v-model.trim="form.manageName" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="电话" prop="managePhone">
<el-input v-model.trim="form.managePhone" placeholder="请输入电话" />
</el-form-item>
<el-form-item label="微信" prop="manageVx">
<el-input maxlength="20" v-model.trim="form.manageVx" placeholder="请输入微信"></el-input>
</el-form-item>
<el-form-item label="QQ" prop="manageQq">
<el-input maxlength="20" v-model.trim="form.manageQq" placeholder="请输入QQ"> </el-input>
</el-form-item>
<el-form-item label="邮箱" prop="manageEmail">
<el-input maxlength="30" v-model.trim="form.manageEmail" placeholder="请输入邮箱"> </el-input>
</el-form-item>
<div class="pl-100px">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="cancelForm">取消</el-button>
</div>
</div>
</div>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import Navbar from '@/layout/components/Navbar.vue';
import { ref } from 'vue';
import { pcaTextArr } from 'element-china-area-data';
import type { DataItem } from 'element-china-area-data';
import { useUserStore } from '@/store/modules/user';
import { CascaderValue } from 'element-plus';
import { addVillageAPI, editVillageAPI, getVillageByIdAPI, uploadVillageImageAPI } from '@/api/system/community';
import { communitylist_rows_type } from '@/api/system/community/type';
import { Delete, Plus } from '@element-plus/icons-vue';
const route = useRoute();
const router = useRouter();
const editId = ref(route.query.id as string);
const isEdit = computed(() => Boolean(editId.value));
const apiPrefix = import.meta.env.VITE_APP_BASE_API;
const uploadFileUrl = `${apiPrefix}/village/uploadImages`;
// 获取省 市 区╬╬╬╬╬╬╬
const pcaTextArrList = ref<DataItem[]>(pcaTextArr);
// 获取小区用途
const houseUserList = ref([
{
value: 0,
label: '住宅'
},
{
value: 1,
label: '商住两用'
},
{
value: 2,
label: '商业'
},
{
value: 3,
label: '办公'
}
]);
const formRef = ref();
const uploadRef = ref();
const form = ref<communitylist_rows_type>({
villageId: null,
villageName: '',
villageCode: '',
address: '',
houseUse: null,
villageArea: '',
remark: '',
parkingSpaceCount: '',
villageImageUrl: '',
city: '',
status: '0',
choiceStatus: 1, // 0 选择 1未选择
manageName: '',
managePhone: '',
manageVx: '',
manageEmail: '',
manageQq: '',
completDate: ''
});
const builtTime = ref(null);
const city = ref<string[]>([]);
const villageImg = ref([]);
// 表单验证
const rules = ref({
villageName: [{ required: true, message: '请输入小区名称', trigger: 'blur' }],
villageArea: [
{ required: false, message: '请输入小区面积', trigger: 'blur' },
{
validator: (rule, value, callback) => {
const num = Number(value);
if (value === '' || value === null) {
return callback();
}
if (Number.isNaN(num)) {
return callback(new Error('请输入正确的小区面积'));
}
if (num < 0) {
return callback(new Error('小区面积不能小于0'));
}
callback();
}
}
],
parkingSpaceCount: [
{ required: false, message: '请输入车位数量', trigger: 'blur' },
{
validator: (rule, value, callback) => {
const num = Number(value);
if (value === '' || value === null) {
return callback();
}
if (Number.isNaN(num)) {
return callback(new Error('请输入正确的车位数量'));
}
if (num < 0) {
return callback(new Error('车位数量不能小于0'));
}
if (num % 1 !== 0) {
return callback(new Error('请输入正确的整数车位数量'));
}
callback();
}
}
],
villageCode: [
{ required: true, message: '请输入小区编号', trigger: 'blur' },
{
validator: (_, value, callback) => {
if (value === '' || value === null) {
return callback(new Error('请输入小区编号'));
}
if (!/^[a-zA-Z0-9_-]{1,20}$/.test(value)) {
callback(new Error('请输入正确的小区编号,只能包含字母、数字、_和-'));
} else {
callback();
}
}
}
],
city: [{ required: true, message: '请选择城市', trigger: 'blur' }],
managePhone: [
{ required: false, message: '请输入电话', trigger: 'blur' },
{
validator: (_, value, callback) => {
// 值为空直接放行(非必填)
if (value === '' || value === null) {
return callback();
}
if (!/^1[3456789]\d{9}$/.test(value)) {
callback(new Error('请输入正确的手机号'));
} else {
callback();
}
}
}
],
manageEmail: [
{ required: false, message: '请输入邮箱', trigger: 'blur' },
{
validator: (_, value, callback) => {
if (value === '' || value === null) {
return callback();
}
// 邮箱总长度不超过254个字符用户名不超过64个字符
if (value.length > 254 || value.split('@')[0].length > 64) {
callback(new Error('邮箱地址过长'));
} else if (!/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/.test(value)) {
callback(new Error('请输入正确的邮箱'));
} else {
callback();
}
}
}
],
manageVx: [
{ required: false, message: '请输入微信', trigger: 'blur' },
{
validator: (_, value, callback) => {
// 值为空直接放行(非必填)
if (value === '' || value === null) {
return callback();
}
if (!/^[a-zA-Z0-9_]{4,20}$/.test(value)) {
callback(new Error('请输入正确的微信'));
} else {
callback();
}
}
}
],
manageQq: [
{ required: false, message: '请输入QQ', trigger: 'blur' },
{
validator: (_, value, callback) => {
// 值为空直接放行(非必填)
if (value === '' || value === null) {
return callback();
}
if (!/^[1-9]\d{4,10}$/.test(value)) {
callback(new Error('请输入正确的QQ'));
} else {
callback();
}
}
}
]
});
/** 获取编辑小区的信息 */
function getCommunityInfo() {
const id = editId.value;
getVillageByIdAPI(id).then((res) => {
city.value = res.data.city ? res.data.city.split('/') : [];
if (res.data.villageImageUrl) {
villageImg.value = res.data.villageImageUrl
.split(',')
.filter((item) => item !== '')
.map((item) => {
return {
name: item.split('/').pop(),
url: item
};
});
}
if (res.data.completDate) {
builtTime.value = res.data.completDate.split(' ')[0];
}
form.value = {
...res.data,
completDate: builtTime.value
};
});
}
if (isEdit.value) {
getCommunityInfo();
}
// 请求头
function headerToken() {
const useStore = useUserStore();
return {
Authorization: 'Bearer ' + useStore.token
};
}
const allowImgSuffix = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic'];
/** 检测文件大小和格式 */
function handleBeforeUpload(file: UploadFile): Promise<boolean | { file: UploadFile; type: string }> {
const type = file.raw.type.split('/').pop();
return new Promise((resolve, reject) => {
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
reject({
type: '文件大小错误',
file: file
});
ElMessage.warning('图片大小超过10M,请重新上传');
} else if (!allowImgSuffix.includes(type)) {
reject({
type: '文件类型错误',
file: file
});
ElMessage.warning('图片格式不是 png/jpg/jpeg 格式, 请重新上传');
} else {
resolve(true);
}
});
}
/** 上传狗子 */
async function handleUploadChange(file: UploadFile) {
const isUpload = await handleBeforeUpload(file);
if (typeof isUpload === 'object') {
ElMessage.error(isUpload.type);
return;
} else if (typeof isUpload === 'boolean' && isUpload === true) {
villageImg.value.push(file.raw);
}
}
function handleExceed() {
ElMessage.error('最多上传9张图片');
}
/** 移除钩子 */
const handleRemove = (file: UploadFile) => {
villageImg.value = villageImg.value.filter((item) => item.uid !== file.uid);
};
// 改变时间
const changeBuiltTime = (value: string) => {
form.value.completDate = value;
};
const cityChange = (value: CascaderValue[]) => {
form.value.city = value.join('/');
};
const reg = {
phone: /^1[3-9]\d{9}$/,
email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
qq: /^[1-9]\d{4,11}$/,
wechat: /^[a-zA-Z][a-zA-Z0-9_-]{5,19}$/
};
// 上传图片
const submitForm = () => {
formRef.value?.validate(async (valid: boolean) => {
if (valid) {
// 1) 将图片拆分为“新上传文件”和“已有图片URL”
const newFiles: File[] = [];
const existingUrls: string[] = [];
villageImg.value.forEach((item: any) => {
// 新增图片handleUploadChange push 进来的是 File
if (item instanceof File) {
newFiles.push(item);
return;
}
// 编辑态已有图片:通常是 { name, url } 结构
if (item && typeof item === 'object' && typeof item.url === 'string') {
existingUrls.push(item.url);
return;
}
// 兜底如果有字符串URL也兼容
if (typeof item === 'string') {
existingUrls.push(item);
}
});
// 2) 没有新文件不调用上传接口直接用已有URL提交
if (newFiles.length === 0) {
form.value.villageImageUrl = existingUrls.join(',');
submitFormInfo();
return;
}
// 3) 有新文件先上传新文件再与已有URL合并
const formdata = new FormData();
newFiles.forEach((file) => formdata.append('files', file));
console.log('newFiles', newFiles);
uploadVillageImageAPI(formdata).then((res) => {
const uploadedUrls = res.data.success.map((x) => x.url).filter(Boolean);
// 4) 最终图片 = 已有图片 + 新上传图片(避免覆盖)
form.value.villageImageUrl = [...existingUrls, ...uploadedUrls].join(',');
submitFormInfo();
});
}
});
};
function submitFormInfo() {
if (form.value.managePhone && form.value.managePhone.trim()) {
if (!reg.phone.test(form.value.managePhone)) {
ElMessage.error('请输入正确的手机号码');
return;
}
}
if (form.value.manageVx && form.value.manageVx.trim()) {
if (!reg.wechat.test(form.value.manageVx)) {
ElMessage.error('请输入正确的微信号');
return;
}
}
if (form.value.manageQq && form.value.manageQq.trim()) {
if (!reg.qq.test(form.value.manageQq)) {
ElMessage.error('请输入正确的QQ号码');
return;
}
}
if (form.value.manageEmail && form.value.manageEmail.trim()) {
if (!reg.email.test(form.value.manageEmail)) {
ElMessage.error('请输入正确的邮箱号码');
return;
}
}
if (isEdit.value) {
editVillageAPI<communitylist_rows_type>(form.value).then(() => {
ElMessage.success('编辑成功');
cancelForm();
});
} else {
addVillageAPI<communitylist_rows_type>(form.value).then(() => {
ElMessage.success('添加成功');
cancelForm();
});
}
}
// 返回
const cancelForm = () => {
router.back();
};
</script>
<style scoped lang="scss">
.box {
background: #deedff;
.header {
display: flex;
align-items: center;
height: 50px;
line-height: 50px;
background: transparent;
& > div {
flex: 1;
}
& > span {
padding-left: 20px;
font-size: 20px;
color: #000;
}
}
}
.AddBuildingBox {
padding: 15px;
height: 100%;
width: 100%;
}
.AddBuildingBody {
margin-top: 20px;
display: flex;
flex-direction: column;
justify-content: center;
background-color: white;
padding: 15px;
.title {
height: 40px;
line-height: 40px;
padding-left: 20px;
background-color: rgba(255, 255, 255, 1);
color: rgba(16, 16, 16, 1);
border-bottom: 1px solid #bbbbbb;
margin-bottom: 50px;
}
.addBuildingFormBody {
width: 900px;
margin: 0 auto;
padding-bottom: 40px;
.el-form-item {
width: 46%;
margin-bottom: 20px;
&:deep(.el-input, .el-date-editor.el-input) {
width: 300px;
}
}
.el-button {
width: 80px;
height: 35px;
margin-right: 20px;
}
}
}
.addBuildingForm_User_Body {
width: 900px;
margin: 0 auto;
.el-button {
width: 80px;
height: 35px;
line-height: 35px;
}
}
</style>