Browse Source

修改地区选择组件

yuhongzhe
王泽彦 11 months ago
parent
commit
1c394c15e5
  1. 4
      admin/components.d.ts
  2. 25
      admin/src/app/api/venue.ts
  3. 50
      admin/src/app/lang/zh-cn/venue.venue.json
  4. 25
      admin/src/app/views/campus/components/campus-edit.vue
  5. 545
      admin/src/app/views/venue/components/venue-edit.vue
  6. 544
      admin/src/app/views/venue/venue.vue
  7. 389
      admin/src/components/TencentMapPicker.vue
  8. 9
      niucloud/app/adminapi/controller/venue/Venue.php
  9. 3
      niucloud/app/adminapi/route/venue.php
  10. 86
      niucloud/app/model/venue/Venue.php
  11. 15
      niucloud/app/service/admin/campus/CampusService.php
  12. 2
      niucloud/app/service/admin/student_courses/StudentCoursesService.php
  13. 14
      niucloud/app/service/admin/venue/VenueService.php
  14. 3
      niucloud/app/validate/venue/Venue.php

4
admin/components.d.ts

@ -10,6 +10,7 @@ declare module '@vue/runtime-core' {
Attachment: typeof import('./src/components/upload-attachment/attachment.vue')['default'] Attachment: typeof import('./src/components/upload-attachment/attachment.vue')['default']
DiyLink: typeof import('./src/components/diy-link/index.vue')['default'] DiyLink: typeof import('./src/components/diy-link/index.vue')['default']
Editor: typeof import('./src/components/editor/index.vue')['default'] Editor: typeof import('./src/components/editor/index.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAside: typeof import('element-plus/es')['ElAside'] ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
@ -44,6 +45,7 @@ declare module '@vue/runtime-core' {
ElPopover: typeof import('element-plus/es')['ElPopover'] ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio'] ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElResult: typeof import('element-plus/es')['ElResult']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
@ -56,6 +58,8 @@ declare module '@vue/runtime-core' {
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree'] ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']

25
admin/src/app/api/venue.ts

@ -1,5 +1,7 @@
import request from '@/utils/request' import request from '@/utils/request'
// USER_CODE_BEGIN -- venue // USER_CODE_BEGIN -- venue
/** /**
* *
@ -7,7 +9,7 @@ import request from '@/utils/request'
* @returns * @returns
*/ */
export function getVenueList(params: Record<string, any>) { export function getVenueList(params: Record<string, any>) {
return request.get(`venue/venue`, { params }) return request.get(`venue/venue`, {params})
} }
/** /**
@ -16,7 +18,7 @@ export function getVenueList(params: Record<string, any>) {
* @returns * @returns
*/ */
export function getVenueInfo(id: number) { export function getVenueInfo(id: number) {
return request.get(`venue/venue/${id}`) return request.get(`venue/venue/${id}`);
} }
/** /**
@ -25,10 +27,7 @@ export function getVenueInfo(id: number) {
* @returns * @returns
*/ */
export function addVenue(params: Record<string, any>) { export function addVenue(params: Record<string, any>) {
return request.post('venue/venue', params, { return request.post('venue/venue', params, { showErrorMessage: true, showSuccessMessage: true })
showErrorMessage: true,
showSuccessMessage: true,
})
} }
/** /**
@ -38,10 +37,7 @@ export function addVenue(params: Record<string, any>) {
* @returns * @returns
*/ */
export function editVenue(params: Record<string, any>) { export function editVenue(params: Record<string, any>) {
return request.put(`venue/venue/${params.id}`, params, { return request.put(`venue/venue/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
showErrorMessage: true,
showSuccessMessage: true,
})
} }
/** /**
@ -50,10 +46,11 @@ export function editVenue(params: Record<string, any>) {
* @returns * @returns
*/ */
export function deleteVenue(id: number) { export function deleteVenue(id: number) {
return request.delete(`venue/venue/${id}`, { return request.delete(`venue/venue/${id}`, { showErrorMessage: true, showSuccessMessage: true })
showErrorMessage: true, }
showSuccessMessage: true,
}) export function getWithCampusList(params: Record<string,any>){
return request.get('venue/campus_all', {params})
} }
// USER_CODE_END -- venue // USER_CODE_END -- venue

50
admin/src/app/lang/zh-cn/venue.venue.json

@ -1,25 +1,27 @@
{ {
"id": "场地编号", "campusId":"校区",
"idPlaceholder": "请输入场地编号", "campusIdPlaceholder":"全部",
"campusId": "校区ID", "venueName":"场地名称",
"campusIdPlaceholder": "请输入校区ID", "venueNamePlaceholder":"请输入场地名称",
"venueName": "场地名称", "capacity":"场地可容纳人数上限",
"venueNamePlaceholder": "请输入场地名称", "capacityPlaceholder":"请输入场地可容纳人数上限",
"capacity": "场地可容纳人数上限", "availabilityStatus":"场地可用状态",
"capacityPlaceholder": "请输入场地可容纳人数上限", "availabilityStatusPlaceholder":"请输入场地可用状态",
"availabilityStatus": "场地可用状态", "timeRangeType":"场地可用时间范围类型",
"availabilityStatusPlaceholder": "请输入场地可用状态", "timeRangeTypePlaceholder":"请输入场地可用时间范围类型",
"timeRangeType": "场地可用时间范围类型", "timeRangeStart":"范围类型的开始时间",
"timeRangeTypePlaceholder": "请输入场地可用时间范围类型", "timeRangeStartPlaceholder":"请输入范围类型的开始时间",
"timeRangeStart": "范围类型的开始时间", "timeRangeEnd":"范围类型的结束时间",
"timeRangeStartPlaceholder": "请输入范围类型的开始时间", "timeRangeEndPlaceholder":"请输入范围类型的结束时间",
"timeRangeEnd": "范围类型的结束时间", "fixedTimeRanges":"固定时间范围类型的可用时间, 存储为JSON数组",
"timeRangeEndPlaceholder": "请输入范围类型的结束时间", "fixedTimeRangesPlaceholder":"请输入固定时间范围类型的可用时间, 存储为JSON数组",
"fixedTimeRanges": "固定时间范围类型的可用时间, 存储为JSON数组", "createdAt":"创建时间",
"fixedTimeRangesPlaceholder": "请输入固定时间范围类型的可用时间, 存储为JSON数组", "createdAtPlaceholder":"请输入创建时间",
"addVenue": "添加场地", "updatedAt":"修改时间",
"updateVenue": "编辑场地", "updatedAtPlaceholder":"请输入修改时间",
"venueDeleteTips": "确定要删除该数据吗?", "addVenue":"添加场地",
"startDate": "请选择开始时间", "updateVenue":"编辑场地",
"endDate": "请选择结束时间" "venueDeleteTips":"确定要删除该数据吗?",
} "startDate":"请选择开始时间",
"endDate":"请选择结束时间"
}

25
admin/src/app/views/campus/components/campus-edit.vue

@ -23,23 +23,26 @@
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('campusAddress')"> <!-- <el-form-item :label="t('campusAddress')">-->
<el-input <!-- <el-input-->
v-model="formData.campus_address" <!-- v-model="formData.campus_address"-->
clearable <!-- clearable-->
:placeholder="t('campusAddressPlaceholder')" <!-- :placeholder="t('campusAddressPlaceholder')"-->
class="input-width" <!-- class="input-width"-->
/> <!-- />-->
</el-form-item> <!-- </el-form-item>-->
<el-form-item :label="t('campusPreviewImage')"> <el-form-item :label="t('campusPreviewImage')">
<upload-image v-model="formData.campus_preview_image" /> <upload-image v-model="formData.campus_preview_image" />
</el-form-item> </el-form-item>
<el-form-item :label="t('campusCoordinates')"> <el-form-item :label="t('campusCoordinates')">
<el-button @click="showMapPicker">{{ <el-button @click="showMapPicker">
t('campusCoordinatesPlaceholder') {{
}}</el-button> formData.campus_coordinates?.address ||
t('campusCoordinatesPlaceholder')
}}</el-button
>
<TencentMapPicker <TencentMapPicker
ref="mapPickerRef" ref="mapPickerRef"
v-model="formData.campus_coordinates" v-model="formData.campus_coordinates"

545
admin/src/app/views/venue/components/venue-edit.vue

@ -1,278 +1,267 @@
<template> <template>
<el-dialog <el-dialog v-model="showDialog" :title="formData.id ? t('updateVenue') : t('addVenue')" width="50%" class="diy-dialog-wrap" :destroy-on-close="true">
v-model="showDialog" <el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
:title="formData.id ? t('updateVenue') : t('addVenue')" <el-form-item :label="t('campusId')" prop="campus_id">
width="50%" <el-select class="input-width" v-model="formData.campus_id" clearable :placeholder="t('campusIdPlaceholder')">
class="diy-dialog-wrap" <el-option label="请选择" value=""></el-option>
:destroy-on-close="true" <el-option
> v-for="(item, index) in campusIdList"
<el-form :key="index"
:model="formData" :label="item['campus_name']"
label-width="120px" :value="item['id']"
ref="formRef" />
:rules="formRules" </el-select>
class="page-form" </el-form-item>
v-loading="loading"
> <el-form-item :label="t('venueName')" prop="venue_name">
<el-form-item :label="t('campusId')" prop="campus_id"> <el-input v-model="formData.venue_name" clearable :placeholder="t('venueNamePlaceholder')" class="input-width" />
<el-input </el-form-item>
v-model="formData.campus_id"
clearable <el-form-item :label="t('capacity')" prop="capacity">
:placeholder="t('campusIdPlaceholder')" <el-input-number v-model="formData.capacity" clearable :placeholder="t('capacityPlaceholder')" class="input-width" :min = "1" max = "500" />
class="input-width" </el-form-item>
/>
</el-form-item> <el-form-item :label="t('availabilityStatus')" prop="availability_status">
<el-radio-group v-model="formData.availability_status" :placeholder="t('availabilityStatusPlaceholder')">
<el-form-item :label="t('venueName')" prop="venue_name"> <el-radio
<el-input v-for="(item, index) in availability_statusList"
v-model="formData.venue_name" :key="index" :label="item.value">
clearable {{ item.name }}
:placeholder="t('venueNamePlaceholder')" </el-radio>
class="input-width" </el-radio-group>
/> </el-form-item>
</el-form-item>
<el-form-item :label="t('timeRangeType')" prop="time_range_type">
<el-form-item :label="t('capacity')" prop="capacity"> <el-radio-group v-model="formData.time_range_type" :placeholder="t('timeRangeTypePlaceholder')">
<el-input <el-radio
v-model="formData.capacity" v-for="(item, index) in time_range_typeList"
clearable :key="index" :label="item.value">
:placeholder="t('capacityPlaceholder')" {{ item.name }}
class="input-width" </el-radio>
/> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item :label="t('availabilityStatus')" prop="availability_status"> <el-form-item :label="t('timeRangeStart')" class="input-width">
<el-input <el-date-picker
v-model="formData.availability_status" class="flex-1 !flex"
clearable v-model="formData.time_range_start"
:placeholder="t('availabilityStatusPlaceholder')" clearable
class="input-width" type="datetime"
/> value-format="YYYY-MM-DD HH:mm:ss"
</el-form-item> :placeholder="t('timeRangeStartPlaceholder')">
</el-date-picker>
<el-form-item :label="t('timeRangeType')" prop="time_range_type"> </el-form-item>
<el-input <el-form-item :label="t('timeRangeEnd')" class="input-width">
v-model="formData.time_range_type" <el-date-picker
clearable class="flex-1 !flex"
:placeholder="t('timeRangeTypePlaceholder')" v-model="formData.time_range_end"
class="input-width" clearable
/> type="datetime"
</el-form-item> value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('timeRangeEndPlaceholder')">
<el-form-item :label="t('timeRangeStart')"> </el-date-picker>
<el-input </el-form-item>
v-model="formData.time_range_start" <el-form-item :label="t('fixedTimeRanges')" class="input-width">
clearable <el-date-picker
:placeholder="t('timeRangeStartPlaceholder')" class="flex-1 !flex"
class="input-width" v-model="formData.fixed_time_ranges"
/> clearable
</el-form-item> type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
<el-form-item :label="t('timeRangeEnd')"> :placeholder="t('fixedTimeRangesPlaceholder')">
<el-input </el-date-picker>
v-model="formData.time_range_end" </el-form-item>
clearable </el-form>
:placeholder="t('timeRangeEndPlaceholder')"
class="input-width" <template #footer>
/> <span class="dialog-footer">
</el-form-item> <el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{
<el-form-item :label="t('fixedTimeRanges')"> t('confirm')
<el-input }}</el-button>
v-model="formData.fixed_time_ranges" </span>
clearable </template>
:placeholder="t('fixedTimeRangesPlaceholder')" </el-dialog>
class="input-width" </template>
/>
</el-form-item> <script lang="ts" setup>
</el-form> import { ref, reactive, computed, watch } from 'vue'
import { useDictionary } from '@/app/api/dict'
<template #footer> import { t } from '@/lang'
<span class="dialog-footer"> import type { FormInstance } from 'element-plus'
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button> import { addVenue, editVenue, getVenueInfo, getWithCampusList } from '@/app/api/venue'
<el-button
type="primary" let showDialog = ref(false)
:loading="loading" const loading = ref(false)
@click="confirm(formRef)"
>{{ t('confirm') }}</el-button /**
> * 表单数据
</span> */
</template> const initialFormData = {
</el-dialog> id: '',
</template> campus_id: '',
venue_name: '',
<script lang="ts" setup> capacity: '',
import { ref, reactive, computed, watch } from 'vue' availability_status: '',
import { useDictionary } from '@/app/api/dict' time_range_type: '',
import { t } from '@/lang' time_range_start: '',
import type { FormInstance } from 'element-plus' time_range_end: '',
import { addVenue, editVenue, getVenueInfo } from '@/app/api/venue' fixed_time_ranges: '',
}
let showDialog = ref(false) const formData: Record<string, any> = reactive({ ...initialFormData })
const loading = ref(false)
const formRef = ref<FormInstance>()
/**
* 表单数据 //
*/ const formRules = computed(() => {
const initialFormData = { return {
id: '', campus_id: [
campus_id: '', { required: true, message: t('campusIdPlaceholder'), trigger: 'blur' },
venue_name: '',
capacity: '', ]
availability_status: '', ,
time_range_type: '', venue_name: [
time_range_start: '', { required: true, message: t('venueNamePlaceholder'), trigger: 'blur' },
time_range_end: '',
fixed_time_ranges: '', ]
} ,
const formData: Record<string, any> = reactive({ ...initialFormData }) capacity: [
{ required: true, message: t('capacityPlaceholder'), trigger: 'blur' },
const formRef = ref<FormInstance>() { validator: (rule: any, value: string, callback: any) => { if (value && !/^\d{1,500}$/.test(value)) { callback(new Error(t('generateBetween')))} else { callback() }}},
]
// ,
const formRules = computed(() => { availability_status: [
return { { required: true, message: t('availabilityStatusPlaceholder'), trigger: 'blur' },
campus_id: [
{ required: true, message: t('campusIdPlaceholder'), trigger: 'blur' }, ]
], ,
venue_name: [ time_range_type: [
{ required: true, message: t('venueNamePlaceholder'), trigger: 'blur' }, { required: true, message: t('timeRangeTypePlaceholder'), trigger: 'blur' },
],
capacity: [ ]
{ required: true, message: t('capacityPlaceholder'), trigger: 'blur' }, ,
], time_range_start: [
availability_status: [ { required: true, message: t('timeRangeStartPlaceholder'), trigger: 'blur' },
{
required: true, ]
message: t('availabilityStatusPlaceholder'), ,
trigger: 'blur', time_range_end: [
}, { required: true, message: t('timeRangeEndPlaceholder'), trigger: 'blur' },
],
time_range_type: [ ]
{ ,
required: true, fixed_time_ranges: [
message: t('timeRangeTypePlaceholder'), { required: true, message: t('fixedTimeRangesPlaceholder'), trigger: 'blur' },
trigger: 'blur',
}, ]
], ,
time_range_start: [ }
{ })
required: true,
message: t('timeRangeStartPlaceholder'), const emit = defineEmits(['complete'])
trigger: 'blur',
}, /**
], * 确认
time_range_end: [ * @param formEl
{ */
required: true, const confirm = async (formEl: FormInstance | undefined) => {
message: t('timeRangeEndPlaceholder'), if (loading.value || !formEl) return
trigger: 'blur', let save = formData.id ? editVenue : addVenue
},
], await formEl.validate(async (valid) => {
fixed_time_ranges: [ if (valid) {
{ loading.value = true
required: true,
message: t('fixedTimeRangesPlaceholder'), let data = formData
trigger: 'blur',
}, save(data).then(res => {
], loading.value = false
} showDialog.value = false
}) emit('complete')
}).catch(err => {
const emit = defineEmits(['complete']) loading.value = false
})
/** }
* 确认 })
* @param formEl }
*/
const confirm = async (formEl: FormInstance | undefined) => { //
if (loading.value || !formEl) return let availability_statusList = ref([])
let save = formData.id ? editVenue : addVenue const availability_statusDictList = async () => {
availability_statusList.value = await (await useDictionary('SiteStatus')).data.dictionary
await formEl.validate(async (valid) => { }
if (valid) { availability_statusDictList();
loading.value = true watch(() => availability_statusList.value, () => { formData.availability_status = availability_statusList.value[0].value })
let time_range_typeList = ref([])
let data = formData const time_range_typeDictList = async () => {
time_range_typeList.value = await (await useDictionary('ALLOTTED_TIME')).data.dictionary
save(data) }
.then((res) => { time_range_typeDictList();
loading.value = false watch(() => time_range_typeList.value, () => { formData.time_range_type = time_range_typeList.value[0].value })
showDialog.value = false
emit('complete')
}) const campusIdList = ref([] as any[])
.catch((err) => { const setCampusIdList = async () => {
loading.value = false campusIdList.value = await (await getWithCampusList({})).data
}) }
} setCampusIdList()
}) const setFormData = async (row: any = null) => {
} Object.assign(formData, initialFormData)
loading.value = true
// if(row){
const data = await (await getVenueInfo(row.id)).data
const setFormData = async (row: any = null) => { if (data) Object.keys(formData).forEach((key: string) => {
Object.assign(formData, initialFormData) if (data[key] != undefined) formData[key] = data[key]
loading.value = true })
if (row) { }
const data = await (await getVenueInfo(row.id)).data loading.value = false
if (data) }
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key] //
}) const mobileVerify = (rule: any, value: any, callback: any) => {
} if (value && !/^1[3-9]\d{9}$/.test(value)) {
loading.value = false callback(new Error(t('generateMobile')))
} } else {
callback()
// }
const mobileVerify = (rule: any, value: any, callback: any) => { }
if (value && !/^1[3-9]\d{9}$/.test(value)) {
callback(new Error(t('generateMobile'))) //
} else { const idCardVerify = (rule: any, value: any, callback: any) => {
callback() if (value && !/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value)) {
} callback(new Error(t('generateIdCard')))
} } else {
callback()
// }
const idCardVerify = (rule: any, value: any, callback: any) => { }
if (
value && //
!/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test( const emailVerify = (rule: any, value: any, callback: any) => {
value if (value && !/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(value)) {
) callback(new Error(t('generateEmail')))
) { } else {
callback(new Error(t('generateIdCard'))) callback()
} else { }
callback() }
}
} //
const numberVerify = (rule: any, value: any, callback: any) => {
// if (!Number.isInteger(value)) {
const emailVerify = (rule: any, value: any, callback: any) => { callback(new Error(t('generateNumber')))
if (value && !/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(value)) { } else {
callback(new Error(t('generateEmail'))) callback()
} else { }
callback() }
}
} defineExpose({
showDialog,
// setFormData
const numberVerify = (rule: any, value: any, callback: any) => { })
if (!Number.isInteger(value)) { </script>
callback(new Error(t('generateNumber')))
} else { <style lang="scss" scoped></style>
callback() <style lang="scss">
} .diy-dialog-wrap .el-form-item__label{
} height: auto !important;
}
defineExpose({ </style>
showDialog,
setFormData,
})
</script>
<style lang="scss" scoped></style>
<style lang="scss">
.diy-dialog-wrap .el-form-item__label {
height: auto !important;
}
</style>

544
admin/src/app/views/venue/venue.vue

@ -1,293 +1,251 @@
<template> <template>
<div class="main-container"> <div class="main-container">
<el-card class="box-card !border-none" shadow="never"> <el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-lg">{{ pageName }}</span> <div class="flex justify-between items-center">
<el-button type="primary" @click="addEvent"> <span class="text-lg">{{pageName}}</span>
{{ t('addVenue') }} <el-button type="primary" @click="addEvent">
</el-button> {{ t('addVenue') }}
</div> </el-button>
</div>
<el-card
class="box-card !border-none my-[10px] table-search-wrap" <el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
shadow="never" <el-form :inline="true" :model="venueTable.searchParam" ref="searchFormRef">
>
<el-form <el-form-item :label="t('campusId')" prop="campus_id">
:inline="true" <el-select class="w-[280px]" v-model="venueTable.searchParam.campus_id" clearable :placeholder="t('campusIdPlaceholder')">
:model="venueTable.searchParam" <el-option
ref="searchFormRef" v-for="(item, index) in campusIdList"
> :key="index"
<el-form-item :label="t('campusId')" prop="campus_id"> :label="item['campus_name']"
<el-input :value="item['id']"
v-model="venueTable.searchParam.campus_id" />
:placeholder="t('campusIdPlaceholder')" </el-select>
/> </el-form-item>
</el-form-item>
<el-form-item :label="t('venueName')" prop="venue_name"> <el-form-item :label="t('venueName')" prop="venue_name">
<el-input <el-input v-model="venueTable.searchParam.venue_name" :placeholder="t('venueNamePlaceholder')" />
v-model="venueTable.searchParam.venue_name" </el-form-item>
:placeholder="t('venueNamePlaceholder')"
/> <el-form-item :label="t('availabilityStatus')" prop="availability_status">
</el-form-item> <el-select class="w-[280px]" v-model="venueTable.searchParam.availability_status" clearable :placeholder="t('availabilityStatusPlaceholder')">
<el-form-item :label="t('capacity')" prop="capacity"> <el-option label="全部" value=""></el-option>
<el-input <el-option
v-model="venueTable.searchParam.capacity" v-for="(item, index) in availability_statusList"
:placeholder="t('capacityPlaceholder')" :key="index"
/> :label="item.name"
</el-form-item> :value="item.value"
<el-form-item />
:label="t('availabilityStatus')" </el-select>
prop="availability_status" </el-form-item>
>
<el-input
v-model="venueTable.searchParam.availability_status" <el-form-item :label="t('timeRangeType')" prop="time_range_type">
:placeholder="t('availabilityStatusPlaceholder')" <el-select class="w-[280px]" v-model="venueTable.searchParam.time_range_type" clearable :placeholder="t('timeRangeTypePlaceholder')">
/> <el-option label="全部" value=""></el-option>
</el-form-item> <el-option
<el-form-item :label="t('timeRangeType')" prop="time_range_type"> v-for="(item, index) in time_range_typeList"
<el-input :key="index"
v-model="venueTable.searchParam.time_range_type" :label="item.name"
:placeholder="t('timeRangeTypePlaceholder')" :value="item.value"
/> />
</el-form-item> </el-select>
<el-form-item :label="t('timeRangeStart')" prop="time_range_start"> </el-form-item>
<el-input
v-model="venueTable.searchParam.time_range_start" <el-form-item :label="t('createdAt')" prop="created_at">
:placeholder="t('timeRangeStartPlaceholder')" <el-date-picker v-model="venueTable.searchParam.created_at" type="datetimerange" format="YYYY-MM-DD hh:mm:ss"
/> :start-placeholder="t('startDate')" :end-placeholder="t('endDate')" />
</el-form-item> </el-form-item>
<el-form-item :label="t('timeRangeEnd')" prop="time_range_end">
<el-input <el-form-item :label="t('updatedAt')" prop="updated_at">
v-model="venueTable.searchParam.time_range_end" <el-date-picker v-model="venueTable.searchParam.updated_at" type="datetimerange" format="YYYY-MM-DD hh:mm:ss"
:placeholder="t('timeRangeEndPlaceholder')" :start-placeholder="t('startDate')" :end-placeholder="t('endDate')" />
/> </el-form-item>
</el-form-item>
<el-form-item :label="t('fixedTimeRanges')" prop="fixed_time_ranges"> <el-form-item>
<el-input <el-button type="primary" @click="loadVenueList()">{{ t('search') }}</el-button>
v-model="venueTable.searchParam.fixed_time_ranges" <el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
:placeholder="t('fixedTimeRangesPlaceholder')" </el-form-item>
/> </el-form>
</el-form-item> </el-card>
<el-form-item> <div class="mt-[10px]">
<el-button type="primary" @click="loadVenueList()">{{ <el-table :data="venueTable.data" size="large" v-loading="venueTable.loading">
t('search') <template #empty>
}}</el-button> <span>{{ !venueTable.loading ? t('emptyData') : '' }}</span>
<el-button @click="resetForm(searchFormRef)">{{ </template>
t('reset') <el-table-column prop="campus_id_name" :label="t('campusId')" min-width="120" :show-overflow-tooltip="true"/>
}}</el-button>
</el-form-item> <el-table-column prop="venue_name" :label="t('venueName')" min-width="120" :show-overflow-tooltip="true"/>
</el-form>
</el-card> <el-table-column prop="capacity" :label="t('capacity')" min-width="120" :show-overflow-tooltip="true"/>
<div class="mt-[10px]"> <el-table-column :label="t('availabilityStatus')" min-width="180" align="center" :show-overflow-tooltip="true">
<el-table <template #default="{ row }">
:data="venueTable.data" <div v-for="(item, index) in availability_statusList">
size="large" <div v-if="item.value == row.availability_status">{{ item.name }}</div>
v-loading="venueTable.loading" </div>
> </template>
<template #empty> </el-table-column>
<span>{{ !venueTable.loading ? t('emptyData') : '' }}</span>
</template> <el-table-column :label="t('timeRangeType')" min-width="180" align="center" :show-overflow-tooltip="true">
<el-table-column <template #default="{ row }">
prop="campus_id" <div v-for="(item, index) in time_range_typeList">
:label="t('campusId')" <div v-if="item.value == row.time_range_type">{{ item.name }}</div>
min-width="120" </div>
:show-overflow-tooltip="true" </template>
/> </el-table-column>
<el-table-column <el-table-column prop="created_at" :label="t('createdAt')" min-width="120" :show-overflow-tooltip="true"/>
prop="venue_name"
:label="t('venueName')" <el-table-column prop="updated_at" :label="t('updatedAt')" min-width="120" :show-overflow-tooltip="true"/>
min-width="120"
:show-overflow-tooltip="true" <el-table-column :label="t('operation')" fixed="right" min-width="120">
/> <template #default="{ row }">
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-table-column <el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
prop="capacity" </template>
:label="t('capacity')" </el-table-column>
min-width="120"
:show-overflow-tooltip="true" </el-table>
/> <div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="venueTable.page" v-model:page-size="venueTable.limit"
<el-table-column layout="total, sizes, prev, pager, next, jumper" :total="venueTable.total"
prop="availability_status" @size-change="loadVenueList()" @current-change="loadVenueList" />
:label="t('availabilityStatus')" </div>
min-width="120" </div>
:show-overflow-tooltip="true"
/> <edit ref="editVenueDialog" @complete="loadVenueList" />
</el-card>
<el-table-column </div>
prop="time_range_type" </template>
:label="t('timeRangeType')"
min-width="120" <script lang="ts" setup>
:show-overflow-tooltip="true" import { reactive, ref, watch } from 'vue'
/> import { t } from '@/lang'
import { useDictionary } from '@/app/api/dict'
<el-table-column import { getVenueList, deleteVenue, getWithCampusList } from '@/app/api/venue'
prop="time_range_start" import { img } from '@/utils/common'
:label="t('timeRangeStart')" import { ElMessageBox,FormInstance } from 'element-plus'
min-width="120" import Edit from '@/app/views/venue/components/venue-edit.vue'
:show-overflow-tooltip="true" import { useRoute } from 'vue-router'
/> const route = useRoute()
const pageName = route.meta.title;
<el-table-column
prop="time_range_end" let venueTable = reactive({
:label="t('timeRangeEnd')" page: 1,
min-width="120" limit: 10,
:show-overflow-tooltip="true" total: 0,
/> loading: true,
data: [],
<el-table-column searchParam:{
prop="fixed_time_ranges" "campus_id":"",
:label="t('fixedTimeRanges')" "venue_name":"",
min-width="120" "capacity":"",
:show-overflow-tooltip="true" "availability_status":"",
/> "time_range_type":"",
"created_at":"",
<el-table-column "updated_at":""
:label="t('operation')" }
fixed="right" })
min-width="120"
> const searchFormRef = ref<FormInstance>()
<template #default="{ row }">
<el-button type="primary" link @click="editEvent(row)">{{ //
t('edit') const selectData = ref<any[]>([])
}}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{ //
t('delete') const availability_statusList = ref([] as any[])
}}</el-button> const availability_statusDictList = async () => {
</template> availability_statusList.value = await (await useDictionary('SiteStatus')).data.dictionary
</el-table-column> }
</el-table> availability_statusDictList();
<div class="mt-[16px] flex justify-end"> const time_range_typeList = ref([] as any[])
<el-pagination const time_range_typeDictList = async () => {
v-model:current-page="venueTable.page" time_range_typeList.value = await (await useDictionary('ALLOTTED_TIME')).data.dictionary
v-model:page-size="venueTable.limit" }
layout="total, sizes, prev, pager, next, jumper" time_range_typeDictList();
:total="venueTable.total"
@size-change="loadVenueList()" /**
@current-change="loadVenueList" * 获取场地列表
/> */
</div> const loadVenueList = (page: number = 1) => {
</div> venueTable.loading = true
venueTable.page = page
<edit ref="editVenueDialog" @complete="loadVenueList" />
</el-card> getVenueList({
</div> page: venueTable.page,
</template> limit: venueTable.limit,
...venueTable.searchParam
<script lang="ts" setup> }).then(res => {
import { reactive, ref, watch } from 'vue' venueTable.loading = false
import { t } from '@/lang' venueTable.data = res.data.data
import { useDictionary } from '@/app/api/dict' venueTable.total = res.data.total
import { getVenueList, deleteVenue } from '@/app/api/venue' }).catch(() => {
import { img } from '@/utils/common' venueTable.loading = false
import { ElMessageBox, FormInstance } from 'element-plus' })
import Edit from '@/app/views/venue/components/venue-edit.vue' }
import { useRoute } from 'vue-router' loadVenueList()
const route = useRoute()
const pageName = route.meta.title const editVenueDialog: Record<string, any> | null = ref(null)
let venueTable = reactive({ /**
page: 1, * 添加场地
limit: 10, */
total: 0, const addEvent = () => {
loading: true, editVenueDialog.value.setFormData()
data: [], editVenueDialog.value.showDialog = true
searchParam: { }
campus_id: '',
venue_name: '', /**
capacity: '', * 编辑场地
availability_status: '', * @param data
time_range_type: '', */
time_range_start: '', const editEvent = (data: any) => {
time_range_end: '', editVenueDialog.value.setFormData(data)
fixed_time_ranges: '', editVenueDialog.value.showDialog = true
}, }
})
/**
const searchFormRef = ref<FormInstance>() * 删除场地
*/
// const deleteEvent = (id: number) => {
const selectData = ref<any[]>([]) ElMessageBox.confirm(t('venueDeleteTips'), t('warning'),
{
// confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
/** type: 'warning',
* 获取场地列表 }
*/ ).then(() => {
const loadVenueList = (page: number = 1) => { deleteVenue(id).then(() => {
venueTable.loading = true loadVenueList()
venueTable.page = page }).catch(() => {
})
getVenueList({ })
page: venueTable.page, }
limit: venueTable.limit,
...venueTable.searchParam,
}) const campusIdList = ref([])
.then((res) => { const setCampusIdList = async () => {
venueTable.loading = false campusIdList.value = await (await getWithCampusList({})).data
venueTable.data = res.data.data }
venueTable.total = res.data.total setCampusIdList()
})
.catch(() => { const resetForm = (formEl: FormInstance | undefined) => {
venueTable.loading = false if (!formEl) return
}) formEl.resetFields()
} loadVenueList()
loadVenueList() }
</script>
const editVenueDialog: Record<string, any> | null = ref(null)
<style lang="scss" scoped>
/** /* 多行超出隐藏 */
* 添加场地 .multi-hidden {
*/ word-break: break-all;
const addEvent = () => { text-overflow: ellipsis;
editVenueDialog.value.setFormData() overflow: hidden;
editVenueDialog.value.showDialog = true display: -webkit-box;
} -webkit-line-clamp: 2;
-webkit-box-orient: vertical;
/** }
* 编辑场地 </style>
* @param data
*/
const editEvent = (data: any) => {
editVenueDialog.value.setFormData(data)
editVenueDialog.value.showDialog = true
}
/**
* 删除场地
*/
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('venueDeleteTips'), t('warning'), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
}).then(() => {
deleteVenue(id)
.then(() => {
loadVenueList()
})
.catch(() => {})
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadVenueList()
}
</script>
<style lang="scss" scoped>
/* 多行超出隐藏 */
.multi-hidden {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>

389
admin/src/components/TencentMapPicker.vue

@ -12,9 +12,7 @@
<div <div
v-if="!props.modelValue.lat || !props.modelValue.lng" v-if="!props.modelValue.lat || !props.modelValue.lng"
class="map-placeholder" class="map-placeholder"
> ></div>
{{ props.placeholder }}
</div>
</div> </div>
<div class="address-search"> <div class="address-search">
<el-select <el-select
@ -55,10 +53,7 @@
:value="item.id" :value="item.id"
/> />
</el-select> </el-select>
<el-input <el-input v-model="detailAddress" placeholder="输入地区" />
v-model="detailAddress"
:placeholder="t('detailAddressPlaceholder')"
/>
<el-button <el-button
type="primary" type="primary"
@click="handleAddressSearch" @click="handleAddressSearch"
@ -78,11 +73,13 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, watch, computed } from 'vue' import { ref, onMounted, watch, computed, nextTick, onBeforeUnmount } from 'vue'
import { getAreaListByPid, getAreaByCode } from '@/app/api/sys' import { ElMessage } from 'element-plus'
import { createMarker, latLngToAddress, addressToLatLng } from '@/utils/qqmap' import { getAreaListByPid } from '@/app/api/sys'
import { createMarker, addressToLatLng } from '@/utils/qqmap'
import { t } from '@/lang' import { t } from '@/lang'
// ====================== Props ======================
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Object, type: Object,
@ -104,6 +101,7 @@ const props = defineProps({
const emit = defineEmits(['update:visible', 'update:modelValue', 'confirm']) const emit = defineEmits(['update:visible', 'update:modelValue', 'confirm'])
// ====================== ======================
const dialogVisible = computed({ const dialogVisible = computed({
get() { get() {
return props.visible return props.visible
@ -113,10 +111,74 @@ const dialogVisible = computed({
}, },
}) })
watch(dialogVisible, (newVal) => { //
emit('update:modelValue', newVal) let map: any = null
let marker: any = null
const mapKey = ref('YOUR_API_KEY') // API Key
let mapScript: HTMLScriptElement | null = null
let resizeTimer: number | null = null
//
const province = ref('')
const city = ref('')
const district = ref('')
const detailAddress = ref('')
const provinceList = ref<any[]>([])
const cityList = ref<any[]>([])
const districtList = ref<any[]>([])
// ====================== ======================
onMounted(() => {
// SDK
const preloadScript = document.createElement('script')
preloadScript.src = `https://map.qq.com/api/gljs?key= ${mapKey.value}`
document.head.appendChild(preloadScript)
}) })
// ====================== ======================
watch(
dialogVisible,
async (newVal) => {
if (newVal) {
try {
//
cleanupMap()
// DOM
await nextTick()
//
const container = document.getElementById('container')
if (!container) throw new Error('地图容器未找到')
// SDK
await loadMapSDK()
//
initMap()
//
const res = await getAreaListByPid(0)
provinceList.value = res.data
//
if (props.modelValue.address) {
await parseAndSetAddress(props.modelValue.address)
}
// resize
bindResizeListener()
} catch (error) {
handleError(error)
}
} else {
cleanupMap()
}
},
{ immediate: true }
)
// ====================== ======================
const handleClose = (done: () => void) => { const handleClose = (done: () => void) => {
done() done()
} }
@ -126,80 +188,164 @@ const handleConfirm = () => {
ElMessage.warning(t('mapPickerWarning')) ElMessage.warning(t('mapPickerWarning'))
return return
} }
//
const provinceName = province.value
? provinceList.value.find((p) => p.id === province.value)?.name || ''
: ''
const cityName = city.value
? cityList.value.find((c) => c.id === city.value)?.name || ''
: ''
const districtName = district.value
? districtList.value.find((d) => d.id === district.value)?.name || ''
: ''
const fullAddress = `${provinceName}${cityName}${districtName}${detailAddress.value}`
emit('confirm', { emit('confirm', {
lat: props.modelValue.lat, lat: props.modelValue.lat,
lng: props.modelValue.lng, lng: props.modelValue.lng,
address: detailAddress.value, address: fullAddress,
}) })
dialogVisible.value = false dialogVisible.value = false
} }
// //
let map: any const parseAndSetAddress = async (address: string) => {
let marker: any //
const mapKey = ref('') const { provinceName, cityName, districtName, detail } = parseAddress(address)
detailAddress.value = detail
// // 1.
const province = ref('') if (provinceName) {
const city = ref('') const provinceItem = provinceList.value.find((p) =>
const district = ref('') p.name.includes(provinceName)
const detailAddress = ref('') )
const provinceList = ref<any[]>([]) if (provinceItem) {
const cityList = ref<any[]>([]) province.value = provinceItem.id
const districtList = ref<any[]>([])
// // 2.
const initMapScript = () => { const cityRes = await getAreaListByPid(province.value)
const container = document.getElementById('container') cityList.value = cityRes.data
if (!container) return
if (cityName) {
mapScript = document.createElement('script') const cityItem = cityList.value.find((c) => c.name.includes(cityName))
mapKey.value = 'IZQBZ-3UHEU-WTCVD-2464U-I5N4V-ZFFU3' if (cityItem) {
mapScript.type = 'text/javascript' city.value = cityItem.id
mapScript.src =
'https://map.qq.com/api/gljs?libraries=tools,service&v=1.exp&key=IZQBZ-3UHEU-WTCVD-2464U-I5N4V-ZFFU3' // 3.
document.body.appendChild(mapScript) const districtRes = await getAreaListByPid(city.value)
mapScript.onload = () => { districtList.value = districtRes.data
setTimeout(() => {
initMap() if (districtName) {
}, 500) const districtItem = districtList.value.find((d) =>
d.name.includes(districtName)
)
if (districtItem) {
district.value = districtItem.id
}
}
}
}
}
}
//
handleAddressSearch()
}
//
const parseAddress = (address: string) => {
let provinceName = ''
let cityName = ''
let districtName = ''
let detail = address
// //
const provinceMatch = address.match(/^([^省市自治区]+[省市区]|[^市]+市)/)
if (provinceMatch) {
provinceName = provinceMatch[0]
detail = address.slice(provinceName.length)
}
//
const cityMatch = detail.match(/^([^市区]+市)/)
if (cityMatch) {
cityName = cityMatch[0]
detail = detail.slice(cityName.length)
}
// /
const districtMatch = detail.match(/^([^区县]+[区县])/)
if (districtMatch) {
districtName = districtMatch[0]
detail = detail.slice(districtName.length)
} }
mapScript.onerror = (error) => { return {
console.error('地图脚本加载失败:', error) provinceName,
cityName,
districtName,
detail: detail.trim() || '',
} }
} }
let mapScript: HTMLScriptElement | null = null // ====================== ======================
const cleanupMap = () => {
if (mapScript) {
document.body.removeChild(mapScript)
mapScript = null
}
watch( if (map) {
dialogVisible, map.destroy()
(newVal) => { map = null
if (newVal) { }
if (mapScript) {
document.body.removeChild(mapScript) marker = null
mapScript = null
} if (resizeTimer) {
initMapScript() clearTimeout(resizeTimer)
getAreaListByPid(0).then((res) => { resizeTimer = null
provinceList.value = res.data }
})
} else { window.removeEventListener('resize', handleResize)
if (mapScript) { }
document.body.removeChild(mapScript)
mapScript = null const loadMapSDK = (): Promise<void> => {
return new Promise((resolve, reject) => {
mapKey.value = 'AKTBZ-OGICT-E5NXQ-LGEGK-H5AJ5-M2BOX'
mapScript = document.createElement('script')
mapScript.type = 'text/javascript'
mapScript.src = `https://map.qq.com/api/gljs?libraries=tools ,service&v=1.exp&key=${mapKey.value}`
mapScript.onload = () => {
if ((window as any).TMap) {
resolve()
} else {
reject(new Error('TMap未定义'))
} }
map = null
marker = null
} }
},
{ immediate: true } mapScript.onerror = (error) => {
) reject(error)
}
document.body.appendChild(mapScript)
})
}
const initMap = () => { const initMap = () => {
console.log('initMap') const container = document.getElementById('container')
if (!(window as any).TMap || !container) {
throw new Error('地图SDK未加载或容器不存在')
}
const TMap = (window as any).TMap const TMap = (window as any).TMap
const center = new TMap.LatLng(39.90403, 116.407526) const center = new TMap.LatLng(39.90403, 116.407526)
map = new TMap.Map('container', { map = new TMap.Map('container', {
center, center,
zoom: 12, zoom: 12,
@ -221,20 +367,37 @@ const initMap = () => {
}) })
} }
// const bindResizeListener = () => {
const handleProvinceChange = (val: string) => { window.addEventListener('resize', handleResize)
getAreaListByPid(val).then((res) => { }
const handleResize = () => {
if (resizeTimer) clearTimeout(resizeTimer)
resizeTimer = setTimeout(() => {
map?.setSize()
}, 300)
}
// ====================== ======================
const handleProvinceChange = async (val: string) => {
try {
const res = await getAreaListByPid(val)
cityList.value = res.data cityList.value = res.data
city.value = '' city.value = ''
district.value = '' district.value = ''
}) } catch (error) {
handleError(error)
}
} }
const handleCityChange = (val: string) => { const handleCityChange = async (val: string) => {
getAreaListByPid(val).then((res) => { try {
const res = await getAreaListByPid(val)
districtList.value = res.data districtList.value = res.data
district.value = '' district.value = ''
}) } catch (error) {
handleError(error)
}
} }
const handleDistrictChange = (val: string) => { const handleDistrictChange = (val: string) => {
@ -242,47 +405,59 @@ const handleDistrictChange = (val: string) => {
} }
// //
const handleAddressSearch = () => { const handleAddressSearch = async () => {
const address = [ try {
province.value const address = [
? provinceList.value.find((p) => p.id === province.value)?.name province.value
: '', ? provinceList.value.find((p) => p.id === province.value)?.name
city.value ? cityList.value.find((c) => c.id === city.value)?.name : '', : '',
district.value city.value ? cityList.value.find((c) => c.id === city.value)?.name : '',
? districtList.value.find((d) => d.id === district.value)?.name district.value
: '', ? districtList.value.find((d) => d.id === district.value)?.name
detailAddress.value, : '',
].join('') detailAddress.value,
].join('')
addressToLatLng({ mapKey: mapKey.value, address }).then(
({ message, result }) => { const { message, result } = await addressToLatLng({
if (message == 'Success' || message == 'query ok') { mapKey: mapKey.value,
const latLng = new (window as any).TMap.LatLng( address,
result.location.lat, })
result.location.lng
) if (message == 'Success' || message == 'query ok') {
map.setCenter(latLng) const latLng = new (window as any).TMap.LatLng(
marker.updateGeometries({ result.location.lat,
id: 'center', result.location.lng
position: latLng, )
}) map.setCenter(latLng)
emit('update:modelValue', { marker.updateGeometries({
lat: result.location.lat, id: 'center',
lng: result.location.lng, position: latLng,
address: detailAddress.value, })
}) emit('update:modelValue', {
} lat: result.location.lat,
lng: result.location.lng,
address: detailAddress.value,
})
} }
) } catch (error) {
handleError(error)
}
}
// ====================== ======================
const handleError = (error: any) => {
console.error('地图组件错误:', error)
ElMessage.error(t('mapLoadFailed'))
dialogVisible.value = false
} }
// //
watch( watch(
() => props.modelValue, () => props.modelValue,
(newVal) => { (newVal) => {
if (newVal.lat && newVal.lng) { if (newVal.lat && newVal.lng && map) {
const latLng = new (window as any).TMap.LatLng(newVal.lat, newVal.lng) const latLng = new (window as any).TMap.LatLng(newVal.lat, newVal.lng)
map?.setCenter(latLng) map.setCenter(latLng)
marker?.updateGeometries({ marker?.updateGeometries({
id: 'center', id: 'center',
position: latLng, position: latLng,
@ -291,6 +466,10 @@ watch(
}, },
{ immediate: true } { immediate: true }
) )
onBeforeUnmount(() => {
cleanupMap()
})
</script> </script>
<style scoped> <style scoped>

9
niucloud/app/adminapi/controller/venue/Venue.php

@ -33,9 +33,8 @@ class Venue extends BaseAdminController
["capacity",""], ["capacity",""],
["availability_status",""], ["availability_status",""],
["time_range_type",""], ["time_range_type",""],
["time_range_start",""], ["created_at",""],
["time_range_end",""], ["updated_at",""]
["fixed_time_ranges",""]
]); ]);
return success((new VenueService())->getPage($data)); return success((new VenueService())->getPage($data));
} }
@ -103,4 +102,8 @@ class Venue extends BaseAdminController
} }
public function getCampusAll(){
return success(( new VenueService())->getCampusAll());
}
} }

