Browse Source

修改地区选择组件

yuhongzhe
王泽彦 11 months ago
parent
commit
1c394c15e5
  1. 4
      admin/components.d.ts
  2. 23
      admin/src/app/api/venue.ts
  3. 10
      admin/src/app/lang/zh-cn/venue.venue.json
  4. 23
      admin/src/app/views/campus/components/campus-edit.vue
  5. 217
      admin/src/app/views/venue/components/venue-edit.vue
  6. 258
      admin/src/app/views/venue/venue.vue
  7. 321
      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. 11
      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']
DiyLink: typeof import('./src/components/diy-link/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']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
@ -44,6 +45,7 @@ declare module '@vue/runtime-core' {
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElResult: typeof import('element-plus/es')['ElResult']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
@ -56,6 +58,8 @@ declare module '@vue/runtime-core' {
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
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']
ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']

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

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

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

@ -1,8 +1,6 @@
{
"id": "场地编号",
"idPlaceholder": "请输入场地编号",
"campusId": "校区ID",
"campusIdPlaceholder": "请输入校区ID",
"campusId":"校区",
"campusIdPlaceholder":"全部",
"venueName":"场地名称",
"venueNamePlaceholder":"请输入场地名称",
"capacity":"场地可容纳人数上限",
@ -17,6 +15,10 @@
"timeRangeEndPlaceholder":"请输入范围类型的结束时间",
"fixedTimeRanges":"固定时间范围类型的可用时间, 存储为JSON数组",
"fixedTimeRangesPlaceholder":"请输入固定时间范围类型的可用时间, 存储为JSON数组",
"createdAt":"创建时间",
"createdAtPlaceholder":"请输入创建时间",
"updatedAt":"修改时间",
"updatedAtPlaceholder":"请输入修改时间",
"addVenue":"添加场地",
"updateVenue":"编辑场地",
"venueDeleteTips":"确定要删除该数据吗?",

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

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

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

@ -1,101 +1,84 @@
<template>
<el-dialog
v-model="showDialog"
:title="formData.id ? t('updateVenue') : t('addVenue')"
width="50%"
class="diy-dialog-wrap"
:destroy-on-close="true"
>
<el-form
:model="formData"
label-width="120px"
ref="formRef"
:rules="formRules"
class="page-form"
v-loading="loading"
>
<el-dialog v-model="showDialog" :title="formData.id ? t('updateVenue') : t('addVenue')" width="50%" class="diy-dialog-wrap" :destroy-on-close="true">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
<el-form-item :label="t('campusId')" prop="campus_id">
<el-input
v-model="formData.campus_id"
clearable
:placeholder="t('campusIdPlaceholder')"
class="input-width"
<el-select class="input-width" v-model="formData.campus_id" clearable :placeholder="t('campusIdPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in campusIdList"
:key="index"
:label="item['campus_name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('venueName')" prop="venue_name">
<el-input
v-model="formData.venue_name"
clearable
:placeholder="t('venueNamePlaceholder')"
class="input-width"
/>
<el-input v-model="formData.venue_name" clearable :placeholder="t('venueNamePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('capacity')" prop="capacity">
<el-input
v-model="formData.capacity"
clearable
:placeholder="t('capacityPlaceholder')"
class="input-width"
/>
<el-input-number v-model="formData.capacity" clearable :placeholder="t('capacityPlaceholder')" class="input-width" :min = "1" max = "500" />
</el-form-item>
<el-form-item :label="t('availabilityStatus')" prop="availability_status">
<el-input
v-model="formData.availability_status"
clearable
:placeholder="t('availabilityStatusPlaceholder')"
class="input-width"
/>
<el-radio-group v-model="formData.availability_status" :placeholder="t('availabilityStatusPlaceholder')">
<el-radio
v-for="(item, index) in availability_statusList"
:key="index" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('timeRangeType')" prop="time_range_type">
<el-input
v-model="formData.time_range_type"
clearable
:placeholder="t('timeRangeTypePlaceholder')"
class="input-width"
/>
<el-radio-group v-model="formData.time_range_type" :placeholder="t('timeRangeTypePlaceholder')">
<el-radio
v-for="(item, index) in time_range_typeList"
:key="index" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('timeRangeStart')">
<el-input
<el-form-item :label="t('timeRangeStart')" class="input-width">
<el-date-picker
class="flex-1 !flex"
v-model="formData.time_range_start"
clearable
:placeholder="t('timeRangeStartPlaceholder')"
class="input-width"
/>
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('timeRangeStartPlaceholder')">
</el-date-picker>
</el-form-item>
<el-form-item :label="t('timeRangeEnd')">
<el-input
<el-form-item :label="t('timeRangeEnd')" class="input-width">
<el-date-picker
class="flex-1 !flex"
v-model="formData.time_range_end"
clearable
:placeholder="t('timeRangeEndPlaceholder')"
class="input-width"
/>
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('timeRangeEndPlaceholder')">
</el-date-picker>
</el-form-item>
<el-form-item :label="t('fixedTimeRanges')">
<el-input
<el-form-item :label="t('fixedTimeRanges')" class="input-width">
<el-date-picker
class="flex-1 !flex"
v-model="formData.fixed_time_ranges"
clearable
:placeholder="t('fixedTimeRangesPlaceholder')"
class="input-width"
/>
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('fixedTimeRangesPlaceholder')">
</el-date-picker>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button
type="primary"
:loading="loading"
@click="confirm(formRef)"
>{{ t('confirm') }}</el-button
>
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{
t('confirm')
}}</el-button>
</span>
</template>
</el-dialog>
@ -106,7 +89,7 @@ import { ref, reactive, computed, watch } from 'vue'
import { useDictionary } from '@/app/api/dict'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { addVenue, editVenue, getVenueInfo } from '@/app/api/venue'
import { addVenue, editVenue, getVenueInfo, getWithCampusList } from '@/app/api/venue'
let showDialog = ref(false)
const loading = ref(false)
@ -134,48 +117,44 @@ const formRules = computed(() => {
return {
campus_id: [
{ required: true, message: t('campusIdPlaceholder'), trigger: 'blur' },
],
]
,
venue_name: [
{ required: true, message: t('venueNamePlaceholder'), trigger: 'blur' },
],
]
,
capacity: [
{ required: true, message: t('capacityPlaceholder'), trigger: 'blur' },
],
{ validator: (rule: any, value: string, callback: any) => { if (value && !/^\d{1,500}$/.test(value)) { callback(new Error(t('generateBetween')))} else { callback() }}},
]
,
availability_status: [
{
required: true,
message: t('availabilityStatusPlaceholder'),
trigger: 'blur',
},
],
{ required: true, message: t('availabilityStatusPlaceholder'), trigger: 'blur' },
]
,
time_range_type: [
{
required: true,
message: t('timeRangeTypePlaceholder'),
trigger: 'blur',
},
],
{ required: true, message: t('timeRangeTypePlaceholder'), trigger: 'blur' },
]
,
time_range_start: [
{
required: true,
message: t('timeRangeStartPlaceholder'),
trigger: 'blur',
},
],
{ required: true, message: t('timeRangeStartPlaceholder'), trigger: 'blur' },
]
,
time_range_end: [
{
required: true,
message: t('timeRangeEndPlaceholder'),
trigger: 'blur',
},
],
{ required: true, message: t('timeRangeEndPlaceholder'), trigger: 'blur' },
]
,
fixed_time_ranges: [
{
required: true,
message: t('fixedTimeRangesPlaceholder'),
trigger: 'blur',
},
],
{ required: true, message: t('fixedTimeRangesPlaceholder'), trigger: 'blur' },
]
,
}
})
@ -195,13 +174,11 @@ const confirm = async (formEl: FormInstance | undefined) => {
let data = formData
save(data)
.then((res) => {
save(data).then(res => {
loading.value = false
showDialog.value = false
emit('complete')
})
.catch((err) => {
}).catch(err => {
loading.value = false
})
}
@ -209,14 +186,31 @@ const confirm = async (formEl: FormInstance | undefined) => {
}
//
let availability_statusList = ref([])
const availability_statusDictList = async () => {
availability_statusList.value = await (await useDictionary('SiteStatus')).data.dictionary
}
availability_statusDictList();
watch(() => availability_statusList.value, () => { formData.availability_status = availability_statusList.value[0].value })
let time_range_typeList = ref([])
const time_range_typeDictList = async () => {
time_range_typeList.value = await (await useDictionary('ALLOTTED_TIME')).data.dictionary
}
time_range_typeDictList();
watch(() => time_range_typeList.value, () => { formData.time_range_type = time_range_typeList.value[0].value })
const campusIdList = ref([] as any[])
const setCampusIdList = async () => {
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
if (data)
Object.keys(formData).forEach((key: string) => {
if (data) Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
}
@ -234,12 +228,7 @@ const mobileVerify = (rule: any, value: any, callback: any) => {
//
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(
value
)
) {
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()
@ -266,7 +255,7 @@ const numberVerify = (rule: any, value: any, callback: any) => {
defineExpose({
showDialog,
setFormData,
setFormData
})
</script>

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

@ -1,6 +1,7 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center">
<span class="text-lg">{{pageName}}</span>
<el-button type="primary" @click="addEvent">
@ -8,167 +9,109 @@
</el-button>
</div>
<el-card
class="box-card !border-none my-[10px] table-search-wrap"
shadow="never"
>
<el-form
:inline="true"
:model="venueTable.searchParam"
ref="searchFormRef"
>
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="venueTable.searchParam" ref="searchFormRef">
<el-form-item :label="t('campusId')" prop="campus_id">
<el-input
v-model="venueTable.searchParam.campus_id"
:placeholder="t('campusIdPlaceholder')"
/>
<el-select class="w-[280px]" v-model="venueTable.searchParam.campus_id" clearable :placeholder="t('campusIdPlaceholder')">
<el-option
v-for="(item, index) in campusIdList"
:key="index"
:label="item['campus_name']"
:value="item['id']"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('venueName')" prop="venue_name">
<el-input
v-model="venueTable.searchParam.venue_name"
:placeholder="t('venueNamePlaceholder')"
/>
</el-form-item>
<el-form-item :label="t('capacity')" prop="capacity">
<el-input
v-model="venueTable.searchParam.capacity"
:placeholder="t('capacityPlaceholder')"
/>
<el-input v-model="venueTable.searchParam.venue_name" :placeholder="t('venueNamePlaceholder')" />
</el-form-item>
<el-form-item
:label="t('availabilityStatus')"
prop="availability_status"
>
<el-input
v-model="venueTable.searchParam.availability_status"
:placeholder="t('availabilityStatusPlaceholder')"
<el-form-item :label="t('availabilityStatus')" prop="availability_status">
<el-select class="w-[280px]" v-model="venueTable.searchParam.availability_status" clearable :placeholder="t('availabilityStatusPlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in availability_statusList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('timeRangeType')" prop="time_range_type">
<el-input
v-model="venueTable.searchParam.time_range_type"
:placeholder="t('timeRangeTypePlaceholder')"
/>
<el-select class="w-[280px]" v-model="venueTable.searchParam.time_range_type" clearable :placeholder="t('timeRangeTypePlaceholder')">
<el-option label="全部" value=""></el-option>
<el-option
v-for="(item, index) in time_range_typeList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('timeRangeStart')" prop="time_range_start">
<el-input
v-model="venueTable.searchParam.time_range_start"
:placeholder="t('timeRangeStartPlaceholder')"
/>
</el-form-item>
<el-form-item :label="t('timeRangeEnd')" prop="time_range_end">
<el-input
v-model="venueTable.searchParam.time_range_end"
:placeholder="t('timeRangeEndPlaceholder')"
/>
<el-form-item :label="t('createdAt')" prop="created_at">
<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 :label="t('fixedTimeRanges')" prop="fixed_time_ranges">
<el-input
v-model="venueTable.searchParam.fixed_time_ranges"
:placeholder="t('fixedTimeRangesPlaceholder')"
/>
<el-form-item :label="t('updatedAt')" prop="updated_at">
<el-date-picker v-model="venueTable.searchParam.updated_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-button type="primary" @click="loadVenueList()">{{
t('search')
}}</el-button>
<el-button @click="resetForm(searchFormRef)">{{
t('reset')
}}</el-button>
<el-button type="primary" @click="loadVenueList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[10px]">
<el-table
:data="venueTable.data"
size="large"
v-loading="venueTable.loading"
>
<el-table :data="venueTable.data" size="large" v-loading="venueTable.loading">
<template #empty>
<span>{{ !venueTable.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column
prop="campus_id"
:label="t('campusId')"
min-width="120"
:show-overflow-tooltip="true"
/>
<el-table-column prop="campus_id_name" :label="t('campusId')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column
prop="venue_name"
:label="t('venueName')"
min-width="120"
:show-overflow-tooltip="true"
/>
<el-table-column prop="venue_name" :label="t('venueName')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column
prop="capacity"
:label="t('capacity')"
min-width="120"
:show-overflow-tooltip="true"
/>
<el-table-column prop="capacity" :label="t('capacity')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column
prop="availability_status"
:label="t('availabilityStatus')"
min-width="120"
:show-overflow-tooltip="true"
/>
<el-table-column
prop="time_range_type"
:label="t('timeRangeType')"
min-width="120"
:show-overflow-tooltip="true"
/>
<el-table-column :label="t('availabilityStatus')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in availability_statusList">
<div v-if="item.value == row.availability_status">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column
prop="time_range_start"
:label="t('timeRangeStart')"
min-width="120"
:show-overflow-tooltip="true"
/>
<el-table-column :label="t('timeRangeType')" min-width="180" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
<div v-for="(item, index) in time_range_typeList">
<div v-if="item.value == row.time_range_type">{{ item.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column
prop="time_range_end"
:label="t('timeRangeEnd')"
min-width="120"
:show-overflow-tooltip="true"
/>
<el-table-column prop="created_at" :label="t('createdAt')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column
prop="fixed_time_ranges"
:label="t('fixedTimeRanges')"
min-width="120"
:show-overflow-tooltip="true"
/>
<el-table-column prop="updated_at" :label="t('updatedAt')" min-width="120" :show-overflow-tooltip="true"/>
<el-table-column
:label="t('operation')"
fixed="right"
min-width="120"
>
<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-button type="primary" link @click="deleteEvent(row.id)">{{
t('delete')
}}</el-button>
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination
v-model:current-page="venueTable.page"
v-model:page-size="venueTable.limit"
layout="total, sizes, prev, pager, next, jumper"
:total="venueTable.total"
@size-change="loadVenueList()"
@current-change="loadVenueList"
/>
<el-pagination v-model:current-page="venueTable.page" v-model:page-size="venueTable.limit"
layout="total, sizes, prev, pager, next, jumper" :total="venueTable.total"
@size-change="loadVenueList()" @current-change="loadVenueList" />
</div>
</div>
@ -181,13 +124,13 @@
import { reactive, ref, watch } from 'vue'
import { t } from '@/lang'
import { useDictionary } from '@/app/api/dict'
import { getVenueList, deleteVenue } from '@/app/api/venue'
import { getVenueList, deleteVenue, getWithCampusList } from '@/app/api/venue'
import { img } from '@/utils/common'
import { ElMessageBox,FormInstance } from 'element-plus'
import Edit from '@/app/views/venue/components/venue-edit.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const pageName = route.meta.title
const pageName = route.meta.title;
let venueTable = reactive({
page: 1,
@ -196,15 +139,14 @@ let venueTable = reactive({
loading: true,
data: [],
searchParam:{
campus_id: '',
venue_name: '',
capacity: '',
availability_status: '',
time_range_type: '',
time_range_start: '',
time_range_end: '',
fixed_time_ranges: '',
},
"campus_id":"",
"venue_name":"",
"capacity":"",
"availability_status":"",
"time_range_type":"",
"created_at":"",
"updated_at":""
}
})
const searchFormRef = ref<FormInstance>()
@ -213,6 +155,16 @@ const searchFormRef = ref<FormInstance>()
const selectData = ref<any[]>([])
//
const availability_statusList = ref([] as any[])
const availability_statusDictList = async () => {
availability_statusList.value = await (await useDictionary('SiteStatus')).data.dictionary
}
availability_statusDictList();
const time_range_typeList = ref([] as any[])
const time_range_typeDictList = async () => {
time_range_typeList.value = await (await useDictionary('ALLOTTED_TIME')).data.dictionary
}
time_range_typeDictList();
/**
* 获取场地列表
@ -224,14 +176,12 @@ const loadVenueList = (page: number = 1) => {
getVenueList({
page: venueTable.page,
limit: venueTable.limit,
...venueTable.searchParam,
})
.then((res) => {
...venueTable.searchParam
}).then(res => {
venueTable.loading = false
venueTable.data = res.data.data
venueTable.total = res.data.total
})
.catch(() => {
}).catch(() => {
venueTable.loading = false
})
}
@ -260,19 +210,27 @@ const editEvent = (data: any) => {
* 删除场地
*/
const deleteEvent = (id: number) => {
ElMessageBox.confirm(t('venueDeleteTips'), t('warning'), {
ElMessageBox.confirm(t('venueDeleteTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
}).then(() => {
deleteVenue(id)
.then(() => {
}
).then(() => {
deleteVenue(id).then(() => {
loadVenueList()
}).catch(() => {
})
.catch(() => {})
})
}
const campusIdList = ref([])
const setCampusIdList = async () => {
campusIdList.value = await (await getWithCampusList({})).data
}
setCampusIdList()
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()

321
admin/src/components/TencentMapPicker.vue

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

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

@ -33,9 +33,8 @@ class Venue extends BaseAdminController
["capacity",""],
["availability_status",""],
["time_range_type",""],
["time_range_start",""],
["time_range_end",""],
["fixed_time_ranges",""]
["created_at",""],
["updated_at",""]
]);
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\AdminCheckToken;
use app\adminapi\middleware\AdminLog;
// USER_CODE_BEGIN -- venue
Route::group('venue', function () {
@ -29,6 +30,8 @@ Route::group('venue', function () {
//删除场地
Route::delete('venue/:id', 'venue.Venue/del');
Route::get('campus_all','venue.Venue/getCampusAll');
})->middleware([
AdminCheckToken::class,
AdminCheckRole::class,

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

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

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

@ -55,6 +55,7 @@ class CampusService extends BaseAdminService
$info = $this->model->field($field)->where([['id', "=", $id]])->findOrEmpty()->toArray();
$info['campus_status'] = strval($info['campus_status']);
$info['campus_coordinates'] = json_decode($info['campus_coordinates'], true);
return $info;
}
@ -65,6 +66,10 @@ class CampusService extends BaseAdminService
*/
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);
return $res->id;
@ -78,7 +83,10 @@ class CampusService extends BaseAdminService
*/
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);
return true;
}
@ -96,5 +104,4 @@ class CampusService extends BaseAdminService
}
}

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

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

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

@ -12,6 +12,7 @@
namespace app\service\admin\venue;
use app\model\venue\Venue;
use app\model\campus\Campus;
use core\base\BaseAdminService;
@ -37,9 +38,9 @@ class VenueService extends BaseAdminService
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';
$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);
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';
$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;
}
@ -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 = [
'campus_id' => 'require',
'venue_name' => 'require',
'capacity' => 'require',
'capacity' => 'require|between:1,500',
'availability_status' => 'require',
'time_range_type' => 'require',
];
@ -31,6 +31,7 @@ class Venue extends BaseValidate
'campus_id.require' => ['common_validate.require', ['campus_id']],
'venue_name.require' => ['common_validate.require', ['venue_name']],
'capacity.require' => ['common_validate.require', ['capacity']],
'capacity.between' => ['common_validate.between', ['capacity','1','500']],
'availability_status.require' => ['common_validate.require', ['availability_status']],
'time_range_type.require' => ['common_validate.require', ['time_range_type']],
];

Loading…
Cancel
Save