diff --git a/ruoyi-ui/.env.development b/ruoyi-ui/.env.development index 6868459..032b0c6 100644 --- a/ruoyi-ui/.env.development +++ b/ruoyi-ui/.env.development @@ -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/ diff --git a/ruoyi-ui/src/api/oa/amap.js b/ruoyi-ui/src/api/oa/amap.js new file mode 100644 index 0000000..ca57be1 --- /dev/null +++ b/ruoyi-ui/src/api/oa/amap.js @@ -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 + }) +} diff --git a/ruoyi-ui/src/utils/geolocationWorkPlace.js b/ruoyi-ui/src/utils/geolocationWorkPlace.js new file mode 100644 index 0000000..f298cd3 --- /dev/null +++ b/ruoyi-ui/src/utils/geolocationWorkPlace.js @@ -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} + */ +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 '获取工作地点失败,请点击「重新获取定位」重试' +} diff --git a/ruoyi-ui/src/views/oa/project/report/my.vue b/ruoyi-ui/src/views/oa/project/report/my.vue index 10a39d1..76546d7 100644 --- a/ruoyi-ui/src/views/oa/project/report/my.vue +++ b/ruoyi-ui/src/views/oa/project/report/my.vue @@ -87,7 +87,19 @@ - + + 重新获取定位 + +
{{ workPlaceLocateError }}
@@ -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; +} \ No newline at end of file