3
niucloud/app/adminapi/route/venue.php

@ -14,6 +14,7 @@ use think\facade\Route;
use app\adminapi\middleware\AdminCheckRole; use app\adminapi\middleware\AdminCheckRole;
use app\adminapi\middleware\AdminCheckToken; use app\adminapi\middleware\AdminCheckToken;
use app\adminapi\middleware\AdminLog; use app\adminapi\middleware\AdminLog;
// USER_CODE_BEGIN -- venue // USER_CODE_BEGIN -- venue
Route::group('venue', function () { Route::group('venue', function () {
@ -29,6 +30,8 @@ Route::group('venue', function () {
//删除场地 //删除场地
Route::delete('venue/:id', 'venue.Venue/del'); Route::delete('venue/:id', 'venue.Venue/del');
Route::get('campus_all','venue.Venue/getCampusAll');
})->middleware([ })->middleware([
AdminCheckToken::class, AdminCheckToken::class,
AdminCheckRole::class, AdminCheckRole::class,

86
niucloud/app/model/venue/Venue.php

@ -9,19 +9,21 @@
// | Author: Niucloud Team // | Author: Niucloud Team
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
namespace app\model\campus; namespace app\model\venue;
use core\base\BaseModel; use core\base\BaseModel;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
use think\model\relation\HasMany; use think\model\relation\HasMany;
use think\model\relation\HasOne; use think\model\relation\HasOne;
use app\model\campus\Campus;
/** /**
* 校区模型 * 场地模型
* Class Campus * Class Venue
* @package app\model\campus * @package app\model\venue
*/ */
class Campus extends BaseModel class Venue extends BaseModel
{ {
use SoftDelete; use SoftDelete;
@ -36,13 +38,13 @@ class Campus extends BaseModel
* 模型名称 * 模型名称
* @var string * @var string
*/ */
protected $name = 'campus'; protected $name = 'venue';
/** /**
* 定义软删除标记字段. * 定义软删除标记字段.
* @var string * @var string
*/ */
protected $deleteTime = 'delete_time'; protected $deleteTime = 'deleted_at';
/** /**
* 定义软删除字段的默认值. * 定义软删除字段的默认值.
@ -51,38 +53,86 @@ class Campus extends BaseModel
protected $defaultSoftDelete = 0; protected $defaultSoftDelete = 0;
/** /**
* 搜索器:校区校区名称 * 搜索器:场地校区
* @param $value
* @param $data
*/
public function searchCampusIdAttr($query, $value, $data)
{
if ($value) {
$query->where("campus_id", $value);
}
}
/**
* 搜索器:场地场地名称
* @param $value * @param $value
* @param $data * @param $data
*/ */
public function searchCampusNameAttr($query, $value, $data) public function searchVenueNameAttr($query, $value, $data)
{ {
if ($value) { if ($value) {
$query->where("campus_name", "like", "%".$value."%"); $query->where("venue_name", $value);
} }
} }
/** /**
* 搜索器:校区校区地址 * 搜索器:场地场地可容纳人数上限
* @param $value * @param $value
* @param $data * @param $data
*/ */
public function searchCampusAddressAttr($query, $value, $data) public function searchCapacityAttr($query, $value, $data)
{ {
if ($value) { if ($value) {
$query->where("campus_address", $value); $query->where("capacity", $value);
} }
} }
/** /**
* 搜索器:校区校区状态 * 搜索器:场地场地可用状态
* @param $value * @param $value
* @param $data * @param $data
*/ */
public function searchCampusStatusAttr($query, $value, $data) public function searchAvailabilityStatusAttr($query, $value, $data)
{ {
if ($value) { if ($value) {
$query->where("campus_status", $value); $query->where("availability_status", $value);
}
}
/**
* 搜索器:场地场地可用时间范围类型
* @param $value
* @param $data
*/
public function searchTimeRangeTypeAttr($query, $value, $data)
{
if ($value) {
$query->where("time_range_type", $value);
}
}
/**
* 搜索器:场地创建时间
* @param $value
* @param $data
*/
public function searchCreatedAtAttr($query, $value, $data)
{
if ($value) {
$query->where("created_at", $value);
}
}
/**
* 搜索器:场地修改时间
* @param $value
* @param $data
*/
public function searchUpdatedAtAttr($query, $value, $data)
{
if ($value) {
$query->where("updated_at", $value);
} }
} }
@ -91,4 +141,8 @@ class Campus extends BaseModel
public function campus(){
return $this->hasOne(Campus::class, 'id', 'campus_id')->joinType('left')->withField('campus_name,id')->bind(['campus_id_name'=>'campus_name']);
}
} }

15
niucloud/app/service/admin/campus/CampusService.php

@ -39,7 +39,7 @@ class CampusService extends BaseAdminService
$field = 'id,campus_name,campus_address,campus_preview_image,campus_coordinates,campus_introduction,campus_status,create_time,update_time,delete_time'; $field = 'id,campus_name,campus_address,campus_preview_image,campus_coordinates,campus_introduction,campus_status,create_time,update_time,delete_time';
$order = ''; $order = '';
$search_model = $this->model->withSearch(["campus_name","campus_address","campus_status"], $where)->field($field)->order($order); $search_model = $this->model->withSearch(["campus_name", "campus_address", "campus_status"], $where)->field($field)->order($order);
$list = $this->pageQuery($search_model); $list = $this->pageQuery($search_model);
return $list; return $list;
} }
@ -54,7 +54,8 @@ class CampusService extends BaseAdminService
$field = 'id,campus_name,campus_address,campus_preview_image,campus_coordinates,campus_introduction,campus_status,create_time,update_time,delete_time'; $field = 'id,campus_name,campus_address,campus_preview_image,campus_coordinates,campus_introduction,campus_status,create_time,update_time,delete_time';
$info = $this->model->field($field)->where([['id', "=", $id]])->findOrEmpty()->toArray(); $info = $this->model->field($field)->where([['id', "=", $id]])->findOrEmpty()->toArray();
$info['campus_status'] = strval($info['campus_status']); $info['campus_status'] = strval($info['campus_status']);
$info['campus_coordinates'] = json_decode($info['campus_coordinates'], true);
return $info; return $info;
} }
@ -65,6 +66,10 @@ class CampusService extends BaseAdminService
*/ */
public function add(array $data) public function add(array $data)
{ {
if (is_array($data['campus_coordinates'])) {
$data['campus_address'] = $data['campus_coordinates']['address'];
$data['campus_coordinates'] = json_encode($data['campus_coordinates']);
}
$res = $this->model->create($data); $res = $this->model->create($data);
return $res->id; return $res->id;
@ -78,7 +83,10 @@ class CampusService extends BaseAdminService
*/ */
public function edit(int $id, array $data) public function edit(int $id, array $data)
{ {
if (is_array($data['campus_coordinates'])) {
$data['campus_address'] = $data['campus_coordinates']['address'];
$data['campus_coordinates'] = json_encode($data['campus_coordinates']);
}
$this->model->where([['id', '=', $id]])->update($data); $this->model->where([['id', '=', $id]])->update($data);
return true; return true;
} }
@ -95,6 +103,5 @@ class CampusService extends BaseAdminService
return $res; return $res;
} }
} }

