feat(geolocation): 添加工作地点自动获取功能
- 在项目报工页面中新增工作地点自动获取功能,用户可通过点击按钮重新获取定位。 - 添加了工作地点加载状态和错误提示,提升用户体验。 - 优化了相关方法以处理地理位置获取的异步操作。
This commit is contained in:
@@ -11,6 +11,7 @@ import com.ruoyi.hrm.domain.bo.HrmTravelReqBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmTravelReqVo;
|
||||
import com.ruoyi.hrm.service.IHrmTravelReqService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -31,6 +32,12 @@ public class HrmTravelReqController extends BaseController {
|
||||
private final IHrmTravelReqService service;
|
||||
private final IHrmTravelReqService hrmTravelReqService;
|
||||
|
||||
@Value("${fad.amap.webkey}")
|
||||
private String amapKey;
|
||||
|
||||
@Value("${fad.amap.securitykey}")
|
||||
private String amapSecurityKey;
|
||||
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<HrmTravelReqVo> list(HrmTravelReqBo bo, PageQuery pageQuery) {
|
||||
return service.queryPageList(bo, pageQuery);
|
||||
@@ -63,6 +70,16 @@ public class HrmTravelReqController extends BaseController {
|
||||
return toAjax(hrmTravelReqService.earlyEnd(bizId));
|
||||
}
|
||||
|
||||
@GetMapping("/amapKey")
|
||||
public R<String> getAmapKey() {
|
||||
return R.ok(amapKey);
|
||||
}
|
||||
|
||||
@GetMapping("/amapSecurityKey")
|
||||
public R<String> getAmapSecurityKey() {
|
||||
return R.ok(amapSecurityKey);
|
||||
}
|
||||
|
||||
@GetMapping("/all")
|
||||
public R<List<HrmTravelReqVo>> all(HrmTravelReqBo bo) {
|
||||
return R.ok(service.queryList(bo));
|
||||
|
||||
@@ -13,10 +13,12 @@ import com.ruoyi.hrm.domain.*;
|
||||
import com.ruoyi.hrm.domain.bo.HrmFlowTaskBo;
|
||||
import com.ruoyi.hrm.domain.bo.HrmSealStampBo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmFlowTaskVo;
|
||||
import com.ruoyi.hrm.domain.vo.HrmEmployeeVo;
|
||||
import com.ruoyi.hrm.mapper.*;
|
||||
import com.ruoyi.hrm.service.IHrmFlowTaskService;
|
||||
import com.ruoyi.hrm.service.IHrmSealReqService;
|
||||
import com.ruoyi.hrm.service.IHrmFlowCcService;
|
||||
import com.ruoyi.hrm.service.IHrmEmployeeService;
|
||||
import com.ruoyi.hrm.domain.bo.HrmFlowCcBo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -47,6 +49,7 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
private final HrmAppropriationReqMapper appropriationReqMapper;
|
||||
private final ObjectMapper objectMapper; // Spring Boot 默认提供
|
||||
private final UserService userService;
|
||||
private final IHrmEmployeeService employeeService;
|
||||
|
||||
@Override
|
||||
public HrmFlowTaskVo queryById(Long taskId) {
|
||||
@@ -74,7 +77,7 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
return tasks;
|
||||
}
|
||||
|
||||
private void fillBizData(List<HrmFlowTaskVo> tasks) {
|
||||
private void fillBizData(List<HrmFlowTaskVo> tasks) {
|
||||
// 1. 按 bizType 分组,并收集 bizId
|
||||
Map<String, List<Long>> bizIdsByType = tasks.stream()
|
||||
.filter(t -> t.getBizType() != null && t.getBizId() != null)
|
||||
@@ -90,19 +93,39 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
if (bizIds.isEmpty()) return;
|
||||
switch (bizType) {
|
||||
case "leave":
|
||||
leaveReqMapper.selectBatchIds(bizIds).forEach(d -> bizDataMap.put("leave_" + d.getBizId(), d));
|
||||
leaveReqMapper.selectBatchIds(bizIds).forEach(d -> {
|
||||
Map<String, Object> dataMap = objectMapper.convertValue(d, Map.class);
|
||||
fillEmpName(dataMap, d.getEmpId());
|
||||
bizDataMap.put("leave_" + d.getBizId(), dataMap);
|
||||
});
|
||||
break;
|
||||
case "travel":
|
||||
travelReqMapper.selectBatchIds(bizIds).forEach(d -> bizDataMap.put("travel_" + d.getBizId(), d));
|
||||
travelReqMapper.selectBatchIds(bizIds).forEach(d -> {
|
||||
Map<String, Object> dataMap = objectMapper.convertValue(d, Map.class);
|
||||
fillEmpName(dataMap, d.getEmpId());
|
||||
bizDataMap.put("travel_" + d.getBizId(), dataMap);
|
||||
});
|
||||
break;
|
||||
case "seal":
|
||||
sealReqMapper.selectBatchIds(bizIds).forEach(d -> bizDataMap.put("seal_" + d.getBizId(), d));
|
||||
sealReqMapper.selectBatchIds(bizIds).forEach(d -> {
|
||||
Map<String, Object> dataMap = objectMapper.convertValue(d, Map.class);
|
||||
fillEmpName(dataMap, d.getEmpId());
|
||||
bizDataMap.put("seal_" + d.getBizId(), dataMap);
|
||||
});
|
||||
break;
|
||||
case "reimburse":
|
||||
reimburseReqMapper.selectBatchIds(bizIds).forEach(d -> bizDataMap.put("reimburse_" + d.getBizId(), d));
|
||||
reimburseReqMapper.selectBatchIds(bizIds).forEach(d -> {
|
||||
Map<String, Object> dataMap = objectMapper.convertValue(d, Map.class);
|
||||
fillEmpName(dataMap, d.getEmpId());
|
||||
bizDataMap.put("reimburse_" + d.getBizId(), dataMap);
|
||||
});
|
||||
break;
|
||||
case "appropriation":
|
||||
appropriationReqMapper.selectBatchIds(bizIds).forEach(d -> bizDataMap.put("appropriation_" + d.getBizId(), d));
|
||||
appropriationReqMapper.selectBatchIds(bizIds).forEach(d -> {
|
||||
Map<String, Object> dataMap = objectMapper.convertValue(d, Map.class);
|
||||
fillEmpName(dataMap, d.getEmpId());
|
||||
bizDataMap.put("appropriation_" + d.getBizId(), dataMap);
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -112,13 +135,20 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
String key = task.getBizType() + "_" + task.getBizId();
|
||||
Object data = bizDataMap.get(key);
|
||||
if (data != null) {
|
||||
// 将实体对象转换为 Map<String, Object>,方便前端使用
|
||||
Map<String, Object> dataMap = objectMapper.convertValue(data, Map.class);
|
||||
task.setBizData(dataMap);
|
||||
task.setBizData((Map<String, Object>) data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fillEmpName(Map<String, Object> dataMap, Long empId) {
|
||||
if (empId != null) {
|
||||
HrmEmployeeVo emp = employeeService.queryById(empId);
|
||||
if (emp != null && emp.getEmpName() != null) {
|
||||
dataMap.put("empName", emp.getEmpName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -416,6 +446,11 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
||||
|
||||
if (result.getRecords() != null && !result.getRecords().isEmpty()) {
|
||||
fillBizData(result.getRecords());
|
||||
result.getRecords().forEach(task -> {
|
||||
if (task.getAssigneeUserId() != null) {
|
||||
task.setAssigneeNickName(userService.selectNickNameById(task.getAssigneeUserId()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return TableDataInfo.build(result);
|
||||
|
||||
@@ -51,12 +51,13 @@
|
||||
<sql id="selectVo">
|
||||
SELECT
|
||||
e.emp_id, e.user_id, e.emp_no, e.emp_name, e.gender, e.mobile, e.email, e.id_no,
|
||||
e.hire_date, e.employment_type, e.status, e.dept_id, e.post_id, e.remark,
|
||||
e.hire_date, e.employment_type, e.status, su.dept_id, e.post_id, e.remark,
|
||||
e.create_by, e.create_time, e.update_by, e.update_time,
|
||||
d.dept_name,
|
||||
p.post_name
|
||||
FROM hrm_employee e
|
||||
LEFT JOIN sys_dept d ON e.dept_id = d.dept_id AND d.del_flag = '0'
|
||||
left join sys_user su on su.user_id = e.user_id
|
||||
LEFT JOIN sys_dept d ON su.dept_id = d.dept_id AND d.del_flag = 0
|
||||
LEFT JOIN sys_post p ON e.post_id = p.post_id
|
||||
WHERE e.del_flag = 0
|
||||
</sql>
|
||||
|
||||
@@ -319,8 +319,12 @@ flowable:
|
||||
check-process-definitions: false
|
||||
# 关闭历史任务定时任务job
|
||||
async-history-executor-activate: false
|
||||
|
||||
|
||||
fad:
|
||||
amap:
|
||||
key: 978ae5bc551f57d172d3e397af5a6f67
|
||||
# 留作后端接口调用(服务 API)的 Key
|
||||
key: 978ae5bc551f57d172d3e397af5a6f67
|
||||
# 新增的前端 Web 端使用的 Key 和安全密钥
|
||||
webKey: 34bf20d1db5b183558b9bb85d6eed783
|
||||
securityKey: 6f9171724396deb5f8c42ef256b3cbc5
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"url": "https://gitee.com/KonBAI-Q/ruoyi-flowable-plus.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@babel/parser": "7.7.4",
|
||||
"@handsontable/vue": "^15.3.0",
|
||||
"@jiaminghi/data-view": "^2.10.0",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<title>
|
||||
<%= webpackConfig.name %>
|
||||
</title>
|
||||
|
||||
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
|
||||
<style>
|
||||
html,
|
||||
@@ -18,7 +19,7 @@
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
|
||||
.chromeframe {
|
||||
margin: 0.2em 0;
|
||||
@@ -216,4 +217,4 @@
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -254,9 +254,4 @@ export function listAssignTask (instId) {
|
||||
url: `/hrm/flow/instance/tasks/${instId}`,
|
||||
method: 'get'
|
||||
})
|
||||
|
||||
/**
|
||||
* 查询当前用户的审批历史
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
@@ -52,3 +52,16 @@ export function earlyEndTravel(bizId) {
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
export function getAmapKey() {
|
||||
return request({
|
||||
url: '/hrm/travel/amapKey',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
export function getAmapSecurityKey() {
|
||||
return request({
|
||||
url: '/hrm/travel/amapSecurityKey',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
371
ruoyi-ui/src/components/AmapCitySelect/index.vue
Normal file
371
ruoyi-ui/src/components/AmapCitySelect/index.vue
Normal file
@@ -0,0 +1,371 @@
|
||||
<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.native="searchLocation"
|
||||
clearable
|
||||
@clear="searchKeyword = ''"
|
||||
>
|
||||
<el-button slot="append" @click="searchLocation">搜索</el-button>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<!-- 热门城市模块 -->
|
||||
<div class="hot-cities" v-if="!searchKeyword || searchResults.length === 0">
|
||||
<div class="section-title">热门城市</div>
|
||||
<div class="city-list">
|
||||
<span
|
||||
v-for="city in hotCities"
|
||||
:key="city"
|
||||
class="city-item"
|
||||
@click="selectHotCity(city)"
|
||||
>
|
||||
{{ city }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索结果模块 -->
|
||||
<div class="search-results" v-if="searchResults.length > 0">
|
||||
<div class="section-title">搜索结果</div>
|
||||
<div class="result-list">
|
||||
<div
|
||||
v-for="(result, index) in searchResults"
|
||||
:key="index"
|
||||
class="result-item"
|
||||
@click="selectLocation(result)"
|
||||
>
|
||||
<i class="el-icon-location-outline"></i>
|
||||
<div class="result-info">
|
||||
<div class="poi-name">{{ result.name }}</div>
|
||||
<div class="poi-address">{{ result.address }}</div>
|
||||
</div>
|
||||
</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>
|
||||
<span v-if="selectedPoi" style="margin-left: 10px; color: #909399; font-size: 12px;">
|
||||
( {{ selectedPoi }} )
|
||||
</span>
|
||||
</div>
|
||||
<div v-else></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'
|
||||
import { getAmapKey, getAmapSecurityKey } from '@/api/hrm/travel'
|
||||
|
||||
export default {
|
||||
name: 'AmapCitySelect',
|
||||
props: {
|
||||
value: { type: String, default: '' },
|
||||
placeholder: { type: String, default: '请选择出差地点' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cityName: '',
|
||||
dialogVisible: false,
|
||||
searchKeyword: '',
|
||||
searchResults: [],
|
||||
selectedCity: '', // 最终表单需要的城市名
|
||||
selectedPoi: '', // 具体的地点名(仅用于展示)
|
||||
map: null,
|
||||
geocoder: null,
|
||||
placeSearch: null, // 新增地点搜索对象
|
||||
marker: null,
|
||||
AMap: null,
|
||||
hotCities: [
|
||||
'北京市', '上海市', '广州市', '深圳市',
|
||||
'杭州市', '南京市', '成都市', '武汉市',
|
||||
'重庆市', '天津市', '苏州市', '西安市'
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) { this.cityName = val || '' }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onDialogOpened() {
|
||||
const AMap = await this.loadAMap()
|
||||
if (!AMap) return
|
||||
this.$nextTick(() => {
|
||||
this.initMap()
|
||||
})
|
||||
},
|
||||
|
||||
async loadAMap() {
|
||||
if (this.AMap && window._AMapSecurityConfig) return this.AMap
|
||||
try {
|
||||
const [keyRes, securityRes] = await Promise.all([getAmapKey(), getAmapSecurityKey()])
|
||||
const amapkey = keyRes.data || keyRes.msg
|
||||
const securityKey = securityRes.data || securityRes.msg
|
||||
|
||||
if (!amapkey) throw new Error('未获取到高德地图 Key')
|
||||
|
||||
window._AMapSecurityConfig = { securityJsCode: securityKey }
|
||||
|
||||
this.AMap = await AMapLoader.load({
|
||||
key: amapkey,
|
||||
version: '2.0',
|
||||
// 加入 PlaceSearch 插件
|
||||
plugins: ['AMap.Geocoder', 'AMap.PlaceSearch']
|
||||
})
|
||||
return this.AMap
|
||||
} catch (error) {
|
||||
console.error('地图加载失败:', error)
|
||||
this.$message.error('地图加载失败,请重试')
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
initMap() {
|
||||
const container = document.getElementById('amapContainer')
|
||||
if (!container) {
|
||||
setTimeout(() => this.initMap(), 100)
|
||||
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.placeSearch = new this.AMap.PlaceSearch({
|
||||
pageSize: 15, // 单页显示结果条数
|
||||
pageIndex: 1, // 页码
|
||||
autoFitView: false // 禁用自动调整视图,我们自己控制
|
||||
})
|
||||
|
||||
// 监听地图点击
|
||||
this.map.on('click', (e) => {
|
||||
const lng = e.lnglat.getLng()
|
||||
const lat = e.lnglat.getLat()
|
||||
this.selectedPoi = '地图选点'
|
||||
this.getCityByLngLat(lng, lat, true)
|
||||
})
|
||||
},
|
||||
|
||||
// 通过经纬度逆解析城市
|
||||
getCityByLngLat(lng, lat, setCenter = false) {
|
||||
this.geocoder.getAddress([lng, lat], (status, result) => {
|
||||
if (status === 'complete' && result.regeocode) {
|
||||
const addrComp = result.regeocode.addressComponent
|
||||
let city = addrComp.city
|
||||
// 直辖市的 city 可能是空的,取 province
|
||||
if (!city || city === '[]' || city.length === 0) {
|
||||
city = addrComp.province
|
||||
}
|
||||
this.selectedCity = city
|
||||
this.addMarker(lng, lat)
|
||||
if(setCenter) {
|
||||
this.map.setCenter([lng, lat])
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 在地图上打点
|
||||
addMarker(lng, lat) {
|
||||
if (this.marker) {
|
||||
this.marker.setMap(null)
|
||||
}
|
||||
this.marker = new this.AMap.Marker({
|
||||
position: [lng, lat],
|
||||
map: this.map,
|
||||
animation: 'AMAP_ANIMATION_DROP' // 加上掉落动画
|
||||
})
|
||||
},
|
||||
|
||||
openMapDialog() {
|
||||
this.dialogVisible = true
|
||||
this.selectedCity = this.value || ''
|
||||
this.selectedPoi = ''
|
||||
this.searchKeyword = ''
|
||||
this.searchResults = []
|
||||
},
|
||||
|
||||
// 搜索地点核心方法
|
||||
async searchLocation() {
|
||||
if (!this.searchKeyword.trim()) {
|
||||
this.searchResults = []
|
||||
return
|
||||
}
|
||||
|
||||
await this.loadAMap()
|
||||
|
||||
// 使用 PlaceSearch 搜索具体地点
|
||||
this.placeSearch.search(this.searchKeyword, (status, result) => {
|
||||
if (status === 'complete' && result.info === 'OK') {
|
||||
// 提取返回的 POI 列表
|
||||
const pois = result.poiList.pois
|
||||
this.searchResults = pois.map(poi => ({
|
||||
id: poi.id,
|
||||
name: poi.name,
|
||||
address: poi.address && typeof poi.address === 'string' ? poi.address : poi.adname,
|
||||
location: poi.location, // 包含 lng/lat 的对象
|
||||
city: poi.cityname || poi.pname // 城市名
|
||||
}))
|
||||
} else {
|
||||
this.searchResults = []
|
||||
this.$message.info('未找到相关地点,请尝试更换关键词')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 选中左侧搜索结果列表的具体地点
|
||||
selectLocation(poi) {
|
||||
if (!poi.location) {
|
||||
this.$message.warning('该地点缺少坐标信息')
|
||||
return
|
||||
}
|
||||
|
||||
const lng = poi.location.lng
|
||||
const lat = poi.location.lat
|
||||
|
||||
// 设置地图中心点并放大(层级15看街道)
|
||||
this.map.setZoomAndCenter(15, [lng, lat])
|
||||
this.addMarker(lng, lat)
|
||||
|
||||
// 更新选择数据
|
||||
this.selectedCity = poi.city
|
||||
this.selectedPoi = poi.name
|
||||
this.$message.success(`已定位到:${poi.name}`)
|
||||
},
|
||||
|
||||
// 选中热门城市
|
||||
async selectHotCity(city) {
|
||||
await this.loadAMap()
|
||||
this.selectedCity = city
|
||||
this.selectedPoi = city
|
||||
|
||||
// 获取城市中心点坐标
|
||||
this.geocoder.getLocation(city, (status, result) => {
|
||||
if (status === 'complete' && result.geocodes.length) {
|
||||
const loc = result.geocodes[0].location
|
||||
this.map.setZoomAndCenter(11, [loc.lng, loc.lat])
|
||||
this.addMarker(loc.lng, loc.lat)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
confirmCity() {
|
||||
if (this.selectedCity) {
|
||||
this.cityName = this.selectedCity
|
||||
// 组件 v-model 抛出城市名
|
||||
this.$emit('input', this.selectedCity)
|
||||
// 如果你需要具体的地点名,可以额外抛出一个事件
|
||||
this.$emit('poi-change', { city: this.selectedCity, poi: this.selectedPoi })
|
||||
this.closeDialog()
|
||||
}
|
||||
},
|
||||
|
||||
closeDialog() {
|
||||
this.dialogVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.amap-city-select { width: 100%; }
|
||||
.map-selector { display: flex; gap: 20px; min-height: 450px;
|
||||
.city-sidebar { width: 300px; flex-shrink: 0; display: flex; flex-direction: column;
|
||||
.search-box { margin-bottom: 16px; }
|
||||
.section-title { font-size: 14px; font-weight: bold; color: #303133; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; }
|
||||
|
||||
.hot-cities .city-list { display: flex; flex-wrap: wrap; gap: 10px;
|
||||
.city-item { padding: 6px 14px; background: #f4f4f5; color: #606266; border-radius: 4px; font-size: 13px; cursor: pointer; transition: all 0.2s;
|
||||
&:hover { background: #409eff; color: white; }
|
||||
}
|
||||
}
|
||||
|
||||
/* 搜索结果列表样式升级 */
|
||||
.search-results {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.result-list {
|
||||
flex: 1; overflow-y: auto; padding-right: 5px;
|
||||
|
||||
/* 自定义滚动条 */
|
||||
&::-webkit-scrollbar { width: 4px; }
|
||||
&::-webkit-scrollbar-thumb { background: #dcdfe6; border-radius: 4px; }
|
||||
|
||||
.result-item {
|
||||
padding: 12px 10px; cursor: pointer; display: flex; align-items: flex-start; gap: 10px; border-bottom: 1px solid #ebeef5; transition: background 0.2s;
|
||||
&:hover { background: #f0f7ff; }
|
||||
i { color: #409eff; margin-top: 3px; font-size: 16px; }
|
||||
.result-info {
|
||||
flex: 1; overflow: hidden;
|
||||
.poi-name { font-size: 14px; color: #303133; font-weight: 500; margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.poi-address { font-size: 12px; color: #909399; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.map-container { flex: 1; display: flex; flex-direction: column;
|
||||
.amap-wrapper { flex: 1; width: 100%; border: 1px solid #dcdfe6; border-radius: 6px; overflow: hidden; }
|
||||
.map-tip { margin-top: 10px; font-size: 12px; color: #909399; text-align: center; i { margin-right: 4px; color: #e6a23c; } }
|
||||
}
|
||||
}
|
||||
.dialog-footer { display: flex; justify-content: space-between; align-items: center;
|
||||
.selected-info { font-size: 14px; color: #606266; .selected-city { color: #409eff; font-weight: bold; font-size: 15px; } }
|
||||
}
|
||||
</style>
|
||||
@@ -65,8 +65,8 @@
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="目的地" prop="destination">
|
||||
<el-input v-model="form.destination" placeholder="城市/地址/项目现场" />
|
||||
<div class="hint-text">请填写具体目的地,便于审批人判断出差必要性</div>
|
||||
<amap-city-select v-model="form.destination" placeholder="请选择出差城市" />
|
||||
<div class="hint-text">请通过地图或搜索选择具体城市,便于审批人判断出差必要性</div>
|
||||
</el-form-item>
|
||||
|
||||
<div class="block-title">出差说明</div>
|
||||
@@ -223,13 +223,15 @@ import { ccFlowTask, listFlowNode, listFlowTemplate } from '@/api/hrm/flow'
|
||||
import FileUpload from '@/components/FileUpload'
|
||||
import UserMultiSelect from '@/components/UserSelect/multi.vue'
|
||||
import UserSelect from '@/components/UserSelect/single.vue'
|
||||
import AmapCitySelect from '@/components/AmapCitySelect/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'HrmTravelRequest',
|
||||
components: {
|
||||
UserSelect,
|
||||
FileUpload,
|
||||
UserMultiSelect
|
||||
UserMultiSelect,
|
||||
AmapCitySelect
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user