Files
ss-crm-manage-web/src/views/SchoolManagement/Place/index.vue
2024-07-26 17:51:35 +08:00

754 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="absolute top-0 left-0 h-full w-full" style="height: calc(100vh - 85px)">
<div id="dialogMap" style="height: 100%"></div>
<!-- 右侧驾校列表 -->
<div class="asider" :class="showSchool ? '' : 'hidden-school'">
<el-card class="box-card" :body-style="{ flex: 1, 'overflow-y': 'scroll', padding: 0 }">
<div style="margin: 10px">
<el-input v-model="searchValue" placeholder="请输入驾校名" />
</div>
<template #header>
<div class="clearfix">
<div class="map-card-title">驾校列表</div>
<el-switch
v-model="showAllSchool"
:active-value="true"
:inactive-value="false"
active-text="隐藏所有"
inactive-text="展示所有"
@change="showAllSchoolChange"
/>
</div>
</template>
<div
v-for="school in schoolList.filter(
(item) => searchValue == undefined || item.schoolName.includes(searchValue)
)"
:key="school.schoolId"
style="margin: 10px"
:class="currentdeptId == school.schoolId ? 'actived-school' : ''"
>
<el-card :body-style="{ padding: '10px' }">
<template #header>
<div class="clearfix">
<div class="map-card-title">{{ school.schoolName }}</div>
<el-switch
v-model="school.showInMap"
class="add-icon"
active-text="展示"
inactive-text="隐藏"
@change="changeSchoolStatus(school)"
/>
</div>
</template>
<el-button @click="handleClickSchool(school)">{{
`数据管理(${getCount(school.schoolId)})`
}}</el-button>
<el-tooltip
content="新增场地"
placement="left"
effect="dark"
v-if="checkPermi(['school:place:add'])"
>
<el-button class="add-place-btn" @click="handleInsertPlace(school.schoolId)">
<Icon icon="ep:plus" />
</el-button>
</el-tooltip>
</el-card>
</div>
</el-card>
<div class="asider-sub">
<el-tooltip
:content="`${showSchool ? '折叠' : '展开'}驾校列表`"
placement="left"
effect="dark"
>
<el-button class="is-circle" @click="toggleSchool">
<Icon class="text-12px" :icon="`ep:d-arrow-${showSchool ? 'right' : 'left'}`" />
</el-button>
</el-tooltip>
</div>
</div>
<!-- 左侧场地弹框 -->
<el-card v-if="placeDialogShow" class="place-dialog" :body-style="{ padding: '10px' }">
<template #header>
<div class="clearfix">
<div class="map-card-title">场地设置</div>
<el-tooltip content="取点" placement="right" effect="dark">
<el-button class="add-icon" @click="isPointing = !isPointing">
<Icon icon="ep:location" />
</el-button>
</el-tooltip>
</div>
</template>
<el-tabs v-model="placeTab">
<el-tab-pane label="场地信息" name="info">
<el-form ref="FormPlace" :model="placeForm" label-width="70px">
<el-form-item label="所属驾校" prop="schoolId">
<el-select v-model="placeForm.schoolId" placeholder="请选择" clearable class="w-full">
<el-option
v-for="dict in schoolList"
:key="dict.schoolId"
:label="dict.schoolName"
:value="dict.schoolId"
/>
</el-select>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="placeForm.name" placeholder="输入名称" />
</el-form-item>
<el-form-item label="旗子颜色" prop="flagColor">
<el-radio-group v-model="placeForm.flagColor">
<el-radio v-for="(item, index) in colorOptions" :key="index" :label="item">
<img :src="flagMap[item]" style="width: 20px" />
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="placeForm.address" placeholder="输入地址" />
</el-form-item>
<el-form-item label="经度" prop="lng">
<el-input v-model="placeForm.lng" placeholder="输入经度" />
</el-form-item>
<el-form-item label="纬度" prop="lat">
<el-input v-model="placeForm.lat" placeholder="输入纬度" />
</el-form-item>
<el-form-item label="所属区域" prop="area">
<el-select v-model="placeForm.area" placeholder="请选择" clearable class="w-full">
<el-option
v-for="dict in areaOptions"
:key="dict.id"
:label="dict.name"
:value="dict.id"
/>
</el-select>
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="placeForm.phone" placeholder="输入电话" />
</el-form-item>
<el-form-item label="负责人" prop="contact">
<el-input v-model="placeForm.contact" placeholder="输入负责人" />
</el-form-item>
<el-form-item label="是否推荐" prop="contact">
<el-radio v-model="placeForm.recommend" :label="true">是</el-radio>
<el-radio v-model="placeForm.recommend" :label="false">否</el-radio>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="描述" name="desc">
<Editor v-model:modelValue="placeForm.introduce" />
</el-tab-pane>
</el-tabs>
<div class="flex items-center justify-center mt-10px">
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button @click="closePlaceDialog">取消</el-button>
</div>
</el-card>
<!-- 底部驾校场地列表 -->
<el-card
:class="placeListDialogShow ? '' : 'hidden-place-list'"
class="place-list-dialog"
:style="{ right: showSchool ? '300px' : '0', top: fullScreenPlaceList ? '0px' : '420px' }"
:body-style="{ padding: '10px', height: 'calc(100% - 52px)' }"
>
<template #header>
<div class="clearfix">
<div class="map-card-title">
{{ placeListDialogTitle }}
<el-input v-model="tableSearch" placeholder="请输入场地名称" clearable />
</div>
<el-tooltip content="全屏" placement="top" effect="dark">
<el-button class="add-icon" @click="fullScreenPlaceList = !fullScreenPlaceList">
<Icon icon="ep:full-screen" />
</el-button>
</el-tooltip>
<el-tooltip content="关闭" placement="top" effect="dark">
<el-button class="add-icon" @click="closePlaceList">
<Icon icon="ep:close" />
</el-button>
</el-tooltip>
</div>
</template>
<el-table
:data="placeTableData"
border
stripe
class="place-table-list"
size="small"
height="100%"
>
<el-table-column label="序号" type="index" fixed="left" width="50" />
<el-table-column prop="name" label="名称" min-width="100" />
<el-table-column prop="phone" label="电话" width="120" />
<el-table-column prop="contact" label="负责人" width="120" />
<el-table-column prop="address" label="地址" min-width="100" />
<el-table-column prop="lng" label="经度" width="110" />
<el-table-column prop="lat" label="纬度" width="110" />
<el-table-column prop="area" label="所属区域" width="110" />
<el-table-column label="启用" width="100">
<template #default="scope">
<el-switch
v-model="scope.row.status"
:active-value="0"
:inactive-value="1"
:disabled="!checkPermi(['school:place:update'])"
@change="changePlaceStatus(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template #default="scope">
<el-tooltip content="编辑" placement="top" effect="dark">
<el-button
type="primary"
style="padding: 4px 8px"
@click="handleEditPlace(scope.row)"
v-hasPermi="['school:place:update']"
>
<Icon icon="ep:edit" />
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<div
v-if="isPointing"
class="map-tip"
:style="{
transform: 'translate3D(' + (tipPostion.x + 15) + 'px,' + (tipPostion.y - 10) + 'px, 0)'
}"
>{{ mapHelpText }}</div
>
<Icon
v-if="isPointing"
icon="ep:s-flag"
class="circle"
:style="{ transform: 'translate3D(' + tipPostion.x + 'px,' + tipPostion.y + 'px, 0)' }"
/>
</div>
</template>
<script setup name="Place">
import * as PlaceApi from '@/api/school/place'
import { getAreaSimpleList } from '@/api/school/setting/area'
import { checkPermi } from '@/utils/permission'
import FlagRed from '@/assets/imgs/flag/flag_red.png'
import FlagYellow from '@/assets/imgs/flag/flag_yellow.png'
import FlagPurple from '@/assets/imgs/flag/flag_purple.png'
import FlagGreen from '@/assets/imgs/flag/flag_green.png'
import FlagBlue from '@/assets/imgs/flag/flag_blue.png'
import FlagBlack from '@/assets/imgs/flag/flag_black.png'
import AMapLoader from '@amap/amap-jsapi-loader'
const message = useMessage() // 消息弹窗
// 地图相关
const dialogMap = ref(null)
const aMap = ref(null)
const geoCoder = ref(null)
const locationMarker = ref(null)
const flagMap = {
red: FlagRed,
yellow: FlagYellow,
purple: FlagPurple,
green: FlagGreen,
blue: FlagBlue,
black: FlagBlack
}
const colorOptions = ['red', 'yellow', 'blue', 'green', 'purple', 'black']
const areaOptions = ref([])
const mapHelpText = ref('')
const tipPostion = ref({})
const placeTab = ref('info')
function initMap() {
AMapLoader.load({
key: '713d839ff505943b0f18e6df45f3b0dc', //设置您的key
version: '2.0',
plugins: ['AMap.Geocoder']
}).then((AMap) => {
aMap.value = AMap
geoCoder.value = new AMap.Geocoder()
dialogMap.value = new AMap.Map('dialogMap', {
zoom: 12,
zooms: [2, 22],
center: [117.283042, 31.86119]
})
locationMarker.value = new AMap.Marker({
icon: FlagRed
})
getPageData()
dialogMap.value.on('click', (ev) => {
if (isPointing.value) {
placeForm.value.lat = ev.lnglat.lat
placeForm.value.lng = ev.lnglat.lng
regeoCode()
locationMarker.value.setPosition([placeForm.value.lng, placeForm.value.lat])
dialogMap.value.add(locationMarker.value)
isPointing.value = false
}
})
dialogMap.value.on('mousemove', (ev) => {
// if (isRanging.value) {
// mapHelpText.value =
// '左键单击选点,双击/右键单击完成选点,再次点击测距按钮可退出测距模式,并清除测距结果'
// tipPostion.value = {
// x: ev.pixel.x,
// y: ev.pixel.y
// }
// } else if (isPointing.value) {
mapHelpText.value = '点击地图添加标注'
tipPostion.value = {
x: ev.pixel.x,
y: ev.pixel.y
}
// }
})
})
}
function regeoCode() {
geoCoder.value.getAddress([placeForm.value.lng, placeForm.value.lat], (status, result) => {
if (status === 'complete' && result.regeocode) {
placeForm.value.address = result.regeocode.formattedAddress
} else {
message.error('根据经纬度查询地址失败')
}
})
}
async function getPageData() {
schoolList.value = []
tableData.value = []
const data = await PlaceApi.getPlaceList({
schoolStatus: 0,
isSearchSchool: true
})
if (data.schoolList) {
schoolList.value = data.schoolList.map((item) => ({ ...item, showInMap: true }))
}
if (data.placeList) {
tableData.value = data.placeList.map((item) => ({ ...item, showInMap: true, schoolShow: true }))
}
if (schoolList.value.length) {
currentdeptId.value = schoolList.value[0].schoolId
createMarkersInMap()
}
}
const searchValue = ref('')
const schoolList = ref([])
const placeListDialogShow = ref(false)
const currentdeptId = ref('')
function changeSchoolStatus(school) {
tableData.value.forEach((item) => {
if (school.schoolId == item.schoolId) {
item.schoolShow = school.showInMap
}
})
resetMarkers()
}
function handleClickSchool(item) {
placeListDialogShow.value = true
placeListDialogTitle.value = `数据管理 [${item.schoolName}]`
currentdeptId.value = item.schoolId
}
// 生成markers
function createMarkersInMap() {
for (let i = 0; i < tableData.value.length; i++) {
const element = tableData.value[i]
if (!element.schoolShow || !element.showInMap || element.status == 1) {
continue
}
const markerIcon = flagMap[element.flagColor || 'red']
const tmpMarker = new aMap.value.Marker({
map: dialogMap.value,
position: [element.lng, element.lat],
icon: markerIcon,
label: {
content: element.name,
direction: 'right'
},
extData: element
})
tmpMarker.on('click', handleClickMarker)
placeMarkerList.value.push(tmpMarker)
}
}
function handleInsertPlace(schoolId) {
placeDialogShow.value = true
dialogMap.value.setDefaultCursor('default')
// isRanging.value = false
placeForm.value = {
lat: undefined,
lng: undefined,
name: undefined,
address: undefined,
introduce: undefined,
phone: undefined,
schoolId: schoolId,
showInMap: true,
flagColor: 'red',
recommend: false
}
}
// 关闭场地弹窗
function closePlaceDialog() {
placeDialogShow.value = false
isPointing.value = false
dialogMap.value.remove(locationMarker.value)
}
const isPointing = ref(false)
function checkPlaceFormValidate() {
const valid = []
if (!placeForm.value.name) {
valid.push('名称')
}
if (!placeForm.value.address) {
valid.push('地址')
}
if (!placeForm.value.lng) {
valid.push('经度')
}
if (!placeForm.value.lat) {
valid.push('纬度')
}
if (!placeForm.value.phone) {
valid.push('电话')
}
if (valid.length == 0) {
return true
} else {
message.error(`请将以下填写完整: ${valid.join(',')}`)
return false
}
}
function handleClickMarker(ev) {
if (!checkPermi(['school:place:update'])) {
return
}
placeForm.value = ev.target.getExtData()
placeDialogShow.value = true
}
async function onSubmit() {
// 保存接口
if (checkPlaceFormValidate()) {
try {
let resp
if (placeForm.value.placeId) {
resp = await PlaceApi.updatePlace(placeForm.value)
} else {
resp = await PlaceApi.createPlace(placeForm.value)
placeForm.value.placeId = resp.data
}
message.success('操作成功')
// 移除选点用 的标记
dialogMap.value.remove(locationMarker.value)
// 根据form创建新marker 并添加到地图上
const tmpMarker = new aMap.value.Marker({
map: dialogMap.value,
position: [placeForm.value.lng, placeForm.value.lat],
icon: flagMap[placeForm.value.flagColor],
label: {
content: placeForm.value.name,
direction: 'right'
},
extData: placeForm.value
})
// 新marker事件
tmpMarker.on('click', handleClickMarker)
// 如果当前选择的marker点存在编辑
// if (selectMarker.value) {
// // 地图上 移除选择的点
// dialogMap.value.remove(selectMarker.value)
// selectMarker.value = null
// }
// 关闭场地弹窗
placeDialogShow.value = false
isPointing.value = false
// 场地列表 移除原列表中操作的场地数据
const tmpArr = tableData.value.filter((item) => item.placeId !== placeForm.value.placeId)
// 新增新的场地
tmpArr.push(placeForm.value)
// 重置场地数组
tableData.value = tmpArr
// 地图marker列表 移除操作的原marker 添加新marker进数组
const tmpArr1 = placeMarkerList.value.filter(
(item) => item.getExtData().placeId !== placeForm.value.placeId
)
tmpArr1.push(tmpMarker)
placeMarkerList.value = tmpArr1
} catch (error) {
console.log(error)
}
}
}
function getCount(schoolId) {
return tableData.value.filter((item) => item.schoolId === schoolId).length
}
const showSchool = ref(true)
const showAllSchool = ref(false)
const fullScreenPlaceList = ref(false)
const placeListDialogTitle = ref('')
const tableSearch = ref('')
const tableData = ref([])
const placeTableData = computed(() => {
if (tableSearch.value) {
return tableData.value.filter(
(dataNews) =>
dataNews.schoolId === currentdeptId.value && dataNews.name.includes(tableSearch.value)
)
}
return tableData.value.filter((dataNews) => dataNews.schoolId === currentdeptId.value)
})
function showAllSchoolChange() {
schoolList.value.forEach((item) => (item.showInMap = !showAllSchool.value))
tableData.value.forEach((item) => (item.schoolShow = !showAllSchool.value))
resetMarkers()
}
function toggleSchool() {
showSchool.value = !showSchool.value
}
function closePlaceList() {
placeListDialogShow.value = false
fullScreenPlaceList.value = false
}
async function changePlaceStatus(row) {
try {
// 修改状态的二次确认
const text = row.status == 0 ? '启用' : '停用'
await message.confirm('确认要"' + text + '""' + row.name + '"场地吗?')
// 发起修改状态
await PlaceApi.updatePlaceStatus(row.placeId, row.status)
message.success('修改成功')
// 刷新列表
// await getPageData()
tableData.value.find((item) => item.placeId == row.placeId).status = row.status
resetMarkers()
} catch {
// 取消后,进行恢复按钮
row.status = row.status == 0 ? 1 : 0
}
}
// const isRanging = ref(false)
const placeForm = ref({
lat: undefined,
lng: undefined,
name: undefined,
address: undefined,
introduce: undefined,
phone: undefined,
flagColor: 'red'
})
// const selectMarker = ref(null)
const placeMarkerList = ref([])
// 编辑场地
function handleEditPlace(item) {
placeDialogShow.value = true
dialogMap.value.setDefaultCursor('default')
// 方法已废弃
// if (selectMarker.value) {
// selectMarker.value.setAnimation('AMAP_ANIMATION_NONE')
// }
// isRanging.value = false
placeForm.value = Object.assign({}, item)
// selectMarker.value = placeMarkerList.value.find(
// (marker) => marker.getExtData().placeId === item.placeId
// )
// 方法已废弃
// selectMarker.value && selectMarker.value.setAnimation('AMAP_ANIMATION_BOUNCE')
dialogMap.value.setCenter([item.lng, item.lat])
}
const placeDialogShow = ref(false)
// 重置markers
function resetMarkers() {
dialogMap.value.clearMap()
createMarkersInMap()
}
function getDictOptions() {
getAreaSimpleList().then((data) => {
areaOptions.value = data
})
}
onMounted(() => {
initMap()
getDictOptions()
})
</script>
<style lang="scss" scoped>
.asider {
position: absolute;
right: 0;
top: 0;
width: 300px;
height: 100%;
transition: 0.3s;
z-index: 9;
}
.box-card {
display: flex;
flex-direction: column;
height: 100%;
}
::v-deep(.el-card__header) {
padding: 10px 15px;
}
.clearfix {
display: flex;
}
.clearfix .map-card-title {
flex: 1;
line-height: 30px;
}
.clearfix .add-icon {
width: auto;
height: 30px;
}
.asider-sub {
position: absolute;
top: 40px;
left: -45px;
padding: 10px 10px 0 0;
z-index: 900;
}
.asider-sub .is-circle {
// display: block;
margin: 0;
padding: 10px;
color: #464646;
border-radius: 0;
font-size: 16px;
box-shadow: 2px 2px 2px rgba(80, 80, 80, 0.67);
}
.mt10 {
margin-top: 10px;
}
.hidden-school {
transform: translateX(300px);
}
.search-body {
position: absolute;
top: 20px;
left: 20px;
width: 400px;
}
::v-deep(.place-dialog) {
position: absolute;
left: 20px;
top: 20px;
width: 350px;
}
.map-tip {
position: absolute;
left: 0;
top: 0;
max-width: 150px;
padding: 5px;
border-radius: 2px;
background: #000;
color: #fff;
opacity: 0.7;
font-size: 12px;
transition-duration: 1ms;
}
.circle {
position: absolute;
left: -8px;
top: -25px;
font-size: 24px;
color: red;
transition-duration: 1ms;
}
.add-place-btn {
float: right;
border: none;
font-size: 16px;
color: #409eff;
}
.place-dialog .el-form .el-form-item {
margin-bottom: 8px;
}
.place-list-dialog {
position: absolute;
top: 420px;
left: 0;
bottom: 0;
transition: 0.3s;
z-index: 151;
background: #e2e5ea;
}
.place-list-dialog .add-icon {
font-size: 18px;
border: none;
}
.hidden-place-list {
transform: translateY(100%);
}
.place-list-dialog .clearfix .map-card-title {
font-weight: bold;
}
.place-list-dialog .clearfix .map-card-title .el-input {
margin-left: 20px;
width: 240px;
}
.actived-school {
border: 2px solid #409eff !important;
}
::v-deep(.el-radio__label) {
vertical-align: middle;
}
</style>