2
niucloud/app/service/admin/student_courses/StudentCoursesService.php

@ -21,7 +21,7 @@ use core\base\BaseAdminService;
* Class CampusService * Class CampusService
* @package app\service\admin\campus * @package app\service\admin\campus
*/ */
class CampusService extends BaseAdminService class StudentCoursesService extends BaseAdminService
{ {
public function __construct() public function __construct()
{ {

14
niucloud/app/service/admin/venue/VenueService.php

@ -12,6 +12,7 @@
namespace app\service\admin\venue; namespace app\service\admin\venue;
use app\model\venue\Venue; use app\model\venue\Venue;
use app\model\campus\Campus;
use core\base\BaseAdminService; use core\base\BaseAdminService;
@ -37,9 +38,9 @@ class VenueService extends BaseAdminService
public function getPage(array $where = []) public function getPage(array $where = [])
{ {
$field = 'id,campus_id,venue_name,capacity,availability_status,time_range_type,time_range_start,time_range_end,fixed_time_ranges,created_at,updated_at,deleted_at'; $field = 'id,campus_id,venue_name,capacity,availability_status,time_range_type,time_range_start,time_range_end,fixed_time_ranges,created_at,updated_at,deleted_at';
$order = 'id desc'; $order = 'updated_at desc';
$search_model = $this->model->withSearch(["id","campus_id","venue_name","capacity","availability_status","time_range_type","time_range_start","time_range_end","fixed_time_ranges"], $where)->field($field)->order($order); $search_model = $this->model->withSearch(["campus_id","venue_name","capacity","availability_status","time_range_type","created_at","updated_at"], $where)->with(['campus'])->field($field)->order($order);
$list = $this->pageQuery($search_model); $list = $this->pageQuery($search_model);
return $list; return $list;
} }
@ -53,7 +54,9 @@ class VenueService extends BaseAdminService
{ {
$field = 'id,campus_id,venue_name,capacity,availability_status,time_range_type,time_range_start,time_range_end,fixed_time_ranges,created_at,updated_at,deleted_at'; $field = 'id,campus_id,venue_name,capacity,availability_status,time_range_type,time_range_start,time_range_end,fixed_time_ranges,created_at,updated_at,deleted_at';
$info = $this->model->field($field)->where([['id', "=", $id]])->findOrEmpty()->toArray(); $info = $this->model->field($field)->where([['id', "=", $id]])->with(['campus'])->findOrEmpty()->toArray();
$info['availability_status'] = strval($info['availability_status']);
$info['time_range_type'] = strval($info['time_range_type']);
return $info; return $info;
} }
@ -95,5 +98,10 @@ class VenueService extends BaseAdminService
} }
public function getCampusAll(){
$campusModel = new Campus();
return $campusModel->select()->toArray();
}
} }

