feat: 完成出差目的地高德地图选择功能
This commit is contained in:
@@ -37,6 +37,7 @@
|
|||||||
"url": "https://gitee.com/KonBAI-Q/ruoyi-flowable-plus.git"
|
"url": "https://gitee.com/KonBAI-Q/ruoyi-flowable-plus.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||||
"@babel/parser": "7.7.4",
|
"@babel/parser": "7.7.4",
|
||||||
"@handsontable/vue": "^15.3.0",
|
"@handsontable/vue": "^15.3.0",
|
||||||
"@jiaminghi/data-view": "^2.10.0",
|
"@jiaminghi/data-view": "^2.10.0",
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
<title>
|
<title>
|
||||||
<%= webpackConfig.name %>
|
<%= webpackConfig.name %>
|
||||||
</title>
|
</title>
|
||||||
|
|
||||||
|
|
||||||
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
|
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
@@ -18,7 +20,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
|
||||||
|
|
||||||
.chromeframe {
|
.chromeframe {
|
||||||
margin: 0.2em 0;
|
margin: 0.2em 0;
|
||||||
|
|||||||
@@ -254,9 +254,4 @@ export function listAssignTask (instId) {
|
|||||||
url: `/hrm/flow/instance/tasks/${instId}`,
|
url: `/hrm/flow/instance/tasks/${instId}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询当前用户的审批历史
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
306
ruoyi-ui/src/components/AmapCitySelect/index.vue
Normal file
306
ruoyi-ui/src/components/AmapCitySelect/index.vue
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
<template>
|
||||||
|
<div class="amap-city-select">
|
||||||
|
<el-input
|
||||||
|
v-model="cityName"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
@focus="openMapDialog"
|
||||||
|
clearable
|
||||||
|
readonly
|
||||||
|
>
|
||||||
|
<i slot="prefix" class="el-icon-location" style="color: #409eff"></i>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
title="选择出差城市"
|
||||||
|
:visible.sync="dialogVisible"
|
||||||
|
width="900px"
|
||||||
|
:append-to-body="true"
|
||||||
|
@opened="onDialogOpened"
|
||||||
|
>
|
||||||
|
<div class="map-selector">
|
||||||
|
<div class="city-sidebar">
|
||||||
|
<div class="search-box">
|
||||||
|
<el-input
|
||||||
|
v-model="searchKeyword"
|
||||||
|
placeholder="输入城市名搜索"
|
||||||
|
size="small"
|
||||||
|
prefix-icon="el-icon-search"
|
||||||
|
@keyup.enter="searchCity"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-button slot="append" @click="searchCity">搜索</el-button>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hot-cities" v-if="!searchKeyword">
|
||||||
|
<div class="section-title">热门城市</div>
|
||||||
|
<div class="city-list">
|
||||||
|
<span
|
||||||
|
v-for="city in hotCities"
|
||||||
|
:key="city"
|
||||||
|
class="city-item"
|
||||||
|
@click="selectCityFromList(city)"
|
||||||
|
>
|
||||||
|
{{ city }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-results" v-else>
|
||||||
|
<div class="section-title">搜索结果</div>
|
||||||
|
<div class="result-list">
|
||||||
|
<div
|
||||||
|
v-for="result in searchResults"
|
||||||
|
:key="result.id"
|
||||||
|
class="result-item"
|
||||||
|
@click="selectCityFromList(result.name)"
|
||||||
|
>
|
||||||
|
<i class="el-icon-location-outline"></i>
|
||||||
|
<span>{{ result.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="searchResults.length === 0" class="no-result">
|
||||||
|
未找到相关城市
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="map-container">
|
||||||
|
<div id="amapContainer" class="amap-wrapper"></div>
|
||||||
|
<div class="map-tip">
|
||||||
|
<i class="el-icon-info"></i> 点击地图上的位置,自动识别城市
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<div class="selected-info" v-if="selectedCity">
|
||||||
|
已选择:<span class="selected-city">{{ selectedCity }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-button @click="closeDialog">取消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmCity" :disabled="!selectedCity">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AMapLoader from '@amap/amap-jsapi-loader'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AmapCitySelect',
|
||||||
|
props: {
|
||||||
|
value: { type: String, default: '' },
|
||||||
|
placeholder: { type: String, default: '请选择出差城市' }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
cityName: '',
|
||||||
|
dialogVisible: false,
|
||||||
|
searchKeyword: '',
|
||||||
|
searchResults: [],
|
||||||
|
selectedCity: '',
|
||||||
|
map: null,
|
||||||
|
geocoder: null,
|
||||||
|
marker: null,
|
||||||
|
AMap: null,
|
||||||
|
hotCities: [
|
||||||
|
'北京市', '上海市', '广州市', '深圳市',
|
||||||
|
'杭州市', '南京市', '成都市', '武汉市',
|
||||||
|
'重庆市', '天津市', '苏州市', '西安市',
|
||||||
|
'石家庄市', '唐山市', '保定市', '邯郸市'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
immediate: true,
|
||||||
|
handler(val) { this.cityName = val || '' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async onDialogOpened() {
|
||||||
|
await this.loadAMap()
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initMap()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadAMap() {
|
||||||
|
if (this.AMap) return this.AMap
|
||||||
|
|
||||||
|
// 【关键修改:必须在此处配置安全密钥】
|
||||||
|
window._AMapSecurityConfig = {
|
||||||
|
securityJsCode: 'a2969c40309b1a3930f78b2a232d0de0', // 你的安全密钥
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.AMap = await AMapLoader.load({
|
||||||
|
key: '27baa1cd7dee515accbcf8534d1a2e0e', // 您的Key
|
||||||
|
version: '2.0',
|
||||||
|
plugins: ['AMap.Geocoder', 'AMap.PlaceSearch'] // 插件在这里被正确加载了
|
||||||
|
})
|
||||||
|
return this.AMap
|
||||||
|
} catch (error) {
|
||||||
|
console.error('高德地图加载失败:', error)
|
||||||
|
this.$message.error('地图加载失败,请刷新重试')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
initMap() {
|
||||||
|
const container = document.getElementById('amapContainer')
|
||||||
|
if (!container) {
|
||||||
|
console.error('地图容器不存在')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.map) {
|
||||||
|
this.map.destroy()
|
||||||
|
this.map = null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.map = new this.AMap.Map('amapContainer', {
|
||||||
|
zoom: 12,
|
||||||
|
center: [116.397428, 39.90923],
|
||||||
|
resizeEnable: true
|
||||||
|
})
|
||||||
|
|
||||||
|
this.geocoder = new this.AMap.Geocoder()
|
||||||
|
|
||||||
|
this.map.on('click', (e) => {
|
||||||
|
const lng = e.lnglat.getLng()
|
||||||
|
const lat = e.lnglat.getLat()
|
||||||
|
this.getCityByLngLat(lng, lat)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getCityByLngLat(lng, lat) {
|
||||||
|
this.geocoder.getAddress([lng, lat], (status, result) => {
|
||||||
|
if (status === 'complete' && result.regeocode) {
|
||||||
|
const addrComp = result.regeocode.addressComponent
|
||||||
|
let city = addrComp.city
|
||||||
|
if (!city || city === '[]') {
|
||||||
|
city = addrComp.province
|
||||||
|
}
|
||||||
|
this.selectedCity = city
|
||||||
|
this.addMarker(lng, lat)
|
||||||
|
this.$message.success(`识别到城市:${city}`)
|
||||||
|
} else {
|
||||||
|
this.$message.warning('无法识别该位置的城市')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
addMarker(lng, lat) {
|
||||||
|
if (this.marker) {
|
||||||
|
this.marker.setMap(null)
|
||||||
|
}
|
||||||
|
this.marker = new this.AMap.Marker({
|
||||||
|
position: [lng, lat],
|
||||||
|
map: this.map
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
openMapDialog() {
|
||||||
|
this.dialogVisible = true
|
||||||
|
this.selectedCity = ''
|
||||||
|
this.searchKeyword = ''
|
||||||
|
this.searchResults = []
|
||||||
|
},
|
||||||
|
|
||||||
|
async searchCity() {
|
||||||
|
if (!this.searchKeyword.trim()) {
|
||||||
|
this.$message.warning('请输入城市名称')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.loadAMap()
|
||||||
|
|
||||||
|
this.geocoder.getLocation(this.searchKeyword, (status, result) => {
|
||||||
|
if (status === 'complete' && result.geocodes && result.geocodes.length) {
|
||||||
|
const cities = []
|
||||||
|
result.geocodes.forEach(item => {
|
||||||
|
const cityName = item.city || item.province
|
||||||
|
if (cityName && !cities.find(c => c.name === cityName)) {
|
||||||
|
cities.push({ id: item.adcode, name: cityName })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.searchResults = cities
|
||||||
|
if (this.searchResults.length === 0) {
|
||||||
|
this.$message.info('未找到相关城市')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.searchResults = []
|
||||||
|
this.$message.info('未找到相关城市')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async selectCityFromList(city) {
|
||||||
|
this.selectedCity = city
|
||||||
|
await this.loadAMap()
|
||||||
|
|
||||||
|
this.geocoder.getLocation(city, (status, result) => {
|
||||||
|
if (status === 'complete' && result.geocodes.length) {
|
||||||
|
const loc = result.geocodes[0].location
|
||||||
|
this.map.setCenter([loc.lng, loc.lat])
|
||||||
|
this.map.setZoom(12)
|
||||||
|
this.addMarker(loc.lng, loc.lat)
|
||||||
|
this.$message.success(`已定位到:${city}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmCity() {
|
||||||
|
if (this.selectedCity) {
|
||||||
|
this.cityName = this.selectedCity
|
||||||
|
this.$emit('input', this.selectedCity)
|
||||||
|
this.closeDialog()
|
||||||
|
this.$message.success(`已选择:${this.selectedCity}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
closeDialog() {
|
||||||
|
this.dialogVisible = false
|
||||||
|
this.selectedCity = ''
|
||||||
|
this.searchKeyword = ''
|
||||||
|
this.searchResults = []
|
||||||
|
if (this.marker) {
|
||||||
|
this.marker.setMap(null)
|
||||||
|
this.marker = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.amap-city-select { width: 100%; }
|
||||||
|
.map-selector { display: flex; gap: 20px; min-height: 450px;
|
||||||
|
.city-sidebar { width: 280px; flex-shrink: 0;
|
||||||
|
.search-box { margin-bottom: 16px; }
|
||||||
|
.section-title { font-size: 14px; font-weight: 500; color: #303133; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #e4e7ed; }
|
||||||
|
.hot-cities .city-list { display: flex; flex-wrap: wrap; gap: 10px;
|
||||||
|
.city-item { padding: 4px 12px; background: #f5f7fa; border-radius: 4px; font-size: 13px; cursor: pointer;
|
||||||
|
&:hover { background: #409eff; color: white; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.search-results .result-list { max-height: 380px; overflow-y: auto;
|
||||||
|
.result-item { padding: 10px 12px; cursor: pointer; display: flex; align-items: center; gap: 8px; border-bottom: 1px solid #f0f0f0;
|
||||||
|
&:hover { background: #ecf5ff; }
|
||||||
|
i { color: #409eff; }
|
||||||
|
}
|
||||||
|
.no-result { padding: 40px 0; text-align: center; color: #909399; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map-container { flex: 1;
|
||||||
|
.amap-wrapper { width: 100%; height: 400px; border: 1px solid #e4e7ed; border-radius: 8px; overflow: hidden; }
|
||||||
|
.map-tip { margin-top: 8px; font-size: 12px; color: #909399; text-align: center; i { margin-right: 4px; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dialog-footer { display: flex; justify-content: space-between; align-items: center;
|
||||||
|
.selected-info { font-size: 13px; color: #606266; .selected-city { color: #409eff; font-weight: 500; } }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -65,8 +65,8 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-form-item label="目的地" prop="destination">
|
<el-form-item label="目的地" prop="destination">
|
||||||
<el-input v-model="form.destination" placeholder="城市/地址/项目现场" />
|
<amap-city-select v-model="form.destination" placeholder="请选择出差城市" />
|
||||||
<div class="hint-text">请填写具体目的地,便于审批人判断出差必要性</div>
|
<div class="hint-text">请通过地图或搜索选择具体城市,便于审批人判断出差必要性</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<div class="block-title">出差说明</div>
|
<div class="block-title">出差说明</div>
|
||||||
@@ -223,13 +223,15 @@ import { ccFlowTask, listFlowNode, listFlowTemplate } from '@/api/hrm/flow'
|
|||||||
import FileUpload from '@/components/FileUpload'
|
import FileUpload from '@/components/FileUpload'
|
||||||
import UserMultiSelect from '@/components/UserSelect/multi.vue'
|
import UserMultiSelect from '@/components/UserSelect/multi.vue'
|
||||||
import UserSelect from '@/components/UserSelect/single.vue'
|
import UserSelect from '@/components/UserSelect/single.vue'
|
||||||
|
import AmapCitySelect from '@/components/AmapCitySelect/index.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HrmTravelRequest',
|
name: 'HrmTravelRequest',
|
||||||
components: {
|
components: {
|
||||||
UserSelect,
|
UserSelect,
|
||||||
FileUpload,
|
FileUpload,
|
||||||
UserMultiSelect
|
UserMultiSelect,
|
||||||
|
AmapCitySelect
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user