diff --git a/common/config.js b/common/config.js index 3348a4d..140a0ce 100644 --- a/common/config.js +++ b/common/config.js @@ -1,13 +1,14 @@ const mode = process.env.NODE_ENV; // const mode = 'development'; -let ROOTPATH = ''; //域名 +let ROOTPATH = 'https://sstt.ali251.langye.net'; //域名 +// let ROOTPATH = 'https://sstt.115.langye.net'; //域名 switch (mode) { case 'development': // ROOTPATH = "https://sstt.ali251.langye.net" - ROOTPATH = "https://sstt.115.langye.net" + ROOTPATH = ROOTPATH break; case 'production': - ROOTPATH = "https://sstt.115.langye.net" + ROOTPATH = ROOTPATH break; default: throw new Error('未配置环境'); diff --git a/common/http.api.js b/common/http.api.js index dedef51..84fc255 100644 --- a/common/http.api.js +++ b/common/http.api.js @@ -25,6 +25,8 @@ let apiAdmin = { me: "/api/admin/auth/me", scheduleList: "/api/admin/schedule/schedule-index", customerList: "/api/admin/customer/get-list", + customerInfo:'/api/admin/customer/get-info', + customerSave:'/api/admin/customer/save', saveCallback: "/api/admin/schedule-list-callbacks/save", saveCheck: "/api/admin/schedule-list-checks/save", callbackList: "/api/admin/schedule-list-callbacks/get-list", @@ -67,6 +69,8 @@ const install = (Vue, vm) => { let adminMe = (data = {}) => vm.$u.post(apiAdmin.me, data); let adminScheduleList = (data = {}) => vm.$u.get(apiAdmin.scheduleList, data); let adminCustomerList = (data = {}) => vm.$u.get(apiAdmin.customerList, data); + let adminCustomerInfo = (params = {}) => vm.$u.get(apiAdmin.customerInfo+`/${params.id}`, params); + let adminCustomerSave = (data = {}) => vm.$u.post(apiAdmin.customerSave, data); let adminSaveCallback = (data = {}) => vm.$u.post(apiAdmin.saveCallback, data); let adminSaveCheck = (data = {}) => vm.$u.post(apiAdmin.saveCheck, data); let adminCallbackList = (data = {}) => vm.$u.get(apiAdmin.callbackList, data); @@ -106,6 +110,8 @@ const install = (Vue, vm) => { adminMe, adminScheduleList, adminCustomerList, + adminCustomerInfo, + adminCustomerSave, adminSaveCallback, adminSaveCheck, adminCallbackList, diff --git a/package_sub/pages/addQuality/addQuality.vue b/package_sub/pages/addQuality/addQuality.vue index 0a9b403..7d24bad 100644 --- a/package_sub/pages/addQuality/addQuality.vue +++ b/package_sub/pages/addQuality/addQuality.vue @@ -4,16 +4,48 @@ - - - - - - - - + + - + + + + + + + + + + + 正常 + 死亡 + + + + + + + {{ defaultCustomerAddress.address || '' }} + + 获取位置 + + + + + 获取位置 + + + + + + + 更新用户信息 + + + + + + @@ -27,12 +59,12 @@ - - - - - - + + + + + + { if (this.vuex_sign_image || value) { @@ -263,23 +301,91 @@ deep: true, // 深度监听 immediate: true // 初始化时立即执行一次 } - }, - onLoad(option) { - this.form.customer_id = option.customer_id; - this.id = option.id; - this.type = option.id ? 'edit' : 'add'; - if (this.type === 'edit') { - this.getDetail() - }else{ - this.form.date = moment().format('YYYY-MM-DD') - this.form.customer_name = option.customer_name?option.customer_name:'' - } - }, - onReady() { - this.load(); - this.$refs.uForm.setRules(this.rules); }, - methods: { + onLoad(option) { + this.form.customer_id = option.customer_id; + this.id = option.id; + this.type = option.id ? 'edit' : 'add'; + if (this.type === 'edit') { + this.getDetail() + }else{ + this.form.date = moment().format('YYYY-MM-DD') + this.form.customer_name = option.customer_name?option.customer_name:'' + } + if (this.form.customer_id) { + this.getCustomerInfo() + } + }, + onReady() { + this.load(); + this.$refs.uForm.setRules(this.rules); + }, + computed: { + defaultCustomerAddress(){ + const idx = this.editCustomer.customer_address.findIndex(i => Number(i.default) === 1) + return idx > -1 ? this.editCustomer.customer_address[idx] : null + } + }, + methods: { + async getCustomerInfo(){ + try{ + const res = await this.$u.api.adminCustomerInfo({ id: this.form.customer_id }) + this.customerInfo = res + // 初始化可编辑数据 + this.editCustomer.phone = res.phone || '' + this.editCustomer.is_dead = Number(res.is_dead || 0) + this.editCustomer.customer_address = Array.isArray(res.customer_address) ? JSON.parse(JSON.stringify(res.customer_address)) : [] + }catch(e){ + console.error('adminCustomerInfo error', e) + } + }, + defaultAddrIndex() { + return this.editCustomer.customer_address.findIndex(i => Number(i.default) === 1) + }, + // 获取定位并更新默认地址(已有默认地址) + async updateDefaultCustomerAddress(){ + try{ + const res = await uni.getLocation({ type: 'gcj02', isHighAccuracy: true }) + const location = Array.isArray(res) ? res[1] : res + if(!location) throw new Error('定位失败') + await new Promise((resolve, reject) => { + this.qqmapsdk.reverseGeocoder({ + location: { latitude: location.latitude, longitude: location.longitude }, + success: (r) => resolve(r), + fail: (err) => reject(err) + }) + }).then(geo => { + const idx = this.defaultAddrIndex() + const addr = geo.result.address + (geo.result.formatted_addresses?.recommend || '') + // 如果已存在默认地址,先移除该条(避免携带旧的 id) + if (idx > -1) { + this.editCustomer.customer_address.splice(idx, 1) + } + // 插入新的默认地址,且不包含 id 字段 + this.editCustomer.customer_address.push({ address: addr, lat: location.latitude, lng: location.longitude, default: 1 }) + uni.showToast({ icon: 'none', title: '位置已更新' }) + }) + }catch(e){ + uni.showToast({ icon: 'none', title: '定位失败' }) + } + }, + // 无默认地址时创建 + async createDefaultCustomerAddress(){ + await this.updateDefaultCustomerAddress() + }, + saveCustomerInfo(){ + const payload = { + ...this.customerInfo, + id: this.form.customer_id, + is_dead: this.editCustomer.is_dead, + phone: this.editCustomer.phone, + customer_address_list: this.editCustomer.customer_address + } + this.$u.api.adminCustomerSave(payload).then(()=>{ + uni.showToast({ icon:'success', title:'已更新' }) + this.getCustomerInfo() + }) + }, showimg(url) { if (url) @@ -338,132 +444,132 @@ let arr = e.filter(i => i?.trim()) console.log("arr", e, arr) // this.$set(item, 'score', arr) - }, - blurInput(item){ - console.log("blur",item) - if(!this.validateNumberRange(item.score,item.min,item.max)){ - uni.showToast({ - title: "请输入正确的分值", - icon: "none" - }) - item.score = 0 - } - }, - // 计算总分 - calculateTotalScore(form) { - // 处理 forms 不存在或非数组的情况 - if (!Array.isArray(this.form.forms)) { - this.form.total_score = 0; - return; - } - // 检查是否存在任何一个选项的 checked 为 true - const hasCheckedOption = this.form.forms.some(item => { - // 如果 item.options 存在且包含 checked 为 true 的项 - return Array.isArray(item.options) && - item.options.some(option => option.checked === true); - }); - - // 如果存在 checked 为 true 的选项,直接返回 0 分 - if (hasCheckedOption) { - this.form.total_score = 0; - this.form.forms.map(item=>item.score=0) - return; - } - // 否则,正常累加所有项的分数 - const total = this.form.forms.reduce((sum, item) => { - // 将 item.score 转换为数字,非数字默认 0 - const score = Number(item.score) || 0; - return sum + score; - }, 0); - - this.form.total_score = total; - }, - // 判断是否为有效数子 - validateNumberRange(value, min, max) { - // 步骤1:判断值是否为有效数字(处理字符串形式的数字) - let num; - - // 如果已经是数字类型,直接使用 - if (typeof value === 'number') { - num = value; - } - // 如果是字符串,尝试转换为数字 - else if (typeof value === 'string') { - // 去除首尾空格 - const trimmed = value.trim(); - - // 使用 parseFloat 转换,并验证是否完全匹配数字格式 - num = parseFloat(trimmed); - - // 如果转换后不是NaN,且字符串完全等于转换后的数字字符串(确保没有多余字符) - if (isNaN(num) || trimmed !== String(num)) { - return false; - } - } - // 其他类型(如 null、object 等)直接判定为无效 - else { - return false; - } - - // 步骤2:检查数字是否在范围内(包含边界) - return num >= min && num <= max; - }, - /** - * 验证表单数据 - * @param {Array} forms - 表单数据数组 - * @returns {Object} - 验证结果 { isValid: Boolean, errorMsg: String } - */ - validateForm(forms) { - // 1. 检查是否存在特殊项(包含 options 且有 checked=true) - const specialItem = forms.find(item => - Array.isArray(item.options) && - item.options.some(option => option.checked === true) - ); - - // 如果存在特殊项被勾选,直接返回总分0 - if (specialItem) { - forms.map(item=>item.score = 0) - return { isValid: true, totalScore: 0, errorMsg: '' }; - } - - // 2. 验证其他项的分数是否有效(在 min 和 max 之间) - for (const item of forms) { - // 跳过特殊项 - if (Array.isArray(item.options)) continue; - - const { score, min, max } = item; - const parsedScore = parseFloat(score); - - // 检查分数是否为有效数字 - if (isNaN(parsedScore) || !isFinite(parsedScore)) { - return { isValid: false, totalScore: 0, errorMsg: `请输入有效的分数:${item.ask}` }; - } - - // 检查分数是否在范围内 - if (parsedScore < min || parsedScore > max) { - return { isValid: false, totalScore: 0, errorMsg: `分数超出范围:${item.ask} (${min}-${max}分)` }; - } - } - // 3. 计算总分(当所有项都有效时) - const totalScore = forms.reduce((sum, item) => { - // 跳过特殊项(已处理) - if (Array.isArray(item.options)) return sum; - - // 累加有效分数 - return sum + parseFloat(item.score); - }, 0); - - return { isValid: true, totalScore, errorMsg: '' }; + }, + blurInput(item){ + console.log("blur",item) + if(!this.validateNumberRange(item.score,item.min,item.max)){ + uni.showToast({ + title: "请输入正确的分值", + icon: "none" + }) + item.score = 0 + } + }, + // 计算总分 + calculateTotalScore(form) { + // 处理 forms 不存在或非数组的情况 + if (!Array.isArray(this.form.forms)) { + this.form.total_score = 0; + return; + } + // 检查是否存在任何一个选项的 checked 为 true + const hasCheckedOption = this.form.forms.some(item => { + // 如果 item.options 存在且包含 checked 为 true 的项 + return Array.isArray(item.options) && + item.options.some(option => option.checked === true); + }); + + // 如果存在 checked 为 true 的选项,直接返回 0 分 + if (hasCheckedOption) { + this.form.total_score = 0; + this.form.forms.map(item=>item.score=0) + return; + } + // 否则,正常累加所有项的分数 + const total = this.form.forms.reduce((sum, item) => { + // 将 item.score 转换为数字,非数字默认 0 + const score = Number(item.score) || 0; + return sum + score; + }, 0); + + this.form.total_score = total; + }, + // 判断是否为有效数子 + validateNumberRange(value, min, max) { + // 步骤1:判断值是否为有效数字(处理字符串形式的数字) + let num; + + // 如果已经是数字类型,直接使用 + if (typeof value === 'number') { + num = value; + } + // 如果是字符串,尝试转换为数字 + else if (typeof value === 'string') { + // 去除首尾空格 + const trimmed = value.trim(); + + // 使用 parseFloat 转换,并验证是否完全匹配数字格式 + num = parseFloat(trimmed); + + // 如果转换后不是NaN,且字符串完全等于转换后的数字字符串(确保没有多余字符) + if (isNaN(num) || trimmed !== String(num)) { + return false; + } + } + // 其他类型(如 null、object 等)直接判定为无效 + else { + return false; + } + + // 步骤2:检查数字是否在范围内(包含边界) + return num >= min && num <= max; + }, + /** + * 验证表单数据 + * @param {Array} forms - 表单数据数组 + * @returns {Object} - 验证结果 { isValid: Boolean, errorMsg: String } + */ + validateForm(forms) { + // 1. 检查是否存在特殊项(包含 options 且有 checked=true) + const specialItem = forms.find(item => + Array.isArray(item.options) && + item.options.some(option => option.checked === true) + ); + + // 如果存在特殊项被勾选,直接返回总分0 + if (specialItem) { + forms.map(item=>item.score = 0) + return { isValid: true, totalScore: 0, errorMsg: '' }; + } + + // 2. 验证其他项的分数是否有效(在 min 和 max 之间) + for (const item of forms) { + // 跳过特殊项 + if (Array.isArray(item.options)) continue; + + const { score, min, max } = item; + const parsedScore = parseFloat(score); + + // 检查分数是否为有效数字 + if (isNaN(parsedScore) || !isFinite(parsedScore)) { + return { isValid: false, totalScore: 0, errorMsg: `请输入有效的分数:${item.ask}` }; + } + + // 检查分数是否在范围内 + if (parsedScore < min || parsedScore > max) { + return { isValid: false, totalScore: 0, errorMsg: `分数超出范围:${item.ask} (${min}-${max}分)` }; + } + } + // 3. 计算总分(当所有项都有效时) + const totalScore = forms.reduce((sum, item) => { + // 跳过特殊项(已处理) + if (Array.isArray(item.options)) return sum; + + // 累加有效分数 + return sum + parseFloat(item.score); + }, 0); + + return { isValid: true, totalScore, errorMsg: '' }; }, submit() { - console.log("this.form", this.form) - const res = this.validateForm(this.form.forms) - if(!res.isValid){ - uni.showToast({ - title:res.errorMsg, - icon:'none', - }) - return + console.log("this.form", this.form) + const res = this.validateForm(this.form.forms) + if(!res.isValid){ + uni.showToast({ + title:res.errorMsg, + icon:'none', + }) + return } const uploadSignImage = () => { return new Promise((resolve, reject) => { @@ -541,13 +647,13 @@ icon: 'success', title: '保存成功', }) - setTimeout(() => { - if(this.type==='edit'){ - uni.redirectTo({ - url:'/package_sub/pages/quality/qualityHistory' - }) - }else{ - uni.navigateBack() + setTimeout(() => { + if(this.type==='edit'){ + uni.redirectTo({ + url:'/package_sub/pages/quality/qualityHistory' + }) + }else{ + uni.navigateBack() } this.$u.vuex('vuex_admin_sign_image', '') @@ -570,7 +676,7 @@ this.detail = res; for (let key in this.form) { this.form[key] = res[key] - } + } this.form.customer_name = res.customer.name console.log(this.form) this.fileList = res.files.map(i => ({ diff --git a/package_sub/pages/index/index.vue b/package_sub/pages/index/index.vue index 324405f..546575c 100644 --- a/package_sub/pages/index/index.vue +++ b/package_sub/pages/index/index.vue @@ -46,7 +46,7 @@ - 质控列表 + 质控回访 diff --git a/package_sub/pages/quality/quality.vue b/package_sub/pages/quality/quality.vue index d422af6..62bf821 100644 --- a/package_sub/pages/quality/quality.vue +++ b/package_sub/pages/quality/quality.vue @@ -6,17 +6,37 @@ 刷新 - - - - + + + + + + + + + - - + + + + + + + + + + + + + + + + @@ -24,9 +44,9 @@ 回访次数 {{ item.quality_callbacks_count }} - 回访次数 {{ 0 }} 次 + 回访次数 {{ 0 }} 次 - {{ item.product_type.name }} + {{ item.product_type_name }} @@ -65,7 +85,7 @@ - 联系人 {{ item.contact_name }} + 联系人 {{ item.contact_name?item.contact_name:'' }} @@ -93,6 +113,11 @@ + + + + 附近客户 + @@ -104,10 +129,20 @@ export default { scrollTop: 0, isShowCalendar: false, inputStyle: { - width: "600rpx", + width: "100%", fontSize: "28rpx", fontWeight: "500" }, + callbackDropdownOptions: [ + { label: '全部', value: 'all' }, + { label: '无回访', value: 0 }, + { label: '有回访', value: 1 }, + ], + deadDropdownOptions: [ + { label: '全部', value: 'all' }, + { label: '正常', value: 0 }, + { label: '死亡', value: 1 }, + ], optionsStatus: [{ label: '全部', value: '' @@ -151,7 +186,12 @@ export default { select: { page_size: 10, page: 1, - keyword: '' + keyword: '', + village_name: '', + has_quality_callbacks: 'all', + is_dead: 'all', + lat: '', + lng: '' }, } }, @@ -161,7 +201,12 @@ export default { this.select = { page_size: 10, page: 1, - keyword: '' + keyword: '', + village_name: '', + has_quality_callbacks: 'all', + is_dead: 'all', + lat: '', + lng: '' } this.nursingList = [] this.getList() @@ -175,8 +220,37 @@ export default { this.select.page = 1 this.$u.debounce(this.getList, 1000) }, + villageInput(e) { + this.select.village_name = e + + this.nursingList = [] + this.select.page = 1 + this.$u.debounce(this.getList, 1000) + }, + + async fetchNearby() { + try { + const res = await uni.getLocation({ type: 'gcj02', isHighAccuracy: true }) + // uni.getLocation Promise 风格返回 [err, data] + const location = Array.isArray(res) ? res[1] : res + if (location && location.latitude && location.longitude) { + this.select.lat = location.latitude + this.select.lng = location.longitude + this.nursingList = [] + this.select.page = 1 + this.getList() + } else { + uni.showToast({ icon: 'none', title: '定位失败,请稍后重试' }) + } + } catch (e) { + uni.showToast({ icon: 'none', title: '未能获取定位权限' }) + } + }, async getList() { - const response = await this.$u.api.adminCustomerList(this.select) + const params = { ...this.select } + if (params.has_quality_callbacks === 'all') params.has_quality_callbacks = '' + if (params.is_dead === 'all') params.is_dead = '' + const response = await this.$u.api.adminCustomerList(params) console.log("response",response) let res = response.data if (res.data.length > 0 && res.data) { @@ -221,6 +295,16 @@ export default { } }, watch: { + 'select.has_quality_callbacks'(val) { + this.nursingList = [] + this.select.page = 1 + this.getList() + }, + 'select.is_dead'(val) { + this.nursingList = [] + this.select.page = 1 + this.getList() + } }, onReachBottom() { this.select.page++ @@ -241,11 +325,11 @@ export default {