| | |
| | | <template> |
| | | <view class="client-visit-detail"> |
| | | <PageHeader title="客户拜访详情" @back="goBack" /> |
| | | |
| | | <u-form @submit="handleSignIn" ref="formRef" label-width="90"> |
| | | <PageHeader title="客户拜访详情" |
| | | @back="goBack" /> |
| | | <u-form @submit="handleSignIn" |
| | | ref="formRef" |
| | | label-width="90"> |
| | | <!-- 客户信息 --> |
| | | <u-cell-group title="客户信息"> |
| | | <u-form-item label="客户名称" prop="customerName" required border-bottom> |
| | | <u-input |
| | | v-model="form.customerName" |
| | | placeholder="请输入客户名称" |
| | | /> |
| | | <u-form-item label="客户名称" |
| | | prop="customerName" |
| | | required |
| | | border-bottom> |
| | | <u-input v-model="form.customerName" |
| | | placeholder="请输入客户名称" /> |
| | | </u-form-item> |
| | | <u-form-item label="联系人" prop="contact" border-bottom> |
| | | <u-input |
| | | v-model="form.contact" |
| | | placeholder="请输入联系人" |
| | | /> |
| | | <u-form-item label="联系人" |
| | | prop="contact" |
| | | border-bottom> |
| | | <u-input v-model="form.contact" |
| | | placeholder="请输入联系人" /> |
| | | </u-form-item> |
| | | <u-form-item label="联系电话" prop="contactPhone" border-bottom> |
| | | <u-input |
| | | v-model="form.contactPhone" |
| | | placeholder="请输入联系电话" |
| | | /> |
| | | <u-form-item label="联系电话" |
| | | prop="contactPhone" |
| | | border-bottom> |
| | | <u-input v-model="form.contactPhone" |
| | | placeholder="请输入联系电话" /> |
| | | </u-form-item> |
| | | </u-cell-group> |
| | | |
| | | <!-- 拜访信息 --> |
| | | <u-cell-group title="拜访信息"> |
| | | <u-form-item label="拜访目的" prop="purposeVisit" required border-bottom> |
| | | <u-input |
| | | v-model="form.purposeVisit" |
| | | placeholder="请输入拜访目的" |
| | | /> |
| | | <u-form-item label="拜访目的" |
| | | prop="purposeVisit" |
| | | required |
| | | border-bottom> |
| | | <u-input v-model="form.purposeVisit" |
| | | placeholder="请输入拜访目的" /> |
| | | </u-form-item> |
| | | <u-form-item label="拜访时间" prop="purposeDate" required border-bottom> |
| | | <u-input |
| | | v-model="form.purposeDate" |
| | | placeholder="请选择拜访时间" |
| | | readonly |
| | | @click="showTimePicker" |
| | | /> |
| | | <u-form-item label="拜访时间" |
| | | prop="purposeDate" |
| | | required |
| | | border-bottom> |
| | | <u-input v-model="form.purposeDate" |
| | | placeholder="请选择拜访时间" |
| | | @click="showTimePicker" /> |
| | | <template #right> |
| | | <up-icon |
| | | name="arrow-right" |
| | | @click="showTimePicker" |
| | | ></up-icon> |
| | | </template> |
| | | <up-icon name="arrow-right" |
| | | @click="showTimePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item label="拜访地点" prop="visitAddress" required border-bottom> |
| | | <u-input |
| | | v-model="form.visitAddress" |
| | | placeholder="请输入拜访地点" |
| | | > |
| | | <u-form-item label="拜访地点" |
| | | prop="visitAddress" |
| | | required |
| | | border-bottom> |
| | | <u-input v-model="form.visitAddress" |
| | | placeholder="请输入拜访地点"> |
| | | <template #suffix> |
| | | <u-icon name="map" @click="getCurrentLocation" class="location-icon" /> |
| | | <u-icon name="map" |
| | | @click="getCurrentLocation" |
| | | class="location-icon" /> |
| | | </template> |
| | | </u-input> |
| | | </u-form-item> |
| | | </u-cell-group> |
| | | |
| | | <!-- 备注信息 --> |
| | | <u-cell-group title="备注信息"> |
| | | <u-form-item label="备注" prop="remark" border-bottom> |
| | | <u-textarea |
| | | v-model="form.remark" |
| | | placeholder="请输入备注信息" |
| | | :maxlength="200" |
| | | count |
| | | :autoHeight="true" |
| | | /> |
| | | <u-form-item label="备注" |
| | | prop="remark" |
| | | border-bottom> |
| | | <u-textarea v-model="form.remark" |
| | | placeholder="请输入备注信息" |
| | | :maxlength="200" |
| | | count |
| | | :autoHeight="true" /> |
| | | </u-form-item> |
| | | </u-cell-group> |
| | | |
| | | <!-- 提交按钮 --> |
| | | <view class="footer-btns"> |
| | | <u-button class="cancel-btn" @click="goBack">取消</u-button> |
| | | <u-button class="sign-btn" type="primary" @click="handleSignIn" :loading="loading">签到</u-button> |
| | | <u-button class="cancel-btn" |
| | | @click="goBack">取消</u-button> |
| | | <u-button class="sign-btn" |
| | | type="primary" |
| | | @click="handleSignIn" |
| | | :loading="loading">签到</u-button> |
| | | </view> |
| | | </u-form> |
| | | |
| | | <!-- 时间选择器 --> |
| | | <up-datetime-picker |
| | | :show="showTime" |
| | | v-model="currentTime" |
| | | @confirm="onTimeConfirm" |
| | | @cancel="showTime = false" |
| | | mode="datetime" |
| | | /> |
| | | <up-datetime-picker :show="showTime" |
| | | v-model="currentTime" |
| | | @confirm="onTimeConfirm" |
| | | @cancel="showTime = false" |
| | | mode="datetime" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | // 替换 toast 方法 |
| | | defineOptions({name: 'client-visit-detail'}) |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | // 替换 toast 方法 |
| | | defineOptions({ name: "client-visit-detail" }); |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | |
| | | import { ref, onMounted } from 'vue' |
| | | import { onShow } from '@dcloudio/uni-app' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { clientVisitSignIn } from '@/api/cooperativeOffice/clientVisit' |
| | | import useUserStore from "@/store/modules/user" |
| | | import dayjs from "dayjs" |
| | | import { formatDateToYMD } from '@/utils/ruoyi' |
| | | import { ref, onMounted } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { |
| | | clientVisitSignIn, |
| | | clientVisitUpdate, |
| | | } from "@/api/cooperativeOffice/clientVisit"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import dayjs from "dayjs"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | |
| | | const userStore = useUserStore() |
| | | const userStore = useUserStore(); |
| | | |
| | | // 表单数据 |
| | | const form = ref({ |
| | | customerName: '', |
| | | contact: '', |
| | | contactPhone: '', |
| | | visitingPeople: '', |
| | | purposeVisit: '', |
| | | purposeDate: '', |
| | | visitAddress: '', |
| | | latitude: '', |
| | | longitude: '', |
| | | locationAddress: '', |
| | | remark: '' |
| | | }) |
| | | // 表单数据 |
| | | const form = ref({ |
| | | customerName: "", |
| | | contact: "", |
| | | contactPhone: "", |
| | | visitingPeople: "", |
| | | purposeVisit: "", |
| | | purposeDate: "", |
| | | visitAddress: "", |
| | | latitude: "", |
| | | longitude: "", |
| | | locationAddress: "", |
| | | remark: "", |
| | | }); |
| | | |
| | | // 页面状态 |
| | | const loading = ref(false) |
| | | const formRef = ref(null) |
| | | // 页面状态 |
| | | const loading = ref(false); |
| | | const formRef = ref(null); |
| | | |
| | | // 时间相关 |
| | | const currentTime = ref(Date.now()) |
| | | const showTime = ref(false) |
| | | // 时间相关 |
| | | const currentTime = ref(Date.now()); |
| | | const showTime = ref(false); |
| | | |
| | | // 返回上一页 |
| | | const goBack = () => { |
| | | uni.navigateBack() |
| | | } |
| | | // 返回上一页 |
| | | const goBack = () => { |
| | | // 返回时清除本地存储的ID |
| | | uni.removeStorageSync("clientVisit"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // 显示时间选择器 |
| | | const showTimePicker = () => { |
| | | showTime.value = true |
| | | } |
| | | // 显示时间选择器 |
| | | const showTimePicker = () => { |
| | | showTime.value = true; |
| | | }; |
| | | |
| | | // 确认时间选择 |
| | | const onTimeConfirm = (e) => { |
| | | console.log(e) |
| | | form.value.purposeDate = e.value |
| | | currentTime.value = e.value |
| | | showTime.value = false; |
| | | } |
| | | // 确认时间选择 |
| | | const onTimeConfirm = e => { |
| | | console.log(e); |
| | | form.value.purposeDate = dayjs(e.value).format("YYYY-MM-DD HH:mm:ss"); |
| | | currentTime.value = e.value; |
| | | showTime.value = false; |
| | | }; |
| | | |
| | | // 获取当前位置 |
| | | const getCurrentLocation = () => { |
| | | uni.showLoading({ title: '获取位置中...' }) |
| | | |
| | | uni.getLocation({ |
| | | type: 'gcj02', |
| | | success: (res) => { |
| | | form.value.latitude = res.latitude |
| | | form.value.longitude = res.longitude |
| | | |
| | | // 使用逆地理编码获取地址信息 |
| | | uni.request({ |
| | | url: `https://restapi.amap.com/v3/geocode/regeo?key=c120a5dc69a9f61839f7763e6057005f&location=${res.longitude},${res.latitude}&radius=1000&extensions=all`, |
| | | success: (geoRes) => { |
| | | uni.hideLoading() |
| | | if (geoRes.data.status === '1' && geoRes.data.regeocode) { |
| | | const regeocode = geoRes.data.regeocode |
| | | const address = regeocode.formatted_address |
| | | |
| | | // 优先显示详细地址 |
| | | if (address) { |
| | | form.value.visitAddress = address |
| | | showToast('位置获取成功') |
| | | // 获取当前位置 |
| | | const getCurrentLocation = () => { |
| | | uni.showLoading({ title: "获取位置中..." }); |
| | | |
| | | uni.getLocation({ |
| | | type: "gcj02", |
| | | success: res => { |
| | | form.value.latitude = res.latitude; |
| | | form.value.longitude = res.longitude; |
| | | |
| | | // 使用逆地理编码获取地址信息 |
| | | uni.request({ |
| | | url: `https://restapi.amap.com/v3/geocode/regeo?key=c120a5dc69a9f61839f7763e6057005f&location=${res.longitude},${res.latitude}&radius=1000&extensions=all`, |
| | | success: geoRes => { |
| | | uni.hideLoading(); |
| | | if (geoRes.data.status === "1" && geoRes.data.regeocode) { |
| | | const regeocode = geoRes.data.regeocode; |
| | | const address = regeocode.formatted_address; |
| | | |
| | | // 优先显示详细地址 |
| | | if (address) { |
| | | form.value.visitAddress = address; |
| | | showToast("位置获取成功"); |
| | | } else { |
| | | // 如果没有详细地址,尝试组合地址信息 |
| | | const addressComponent = regeocode.addressComponent; |
| | | const combinedAddress = `${addressComponent.province}${addressComponent.city}${addressComponent.district}${addressComponent.township}`; |
| | | form.value.visitAddress = combinedAddress; |
| | | showToast("位置获取成功"); |
| | | } |
| | | } else { |
| | | // 如果没有详细地址,尝试组合地址信息 |
| | | const addressComponent = regeocode.addressComponent |
| | | const combinedAddress = `${addressComponent.province}${addressComponent.city}${addressComponent.district}${addressComponent.township}` |
| | | form.value.visitAddress = combinedAddress |
| | | showToast('位置获取成功') |
| | | // API调用成功但没有返回地址信息 |
| | | const fallbackAddress = `位置: ${res.latitude.toFixed( |
| | | 4 |
| | | )}, ${res.longitude.toFixed(4)}`; |
| | | form.value.visitAddress = fallbackAddress; |
| | | showToast("获取到位置,但地址解析失败"); |
| | | } |
| | | } else { |
| | | // API调用成功但没有返回地址信息 |
| | | const fallbackAddress = `位置: ${res.latitude.toFixed(4)}, ${res.longitude.toFixed(4)}` |
| | | form.value.visitAddress = fallbackAddress |
| | | showToast('获取到位置,但地址解析失败') |
| | | } |
| | | }, |
| | | fail: (err) => { |
| | | uni.hideLoading() |
| | | console.error('逆地理编码失败:', err) |
| | | |
| | | // 逆地理编码失败时,显示简化的位置信息 |
| | | const fallbackAddress = `位置: ${res.latitude.toFixed(4)}, ${res.longitude.toFixed(4)}` |
| | | form.value.visitAddress = fallbackAddress |
| | | showToast('位置获取成功,但地址解析失败') |
| | | }, |
| | | fail: err => { |
| | | uni.hideLoading(); |
| | | console.error("逆地理编码失败:", err); |
| | | |
| | | // 逆地理编码失败时,显示简化的位置信息 |
| | | const fallbackAddress = `位置: ${res.latitude.toFixed( |
| | | 4 |
| | | )}, ${res.longitude.toFixed(4)}`; |
| | | form.value.visitAddress = fallbackAddress; |
| | | showToast("位置获取成功,但地址解析失败"); |
| | | }, |
| | | }); |
| | | }, |
| | | fail: err => { |
| | | uni.hideLoading(); |
| | | showToast("获取位置失败,请检查定位权限"); |
| | | console.error("获取位置失败:", err); |
| | | |
| | | // 失败时显示错误信息 |
| | | form.value.visitAddress = "位置获取失败"; |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | // 提交签到 |
| | | const handleSignIn = async () => { |
| | | if (!form.value.customerName) { |
| | | showToast("请输入客户名称"); |
| | | return; |
| | | } |
| | | |
| | | if (!form.value.purposeVisit) { |
| | | showToast("请输入拜访目的"); |
| | | return; |
| | | } |
| | | |
| | | if (!form.value.purposeDate) { |
| | | showToast("请选择拜访时间"); |
| | | return; |
| | | } |
| | | if (!form.value.visitAddress) { |
| | | showToast("请获取当前位置"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | loading.value = true; |
| | | |
| | | // 使用安全浅拷贝,避免对象展开在某些运行时抛错 |
| | | const source = |
| | | form.value && typeof form.value === "object" ? form.value : {}; |
| | | const submitData = {}; |
| | | Object.keys(source).forEach(k => { |
| | | submitData[k] = source[k]; |
| | | }); |
| | | console.log("submitData", submitData); |
| | | if (isEdit.value) { |
| | | const { code } = await clientVisitUpdate(submitData); |
| | | if (code === 200) { |
| | | showToast("修改成功"); |
| | | setTimeout(() => { |
| | | goBack(); |
| | | }, 500); |
| | | } else { |
| | | loading.value = false; |
| | | showToast("签到失败,请重试"); |
| | | } |
| | | }) |
| | | }, |
| | | fail: (err) => { |
| | | uni.hideLoading() |
| | | showToast('获取位置失败,请检查定位权限') |
| | | console.error('获取位置失败:', err) |
| | | |
| | | // 失败时显示错误信息 |
| | | form.value.visitAddress = '位置获取失败' |
| | | } else { |
| | | const { code } = await clientVisitSignUp(submitData); |
| | | if (code === 200) { |
| | | showToast("签到成功"); |
| | | setTimeout(() => { |
| | | goBack(); |
| | | }, 500); |
| | | } else { |
| | | loading.value = false; |
| | | showToast("签到失败,请重试"); |
| | | } |
| | | } |
| | | } catch (e) { |
| | | loading.value = false; |
| | | console.error("签到失败:", e); |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 提交签到 |
| | | const handleSignIn = async () => { |
| | | if (!form.value.customerName) { |
| | | showToast('请输入客户名称') |
| | | return |
| | | } |
| | | |
| | | if (!form.value.purposeVisit) { |
| | | showToast('请输入拜访目的') |
| | | return |
| | | } |
| | | |
| | | if (!form.value.purposeDate) { |
| | | showToast('请选择拜访时间') |
| | | return |
| | | } |
| | | if (!form.value.visitAddress) { |
| | | showToast('请获取当前位置') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | loading.value = true |
| | | |
| | | // 使用安全浅拷贝,避免对象展开在某些运行时抛错 |
| | | const source = (form.value && typeof form.value === 'object') ? form.value : {} |
| | | const submitData = {} |
| | | Object.keys(source).forEach((k) => { |
| | | submitData[k] = source[k] |
| | | }) |
| | | |
| | | console.log('提交数据:', submitData) |
| | | |
| | | const { code } = await clientVisitSignIn(submitData) |
| | | console.log('code----', code); |
| | | |
| | | if (code === 200) { |
| | | showToast('签到成功') |
| | | setTimeout(() => { |
| | | uni.navigateBack() |
| | | }, 500) |
| | | }; |
| | | const isEdit = ref(false); |
| | | onLoad(() => { |
| | | // 编辑拜访时,从本地存储获取拜访记录 |
| | | const visit = uni.getStorageSync("clientVisit"); |
| | | if (visit) { |
| | | form.value = visit; |
| | | isEdit.value = true; |
| | | console.log("form.value", form.value); |
| | | } else { |
| | | loading.value = false |
| | | showToast('签到失败,请重试') |
| | | isEdit.value = false; |
| | | } |
| | | } catch (e) { |
| | | loading.value = false |
| | | showToast('签到失败,请检查网络连接') |
| | | console.error('签到失败:', e) |
| | | } |
| | | } |
| | | }); |
| | | |
| | | // 初始化页面数据 |
| | | const initPageData = () => { |
| | | // 设置默认拜访时间为当前时间 |
| | | form.value.purposeDate = dayjs().format('YYYY-MM-DD HH:mm:ss') |
| | | currentTime.value = Date.now() |
| | | |
| | | // 设置拜访人为当前登录用户的昵称 |
| | | form.value.visitingPeople = userStore.nickName || '' |
| | | } |
| | | // 初始化页面数据 |
| | | const initPageData = () => { |
| | | // 设置默认拜访时间为当前时间 |
| | | form.value.purposeDate = dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | currentTime.value = Date.now(); |
| | | |
| | | onMounted(() => { |
| | | initPageData() |
| | | }) |
| | | // 设置拜访人为当前登录用户的昵称 |
| | | form.value.visitingPeople = userStore.nickName || ""; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | initPageData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import '@/static/scss/form-common.scss'; |
| | | .client-visit { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 5rem; |
| | | } |
| | | @import "@/static/scss/form-common.scss"; |
| | | .client-visit { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 5rem; |
| | | } |
| | | |
| | | .footer-btns { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 0.75rem 0; |
| | | box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05); |
| | | z-index: 1000; |
| | | } |
| | | .footer-btns { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 0.75rem 0; |
| | | box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05); |
| | | z-index: 1000; |
| | | } |
| | | |
| | | .cancel-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #666; |
| | | background: #f5f5f5; |
| | | border: 1px solid #ddd; |
| | | width: 45%; |
| | | height: 2.5rem; |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | .cancel-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #666; |
| | | background: #f5f5f5; |
| | | border: 1px solid #ddd; |
| | | width: 45%; |
| | | height: 2.5rem; |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | .sign-btn { |
| | | font-weight: 500; |
| | | font-size: 1rem; |
| | | color: #fff; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | border: none; |
| | | width: 45%; |
| | | height: 2.5rem; |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | .sign-btn { |
| | | font-weight: 500; |
| | | font-size: 1rem; |
| | | color: #fff; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | border: none; |
| | | width: 45%; |
| | | height: 2.5rem; |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | .location-icon { |
| | | color: #1989fa; |
| | | font-size: 1.2rem; |
| | | } |
| | | .location-icon { |
| | | color: #1989fa; |
| | | font-size: 1.2rem; |
| | | } |
| | | </style> |