feat(geolocation): 完善工作地点获取功能
- 新增高德地图逆地理编码接口,支持根据浏览器定位自动获取工作地点。 - 在项目报工页面中实现工作地点的自动更新,用户可通过按钮重新获取定位。 - 添加加载状态和错误提示,提升用户体验。 - 优化相关方法以处理地理位置获取的异步操作。
This commit is contained in:
@@ -5,7 +5,7 @@ VUE_APP_TITLE = 福安德综合办公系统
|
||||
ENV = 'development'
|
||||
|
||||
# 若依管理系统/开发环境
|
||||
VUE_APP_BASE_API = '/dev-api'
|
||||
VUE_APP_BASE_API = '/dev-api'
|
||||
# VUE_APP_BASE_API = 'http://110.41.139.73:8080'
|
||||
|
||||
# 应用访问路径 例如使用前缀 /admin/
|
||||
|
||||
15
ruoyi-ui/src/api/oa/amap.js
Normal file
15
ruoyi-ui/src/api/oa/amap.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 根据经纬度逆地理编码获取城市等信息(后端转发高德)
|
||||
* @param {number} longitude 经度
|
||||
* @param {number} latitude 纬度
|
||||
*/
|
||||
export function getCityByLocation (longitude, latitude) {
|
||||
return request({
|
||||
url: '/oa/amap/city',
|
||||
method: 'get',
|
||||
params: { longitude, latitude },
|
||||
timeout: 15000
|
||||
})
|
||||
}
|
||||
98
ruoyi-ui/src/utils/geolocationWorkPlace.js
Normal file
98
ruoyi-ui/src/utils/geolocationWorkPlace.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { getCityByLocation } from '@/api/oa/amap'
|
||||
|
||||
const GEO_OPTIONS = {
|
||||
enableHighAccuracy: false,
|
||||
timeout: 20000,
|
||||
maximumAge: 120000
|
||||
}
|
||||
|
||||
export const EMPTY_GEOCODE = 'EMPTY_GEOCODE'
|
||||
|
||||
/**
|
||||
* 将 AmapCityNameVo 格式化为展示用工作地点字符串
|
||||
*/
|
||||
export function formatWorkPlaceFromAmap (vo) {
|
||||
if (!vo) {
|
||||
return ''
|
||||
}
|
||||
const province = (vo.province || '').trim()
|
||||
const cityName = (vo.cityName || '').trim()
|
||||
const district = (vo.district || '').trim()
|
||||
const parts = []
|
||||
if (province) {
|
||||
parts.push(province)
|
||||
}
|
||||
if (cityName && cityName !== province) {
|
||||
parts.push(cityName)
|
||||
}
|
||||
if (district) {
|
||||
parts.push(district)
|
||||
}
|
||||
const joined = parts.join(' ').trim()
|
||||
return joined || cityName || province || ''
|
||||
}
|
||||
|
||||
function getCurrentPositionAsync () {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof navigator === 'undefined' || !navigator.geolocation) {
|
||||
reject(new Error('BROWSER_UNSUPPORTED'))
|
||||
return
|
||||
}
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
position => resolve(position),
|
||||
err => reject(err),
|
||||
GEO_OPTIONS
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 浏览器定位 + 后端逆地理 → 工作地点字符串
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export async function resolveWorkPlaceFromBrowser () {
|
||||
const position = await getCurrentPositionAsync()
|
||||
const { longitude, latitude } = position.coords
|
||||
const res = await getCityByLocation(longitude, latitude)
|
||||
const vo = res && res.data
|
||||
const text = formatWorkPlaceFromAmap(vo)
|
||||
if (!text) {
|
||||
const err = new Error(EMPTY_GEOCODE)
|
||||
console.warn('[workPlace] 逆地理结果为空', { longitude, latitude, vo })
|
||||
throw err
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
/**
|
||||
* 将定位/逆地理错误转换为用户可读文案
|
||||
*/
|
||||
export function geolocationUserMessage (err) {
|
||||
if (!err) {
|
||||
return '获取工作地点失败'
|
||||
}
|
||||
if (err.message === 'BROWSER_UNSUPPORTED') {
|
||||
return '当前浏览器不支持定位,请更换浏览器或使用 HTTPS 访问后重试'
|
||||
}
|
||||
if (err.message === EMPTY_GEOCODE) {
|
||||
return '无法根据当前位置解析城市,请稍后重试;若持续失败请联系管理员检查高德地图配置(fad.amap.key)'
|
||||
}
|
||||
const code = err.code
|
||||
if (code === 1) {
|
||||
return '您已拒绝定位权限,请在浏览器设置中允许本站点定位后点击「重新获取定位」'
|
||||
}
|
||||
if (code === 2) {
|
||||
return '暂时无法获取位置信息,请到信号较好处点击「重新获取定位」重试'
|
||||
}
|
||||
if (code === 3) {
|
||||
return '定位请求超时,请检查网络后点击「重新获取定位」重试'
|
||||
}
|
||||
const msg = err.message || ''
|
||||
if (msg.includes('timeout') || msg.includes('超时')) {
|
||||
return '接口请求超时,请稍后点击「重新获取定位」重试'
|
||||
}
|
||||
if (msg.includes('Network Error') || msg.includes('网络')) {
|
||||
return '网络异常,请检查连接后点击「重新获取定位」重试'
|
||||
}
|
||||
return '获取工作地点失败,请点击「重新获取定位」重试'
|
||||
}
|
||||
@@ -87,7 +87,19 @@
|
||||
</el-alert>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="96px">
|
||||
<el-form-item label="工作地点" prop="workPlace">
|
||||
<el-input v-model="form.workPlace" placeholder="请输入工作地点" />
|
||||
<el-input
|
||||
v-model="form.workPlace"
|
||||
readonly
|
||||
:placeholder="workPlaceLoading ? '正在获取定位…' : '请点击「重新获取定位」获取工作地点(不可手动输入)'"
|
||||
>
|
||||
<el-button
|
||||
slot="append"
|
||||
icon="el-icon-location-outline"
|
||||
:loading="workPlaceLoading"
|
||||
@click="refreshWorkPlace"
|
||||
>重新获取定位</el-button>
|
||||
</el-input>
|
||||
<div v-if="workPlaceLocateError" class="work-place-error">{{ workPlaceLocateError }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否出差" prop="isTrip">
|
||||
<el-radio-group v-model="form.isTrip">
|
||||
@@ -148,12 +160,19 @@ import { listDept } from "@/api/system/dept";
|
||||
import { listUser } from "@/api/system/user";
|
||||
import ProjectSelect from "@/components/fad-service/ProjectSelect";
|
||||
import ProjectReportDetail from "@/views/oa/project/report/components/ProjectReportDetail.vue";
|
||||
import {
|
||||
EMPTY_GEOCODE,
|
||||
geolocationUserMessage,
|
||||
resolveWorkPlaceFromBrowser
|
||||
} from "@/utils/geolocationWorkPlace";
|
||||
|
||||
export default {
|
||||
name: "ProjectReport",
|
||||
components: { ProjectReportDetail, ProjectSelect },
|
||||
data () {
|
||||
return {
|
||||
workPlaceLoading: false,
|
||||
workPlaceLocateError: "",
|
||||
// 按钮loading
|
||||
buttonLoading: false,
|
||||
detailVisible: false,
|
||||
@@ -230,6 +249,50 @@ export default {
|
||||
this.getUserList();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @param {{ silent?: boolean, force?: boolean }} options
|
||||
*/
|
||||
async syncWorkPlaceFromGeolocation (options = {}) {
|
||||
const silent = !!options.silent;
|
||||
const force = !!options.force;
|
||||
if (!force && this.form && this.form.reportId) {
|
||||
return;
|
||||
}
|
||||
this.workPlaceLoading = true;
|
||||
this.workPlaceLocateError = "";
|
||||
try {
|
||||
const text = await resolveWorkPlaceFromBrowser();
|
||||
this.$set(this.form, "workPlace", text);
|
||||
if (!silent) {
|
||||
this.$modal.msgSuccess("已根据定位更新工作地点");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[projectReport] 工作地点定位失败", e);
|
||||
const msg = geolocationUserMessage(e);
|
||||
this.workPlaceLocateError = msg;
|
||||
this.$set(this.form, "workPlace", undefined);
|
||||
const isBrowserGeoError =
|
||||
e &&
|
||||
(e.code === 1 ||
|
||||
e.code === 2 ||
|
||||
e.code === 3 ||
|
||||
e.message === "BROWSER_UNSUPPORTED" ||
|
||||
e.message === EMPTY_GEOCODE);
|
||||
if (isBrowserGeoError) {
|
||||
this.$modal.msgWarning(msg);
|
||||
}
|
||||
} finally {
|
||||
this.workPlaceLoading = false;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.validateField("workPlace");
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
refreshWorkPlace () {
|
||||
this.syncWorkPlaceFromGeolocation({ silent: false, force: true });
|
||||
},
|
||||
/** 格式化日期为 yyyy-MM-dd 格式 */
|
||||
formatDate (date) {
|
||||
const year = date.getFullYear();
|
||||
@@ -258,26 +321,31 @@ export default {
|
||||
this.reset();
|
||||
this.open = true;
|
||||
this.title = "补录项目报工";
|
||||
this.$nextTick(() => {
|
||||
this.syncWorkPlaceFromGeolocation({ silent: true, force: false });
|
||||
});
|
||||
},
|
||||
|
||||
/** 检查今日报工 */
|
||||
/** 检查今日报工(须返回 Promise,供新增时 .finally 打开弹窗) */
|
||||
checkTodayReport () {
|
||||
getTodayProjectReport().then(response => {
|
||||
if (response.data && response.data.reportId) {
|
||||
this.hasTodayReport = true;
|
||||
this.todayReportId = response.data.reportId;
|
||||
this.form = {
|
||||
...this.form,
|
||||
...response.data
|
||||
};
|
||||
} else {
|
||||
return getTodayProjectReport()
|
||||
.then(response => {
|
||||
if (response.data && response.data.reportId) {
|
||||
this.hasTodayReport = true;
|
||||
this.todayReportId = response.data.reportId;
|
||||
this.form = {
|
||||
...this.form,
|
||||
...response.data
|
||||
};
|
||||
} else {
|
||||
this.hasTodayReport = false;
|
||||
this.todayReportId = null;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.hasTodayReport = false;
|
||||
this.todayReportId = null;
|
||||
}
|
||||
}).catch(() => {
|
||||
this.hasTodayReport = false;
|
||||
this.todayReportId = null;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getDeptList () {
|
||||
@@ -313,6 +381,8 @@ export default {
|
||||
},
|
||||
// 表单重置
|
||||
reset () {
|
||||
this.workPlaceLoading = false;
|
||||
this.workPlaceLocateError = "";
|
||||
this.form = {
|
||||
reportId: undefined,
|
||||
handler: undefined,
|
||||
@@ -359,9 +429,15 @@ export default {
|
||||
handleAdd () {
|
||||
this.reset();
|
||||
this.suppVisible = false;
|
||||
this.checkTodayReport();
|
||||
this.open = true;
|
||||
this.title = "添加项目报工";
|
||||
this.checkTodayReport().finally(() => {
|
||||
this.open = true;
|
||||
this.$nextTick(() => {
|
||||
if (!this.form.reportId) {
|
||||
this.syncWorkPlaceFromGeolocation({ silent: true, force: false });
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate (row) {
|
||||
@@ -473,4 +549,11 @@ export default {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.work-place-error {
|
||||
color: #f56c6c;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user