3
niucloud/app/validate/venue/Venue.php

@ -22,7 +22,7 @@ class Venue extends BaseValidate
protected $rule = [ protected $rule = [
'campus_id' => 'require', 'campus_id' => 'require',
'venue_name' => 'require', 'venue_name' => 'require',
'capacity' => 'require', 'capacity' => 'require|between:1,500',
'availability_status' => 'require', 'availability_status' => 'require',
'time_range_type' => 'require', 'time_range_type' => 'require',
]; ];
@ -31,6 +31,7 @@ class Venue extends BaseValidate
'campus_id.require' => ['common_validate.require', ['campus_id']], 'campus_id.require' => ['common_validate.require', ['campus_id']],
'venue_name.require' => ['common_validate.require', ['venue_name']], 'venue_name.require' => ['common_validate.require', ['venue_name']],
'capacity.require' => ['common_validate.require', ['capacity']], 'capacity.require' => ['common_validate.require', ['capacity']],
'capacity.between' => ['common_validate.between', ['capacity','1','500']],
'availability_status.require' => ['common_validate.require', ['availability_status']], 'availability_status.require' => ['common_validate.require', ['availability_status']],
'time_range_type.require' => ['common_validate.require', ['time_range_type']], 'time_range_type.require' => ['common_validate.require', ['time_range_type']],
]; ];

Loading…
Cancel
Save