master
lion 3 months ago
parent aad13901ae
commit 766572da5e

@ -1,10 +1,10 @@
const mode = process.env.NODE_ENV;
//const mode = 'development';
// const mode = process.env.NODE_ENV;
const mode = 'development';
let ROOTPATH = ''; //域名
switch (mode) {
case 'development':
// ROOTPATH = "http://sstt.ali251.langye.net"
ROOTPATH = "https://sstt.115.langye.net"
ROOTPATH = "http://sstt.ali251.langye.net"
// ROOTPATH = "https://sstt.115.langye.net"
break;
case 'production':
ROOTPATH = "https://sstt.115.langye.net"

@ -35,6 +35,12 @@ let apiAdmin = {
saveLongInsurance: "/api/admin/long-insurance/save",
longInsuranceList: "/api/admin/long-insurance/get-list",
longInsuranceDetail: "/api/admin/long-insurance/get-form/",
qualityCustomer:"/api/admin/schedule/customer-list",
qualityList:"/api/admin/quality-callbacks/get-list",
qualityDetail:"/api/admin/quality-callbacks/get-form/",
saveQuality:"/api/admin/quality-callbacks/save",
qualityDel:'/api/admin/quality-callbacks/delete/'
}
// 此处第二个参数vm就是我们在页面使用的this你可以通过vm获取vuex等操作
const install = (Vue, vm) => {
@ -71,6 +77,13 @@ const install = (Vue, vm) => {
let adminSaveLongInsurance = (data) => vm.$u.post(apiAdmin.saveLongInsurance, data);
let adminLongInsuranceList = (data) => vm.$u.get(apiAdmin.longInsuranceList, data);
let adminLongInsuranceDetail = (id) => vm.$u.get(apiAdmin.longInsuranceDetail + id);
let adminSaveQuality = (data) => vm.$u.post(apiAdmin.saveQuality, data);
let adminQualityList = (data) => vm.$u.get(apiAdmin.qualityList, data);
let adminQualityDetail = (id) => vm.$u.get(apiAdmin.qualityDetail + id);
let adminQualityCustomer = (data) => vm.$u.get(apiAdmin.qualityCustomer, data);
// 将各个定义的接口名称统一放进对象挂载到vm.$u.api(因为vm就是this也即this.$u.api)下
vm.$u.api = {
login,
@ -102,7 +115,11 @@ const install = (Vue, vm) => {
adminProductTypeList,
adminSaveLongInsurance,
adminLongInsuranceList,
adminLongInsuranceDetail
adminLongInsuranceDetail,
adminSaveQuality,
adminQualityList,
adminQualityDetail,
adminQualityCustomer
};
};

@ -0,0 +1,602 @@
<template>
<view>
<cpn-navbar title="质控回访" :is-back="true"></cpn-navbar>
<view class="container">
<u-form :model="form" ref="uForm" :label-width="200" :error-type="['message', 'border-bottom']">
<u-form-item label="日期" prop="date" label-position="left">
<u-input v-model="form.date" disabled type="text"></u-input>
</u-form-item>
<u-form-item label="客户" prop="customer_name" label-position="left">
<u-input v-model="form.customer_name" disabled type="text"></u-input>
</u-form-item>
<u-form-item label="护理员" prop="nurse_name" label-position="left">
<u-input v-model="form.nurse_name" type="text" placeholder="请输入护理员"></u-input>
</u-form-item>
<u-form-item :required="item.type==='checkbox'?false:true" v-for="(item,index) in form.forms" :label="item.ask" :prop="index" label-position="top">
<u-checkbox-group v-if="item.type==='checkbox'" ref="serve" @change="e => checkboxChange(e)">
<u-checkbox v-for="(item1, index1) in item.options" :key="index1" :name="item1.name"
v-model="item1.checked">
{{ item1.name }}
</u-checkbox>
</u-checkbox-group>
<u-input v-else @blur="blurInput(item)" v-model="item.score" type="number" placeholder="请输入分值"></u-input>
</u-form-item>
<u-form-item label="总分(得90分及以上为及格)" prop="total_score" label-position="top">
<u-input v-model="form.total_score" disabled type="text"></u-input>
</u-form-item>
<u-form-item label="对护理员哪些方面要求改进" prop="tip" label-position="top">
<u-input v-model="form.tip" type="textarea"></u-input>
</u-form-item>
<u-form-item label="对护理员有哪些方面肯定" prop="sure" label-position="top">
<u-input v-model="form.sure" type="textarea"></u-input>
</u-form-item>
<u-form-item label="备注" prop="remark" label-position="top">
<u-input v-model="form.remark" type="textarea"></u-input>
</u-form-item>
<u-form-item label="回访图片">
<u-upload ref="uUpload" :custom-btn="true" :action="action" :file-list="fileList"
:source-type="['camera']">
<view slot="addBtn" class="slot-btn" hover-class="slot-btn__hover" hover-stay-time="150">
<u-icon name="camera" size="60" color="#c0c4cc" label="点击拍摄" label-pos="bottom"></u-icon>
</view>
</u-upload>
</u-form-item>
<u-form-item label="服务对象/家属签名" required prop="sign_image_id">
<view>
<u-button type="primary" size="mini" :throttle-time="3000"
@click="pageTo('/pages/sign/sign?key=vuex_sign_image')">点击签名</u-button>
<image v-if="vuex_sign_image || (detail.sign_image && detail.sign_image.url)"
:src="vuex_sign_image || (detail.sign_image && detail.sign_image.url)"
style="width: 260rpx;object-fit: cover;transform: rotate(270deg);"
@click="showimg(vuex_sign_image || (detail.sign_image && detail.sign_image.url))"></image>
</view>
</u-form-item>
<u-form-item label="调查人员签名" required prop="admin_sign_image_id">
<view>
<u-button type="primary" size="mini" :throttle-time="3000"
@click="pageTo('/pages/sign/sign?key=vuex_admin_sign_image')">点击签名</u-button>
<image v-if="vuex_admin_sign_image || (detail.admin_sign_image && detail.admin_sign_image.url)"
:src="vuex_admin_sign_image || (detail.admin_sign_image && detail.admin_sign_image.url)"
style="width: 260rpx;object-fit: cover;transform: rotate(270deg);"
@click="showimg(vuex_admin_sign_image || (detail.admin_sign_image && detail.admin_sign_image.url))">
</image>
</view>
</u-form-item>
<u-form-item label="所在位置">
<view>
<u-button type="primary" size="mini" :throttle-time="3000" @click="getLoaction"></u-button>
<view style="word-break: break-all;">
{{ `${(form.longitude || form.latitude) ? ('(' + form.longitude + ',' + form.latitude + ')') : ''}${form.address}` }}
</view>
</view>
</u-form-item>
</u-form>
<u-button type="primary" @click="submit"></u-button>
</view>
</view>
</template>
<script>
import QQMapWX from '@/libs/qqmap-wx-jssdk.js'
import {
ROOTPATH
} from "@/common/config"
import moment from "@/libs/moment.min";
export default {
data() {
return {
action: `${ROOTPATH}/api/admin/upload-file`,
fileList: [],
id: "",
type: "add",
detail: {},
form: {
customer_id: "",
customer_name:'',
date: '',
nurse_name: '',
forms: [{
ask: '执证上岗(工作证完整无损且佩戴在身)(2分)',
max: 2,
min: 0,
score: ''
}, {
ask: '工作服整洁、穿戴整齐(3分)',
max: 3,
min: 0,
score: ''
}, {
ask: '仪容仪表(指甲、束发)(2分)',
max: 2,
min: 0,
score: ''
}, {
ask: '对应本次服务所需工具齐全、性能完好(3分)',
max: 3,
min: 0,
score: ''
}, {
ask: '上门核对客户信息正确;地址、电话正确(5分)',
max: 5,
min: 0,
score: ''
}, {
ask: '告知客户可以提供的服务项目及本次上门服务的时长(5分)',
max: 3,
min: 0,
score: ''
}, {
ask: '客户选择本次服务项目(合法合规)(5分)',
max: 5,
min: 0,
score: ''
}, {
ask: '服务态度积极主动,不推托,不生硬拒绝(微笑服务)(5分)',
max: 5,
min: 0,
score: ''
}, {
ask: '服务内容、纸质工单、系统派单三者一致(个性化服务最后)(5分)',
max: 5,
min: 0,
score: ''
}, {
ask: '工单服务项目能保质保量完成(质量标准参考入户检查表)(50分)',
max: 50,
min: 0,
score: ''
}, {
ask: '抽查背诵一项服务流程(5分)',
max: 5,
min: 0,
score: ''
}, {
ask: '评估现场服务流程准确(5分)',
max: 5,
min: 0,
score: ''
}, {
ask: '能准确知晓并说出公司全称(每次上门先介绍)(5分)',
max: 5,
min: 0,
score: ''
}, {
ask: '每次上门服务前都提前预约(至少提前一天预约)(5分)',
max: 5,
min: 0,
score: ''
}, {
ask: '服务前后对比明显,所需部位消毒及时准确(5分)',
max: 5,
min: 0,
score: ''
}, {
ask: '所需服务技能娴熟欠缺一项扣2分扣完为止(10分)',
max: 10,
min: 0,
score: ''
}, {
ask: '其他(涉及此类一律按照 0 分处理)',
max: 10,
type: 'checkbox',
min: 0,
score: '',
options: [{
name: '服务中存在人身及环境安全',
checked: false,
}, {
name: '服务中脱岗',
checked: false,
}, {
name: '无正当理由不提供服务',
checked: false,
}, {
name: '恶意消耗工时、虚假工单、重叠工单、人卡不符、多卡一户现象',
checked: false,
}, {
name: '拒绝客户提出的正当要求',
checked: false,
}, {
name: '替代客户签名',
checked: false,
}, {
name: '存在向客户推销非官方商品、索要物品或货币行为',
checked: false,
}, {
name: '不积极配合公司稽查',
checked: false,
}, {
name: '违反公司原则性规章制度行为',
checked: false,
}]
}],
total_score: '',
tip: '',
sure: "",
remark: '',
file_ids: [],
sign_image_id: "",
admin_sign_image_id: "",
latitude: "",
longitude: "",
address: "",
},
rules: {
sign_image_id: [{
validator: (rule, value, callback) => {
if (this.vuex_sign_image || value) {
callback()
} else {
callback(new Error('请签名'))
}
}
}],
admin_sign_image_id: [{
validator: (rule, value, callback) => {
if (this.vuex_admin_sign_image || value) {
callback()
} else {
callback(new Error('请签名'))
}
}
}]
},
};
},
watch: {
// forms
'form.forms': {
handler() {
this.calculateTotalScore();
},
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: {
showimg(url) {
if (url)
this.$showimg({
imgs: [url],
current: 0
})
},
pageTo(url) {
uni.navigateTo({
url
})
},
load() {
this.qqmapsdk = new QQMapWX({
key: 'I5FBZ-LMN33-BK63F-OGUO7-XE3JK-WJBP5'
});
},
getLoaction() {
return new Promise((resolve, reject) => {
uni.getLocation({
type: 'gcj02',
isHighAccuracy: true
}).then(res => {
if (res[1]) {
this.form.latitude = res[1]?.latitude
this.form.longitude = res[1]?.longitude
this.qqmapsdk.reverseGeocoder({
location: {
latitude: this.form.latitude,
longitude: this.form.longitude
},
success: (res) => {
this.form.address = res.result.address + res.result
.formatted_addresses.recommend
console.log(this.form)
resolve(res)
},
fail: (err) => {
reject(err)
}
})
} else {
uni.showToast({
icon: 'none',
title: '操作频繁,请稍后再试'
})
reject(res)
console.log(res);
}
})
})
},
checkboxChange(e, item) {
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;
}
}
// nullobject
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
}
const uploadSignImage = () => {
return new Promise((resolve, reject) => {
if (this.vuex_sign_image) {
uni.uploadFile({
url: `${ROOTPATH}/api/admin/upload-file`,
header: {
Authorization: `Bearer ${this.vuex_token}`
},
filePath: this.vuex_sign_image,
name: 'file',
success: (res) => {
if (res.statusCode === 200) {
const response = JSON.parse(res.data)
resolve(response)
} else {
reject(res.data)
}
},
fail: (err) => {
reject(err)
}
})
} else {
resolve()
}
})
}
const uploadAdminSignImage = () => {
return new Promise((resolve, reject) => {
if (this.vuex_admin_sign_image) {
uni.uploadFile({
url: `${ROOTPATH}/api/admin/upload-file`,
header: {
Authorization: `Bearer ${this.vuex_token}`
},
filePath: this.vuex_admin_sign_image,
name: 'file',
success: (res) => {
if (res.statusCode === 200) {
const response = JSON.parse(res.data)
resolve(response)
} else {
reject(res.data)
}
},
fail: (err) => {
reject(err)
}
})
} else {
resolve()
}
})
}
this.$refs.uForm.validate(valid => {
if (valid) {
Promise.all([uploadSignImage(), uploadAdminSignImage()]).then(res => {
console.log(res)
if (res[0]?.id) {
this.form.sign_image_id = res[0].id
}
if (res[1]?.id) {
this.form.admin_sign_image_id = res[1].id
}
this.form.file_ids = this.$refs.uUpload.lists.filter(i => i.progress === 100)
.map(i => i.response?.id).filter(i => i)
if (this.type === 'add') {
delete this.form.id
} else {
this.form.id = this.id
}
this.$u.api.adminSaveQuality(this.form).then(res => {
uni.showToast({
icon: 'success',
title: '保存成功',
})
setTimeout(() => {
if(this.type==='edit'){
uni.redirectTo({
url:'/package_sub/pages/quality/qualityHistory'
})
}else{
uni.navigateBack()
}
this.$u.vuex('vuex_admin_sign_image', '')
this.$u.vuex('vuex_sign_image', '')
}, 1500)
})
}).catch(err => {
console.error(err)
uni.showToast({
title: "签名保存失败",
icon: "none"
})
})
}
})
},
async getDetail() {
const res = await this.$u.api.adminQualityDetail(this.id)
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 => ({
url: i.url,
response: i
}))
}
},
}
</script>
<style lang="scss">
.container {
border-radius: 10rpx;
background: #FFFFFF;
box-shadow: 0rpx 4rpx 10rpx 0rpx rgba(219, 218, 218, 0.5);
margin: 20rpx;
padding: 20rpx;
}
.slot-btn {
width: 200rpx;
height: 200rpx;
display: flex;
justify-content: center;
align-items: center;
background: rgb(244, 245, 246);
border: 2rpx #108cff solid;
border-radius: 10rpx;
box-sizing: content-box;
filter: drop-shadow(0 0 4rpx #0fc7ff) drop-shadow(0 0 6rpx #00eaff);
}
.slot-btn__hover {
background-color: rgb(235, 236, 238);
}
</style>

@ -44,6 +44,10 @@
<u-icon :name="item.icon || 'list'" :size="46"></u-icon>
<view class="grid-text">{{ item.label }}</view>
</u-grid-item>
<u-grid-item v-if="isQuality" :index="'/package_sub/pages/quality/quality'" :key="5">
<u-icon :name="'list'" :size="46"></u-icon>
<view class="grid-text">质控列表</view>
</u-grid-item>
</u-grid>
</view>
@ -63,6 +67,7 @@ export default {
return {
marginTop: 96,
swiperIndex: 0,
isQuality:false,
grids: [
{
label: "护理列表",
@ -87,7 +92,8 @@ export default {
{
label: "长护险回访",
url: "/package_sub/pages/longInsurance/longInsurance"
}
},
]
}
},
@ -121,7 +127,10 @@ export default {
}
},
async onShow() {
console.log("user",this.vuex_user)
if(this.vuex_user){
this.isQuality = this.vuex_user.role.some(item => item.name === '质控员');
}
},
created() {
const res = uni.getSystemInfoSync()

@ -0,0 +1,508 @@
<template>
<view>
<cpn-navbar title="客户列表" :is-back="true"></cpn-navbar>
<view>
<view>
<u-button :custom-style="{'margin':'24rpx 24rpx 0 24rpx'}" type="warning" @click="refresh"></u-button>
</view>
<!-- 搜索 -->
<view class="search">
<view class="input-content">
<u-input :clearable="false" :value="select.keyword" placeholder="请输入要搜索的内容" height="30" :custom-style="inputStyle"
@input="searchInput"></u-input>
</view>
<view class="icon">
<u-icon name="search" size="46" color="#ABAEBE"></u-icon>
</view>
</view>
<!-- 护理列表 -->
<view class="nursing-list">
<view v-if="nursingList && nursingList.length > 0">
<view v-for="(item,index) in nursingList" :key="index" class="list-item">
<view class="top">
<view v-if="item.customer.quality_callbacks_count>0" @click="pageTo('/package_sub/pages/quality/qualityHistory?customer_id='+item.customer_id)" class="time">
回访次数 <text style="color:#2979ff">{{ item.customer.quality_callbacks_count }}</text> </view>
<view v-else class="time">回访次数 {{ 0 }} </view>
<view class="status">
<view class="status-text">{{ item.product.product_type.name }}</view>
</view>
</view>
<view class="line"></view>
<view class="center">
<view class="avatar">
<u-image :src="item.customer.sex === '男' ? vuex_male_img : vuex_female_img" height="104"
width="104" shape="circle"></u-image>
</view>
<view class="user-info">
<view class="name">{{item.customer.name}}</view>
<view class="tel">
<view>
<u-icon name="phone" size="28" color="#1479FF"></u-icon>
</view>
<view class="text">{{item.customer.phone || '无'}}</view>
</view>
<view class="address">
<view>
<u-icon name="map" size="28" color="#1479FF"></u-icon>
</view>
<view class="text">{{defaultAddress(item.customer.customer_address).address || '无'}}</view>
</view>
<view class="age">
<view>
<u-icon name="calendar" size="28" color="#1479FF"></u-icon>
</view>
<view class="text">{{getAgeByIdcard(item.customer.idcard) || '-'}}</view>
</view>
<view class="dislevel">
<view>
<u-icon name="eye-off" size="28" color="#1479FF"></u-icon>
</view>
<view class="text">失能等级{{ item.customer.level_type_detail ? item.customer.level_type_detail.value : '' }} {{ item.customer.level_detail ? item.customer.level_detail.value : '' }}</view>
</view>
<view class="contact">
<view>
<u-icon name="man-add-fill" size="28" color="#1479FF"></u-icon>
</view>
<view class="text">联系人 {{ item.customer.contact_name }}</view>
</view>
</view>
<view class="sex">
<view class="sex-text">
{{item.customer.sex || '无'}}
</view>
</view>
</view>
<view class="bottom">
<u-button style="width: 90%;margin: 10rpx auto 0;"
type="primary"
ripple
@click="pageTo('/package_sub/pages/addQuality/addQuality?customer_id='+item.customer_id+'&customer_name='+item.customer.name)">质控回访</u-button>
</view>
</view>
</view>
<view v-else>
<u-empty mode="list" text="暂无记录"></u-empty>
</view>
</view>
</view>
<u-loadmore :status="status" :load-text='loadText' @loadmore='select.page++,getList()' />
<u-back-top :scroll-top="scrollTop"></u-back-top>
</view>
</template>
<script>
import { getAgeByIdcard } from "@/common/util";
export default {
data() {
return {
scrollTop: 0,
isShowCalendar: false,
inputStyle: {
width: "600rpx",
fontSize: "28rpx",
fontWeight: "500"
},
optionsStatus: [{
label: '全部',
value: ''
},
{
label: '待护理',
value: '0',
},
{
label: '护理中',
value: 1,
},
{
label: '已护理',
value: 2,
},
],
optionsSex: [{
label: '全部',
value: ''
},
{
label: '男',
value: '男',
},
{
label: '女',
value: '女',
},
],
status: 'loadmore',
loadText: {
loadmore: '轻轻上拉或点击',
loading: '努力加载中',
nomore: '实在没有了'
},
nursingList: [],
select: {
page_size: 10,
page: 1,
keyword: ''
},
}
},
methods: {
getAgeByIdcard,
refresh(){
this.select = {
page_size: 10,
page: 1,
keyword: ''
}
this.nursingList = []
this.getList()
},
searchInput(e) {
this.select.keyword = e
this.nursingList = []
this.select.page = 1
this.$u.debounce(this.getList, 1000)
},
async getList() {
const response = await this.$u.api.adminQualityCustomer(this.select)
console.log("response",response)
let res = response.list
if (res.data.length > 0 && res.data) {
this.nursingList.push(...res.data)
this.status = 'loadmore'
} else {
this.status = 'nomore'
if (this.select.page > 1) {
this.select.page--
}
}
},
pageTo (url) {
uni.navigateTo({
url
})
}
},
computed: {
statusComputed() {
return function(status) {
switch (status) {
case '0':
return "待护理"
break;
case 1:
return "护理中"
break;
case 2:
return "已护理"
break;
default:
return '全部'
}
}
},
defaultAddress () {
return function (address) {
return address&&address.length>0?address[0]: {}
}
}
},
watch: {
},
onReachBottom() {
this.select.page++
this.status = 'loading';
this.getList()
},
onShow() {
this.select.page = 1
this.nursingList = []
this.getList()
},
onPageScroll(e) {
this.scrollTop = e.scrollTop;
}
}
</script>
<style scoped lang="scss">
//
/deep/.u-dropdown__menu {
justify-content: space-evenly !important;
}
/deep/.u-dropdown__menu__item {
width: 224rpx !important;
flex: none !important;
height: 70rpx;
background: #FFFFFF;
border-radius: 10rpx;
justify-content: space-between !important;
}
/deep/.u-dropdown__menu__item .u-flex {
flex: 1;
}
/deep/.u-dropdown__menu__item__text {
flex: 1 !important;
font-size: 32rpx !important;
font-weight: 500 !important;
color: #333333 !important;
text-align: center;
padding: 12rpx 0rpx !important;
}
/deep/.u-dropdown__menu__item__arrow {
width: 70rpx !important;
height: 70rpx !important;
background: rgba(20, 121, 255, 0.1) !important;
border-radius: 10rpx !important;
border: 2rpx solid #FFFFFF !important;
display: flex;
justify-content: center;
align-items: center;
}
/deep/.u-load-more-wrap {
padding: 10px 0;
}
.search {
width: 710rpx;
height: 70rpx;
background: #FFFFFF;
border: 2rpx solid #FAFBFC;
display: flex;
align-items: center;
justify-content: space-between;
margin: 24rpx 24rpx 0 24rpx;
.input-content {
margin-left: 30rpx;
}
.icon {
margin-right: 30rpx;
}
}
.drop-down {
margin-top: 20rpx;
}
.nursing-list {
margin-top: 10rpx;
.list-item {
width: 710rpx;
background: #FFFFFF;
box-shadow: 0rpx 4rpx 10rpx 0rpx rgba(219, 218, 218, 0.5);
margin: 0 20rpx 26rpx 22rpx;
.top {
display: flex;
justify-content: space-between;
align-items: center;
padding: 22rpx 0;
.time {
font-size: 32rpx;
font-weight: 500;
color: #36596A;
line-height: 32rpx;
padding-left: 20rpx;
}
.status {
display: flex;
align-items: center;
.status-icon {
margin-right: 10rpx;
}
.status-text {
height: 40rpx;
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
margin-right: 20rpx;
}
}
}
.line {
width: 670rpx;
height: 2rpx;
border: 2rpx solid #EEEFF5;
margin: 0 auto;
}
.center {
display: flex;
justify-content: space-between;
padding-top: 24rpx;
.avatar {
padding-top: 4rpx;
padding-left: 20rpx;
}
.user-info {
flex: 1;
padding-left: 24rpx;
.name {
width: 270rpx;
height: 48rpx;
font-size: 32rpx;
font-weight: 500;
color: #333333;
}
.tel {
height: 40rpx;
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
display: flex;
margin-top: 18rpx;
.text {
margin-left: 10rpx;
}
}
.address {
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
display: flex;
margin-top: 14rpx;
.text {
margin-left: 10rpx;
}
}
}
.sex {
width: 40rpx;
height: 40rpx;
background: #FDECEC;
opacity: 0.5;
display: flex;
justify-content: center;
align-items: center;
margin-right: 20rpx;
.sex-text {
width: 28rpx;
height: 34rpx;
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 34rpx;
}
}
.age {
@extend .address;
}
.dislevel {
@extend .address;
}
.contact {
@extend .address;
}
}
.bottom {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16rpx;
padding-bottom: 26rpx;
.distance {
align-items: center;
display: flex;
.distance-icon {
padding-left: 20rpx;
}
.distance-text {
height: 34rpx;
font-size: 24rpx;
font-weight: 500;
color: #A7AFBC;
line-height: 34rpx;
padding-left: 20rpx;
}
}
.to-there {
display: flex;
align-items: center;
margin-right: 20rpx;
.to-there-text {
height: 34rpx;
font-size: 24rpx;
font-weight: 500;
color: #A7AFBC;
line-height: 34rpx;
margin-right: 14rpx;
}
.to-there-icon {
margin-left: 3rpx;
}
}
}
}
}
</style>

@ -0,0 +1,362 @@
<template>
<view>
<cpn-navbar title="回访详情" :is-back="true"></cpn-navbar>
<view class="container">
<view class="nursing-list">
<view class="list-item">
<view class="top">
<view class="time">{{detail.date}}</view>
</view>
<!-- <view class="line"></view> -->
<!-- <view style="padding: 20rpx 40rpx 0;font-size: 32rpx;line-height: 2;font-weight: 600;">
被护理人
</view>
<view class="center" v-if="detail.customer">
<view class="avatar">
<u-image :src="((detail.customer && detail.customer.idcard) ? getSexByIdcard(detail.customer.idcard) : '无') === '男' ? vuex_male_img : vuex_female_img" height="104"
width="104" shape="circle"></u-image>
</view>
<view class="user-info">
<view class="name">{{ detail.customer ? detail.customer.name : "" }}</view>
<view class="tel">
<view>
<u-icon name="phone" size="28" color="#1479FF"></u-icon>
</view>
<view class="text">{{ detail.customer ? detail.customer.phone : '无'}}</view>
</view>
<view class="address">
<view>
<u-icon name="map" size="28" color="#1479FF"></u-icon>
</view>
<view class="text">{{ detail.customer_address ? detail.customer_address.address : '无'}}</view>
</view>
</view>
<view class="sex">
<view class="sex-text">
{{ (detail.customer && detail.customer.idcard) ? getSexByIdcard(detail.customer.idcard) : '无' }}
</view>
</view>
</view> -->
<view class="line" style="margin-top: 22rpx;"></view>
<view class="bottom">
<view class="bottom-item">
<view class="bottom-item__title">护理员</view>
<view class="bottom-item__value">{{ detail.nurse_name?detail.nurse_name:'' }}</view>
</view>
<view class="bottom-item" v-for="item in detail.forms">
<block v-if="item.type==='checkbox'">
<view class="bottom-item__title">{{item.ask}}</view>
<view class="bottom-item__value">{{ joinSelectedOptions(item.options) }}</view>
</block>
<block v-else>
<view class="bottom-item__title">{{item.ask}}</view>
<view class="bottom-item__value">分值{{ item.score }}</view>
</block>
</view>
<view class="bottom-item">
<view class="bottom-item__title">总分(得90分及以上为及格)</view>
<view class="bottom-item__value">{{ detail.total_score }}</view>
</view>
<view class="bottom-item">
<view class="bottom-item__title">对护理员哪些方面要求改进</view>
<view class="bottom-item__value">{{ detail.tip?detail.tip:'' }}</view>
</view>
<view class="bottom-item">
<view class="bottom-item__title">对护理员有哪些方面肯定</view>
<view class="bottom-item__value">{{ detail.sure?detail.sure:''}}</view>
</view>
<view class="bottom-item">
<view class="bottom-item__title">备注</view>
<view class="bottom-item__value">{{ detail.remark?detail.remark:'' }}</view>
</view>
<view class="bottom-item">
<view class="bottom-item__title">回访地址</view>
<view class="bottom-item__value">
{{ `${(detail.longitude || detail.latitude) ? ('(' + detail.longitude + ',' + detail.latitude + ')') : ''}${detail.address?detail.address:''}` }}
</view>
</view>
<view class="bottom-item">
<view class="bottom-item__title">回访图片</view>
<view class="bottom-item__value">
<u-image :width="150"
:height="150"
style="margin: 5rpx;"
:border-radius="8"
v-for="img in detail.files"
:key="img.id"
:src="img.url"
@click="showimg(img.url)"></u-image>
</view>
</view>
<view class="bottom-item sign">
<view class="bottom-item__title">服务对象/家属签名</view>
<view class="bottom-item__value">
<u-image :width="300"
:height="300"
style="margin-bottom: 10rpx;"
:border-radius="8"
:src="detail.sign_image ? detail.sign_image.url : ''"
@click="showimg(detail.sign_image ? detail.sign_image.url : '')"></u-image>
</view>
</view>
<view class="bottom-item sign">
<view class="bottom-item__title">调查人员签名</view>
<view class="bottom-item__value">
<u-image :width="300"
:height="300"
style="margin-bottom: 10rpx;"
:border-radius="8"
:src="detail.admin_sign_image ? detail.admin_sign_image.url : ''"
@click="showimg(detail.admin_sign_image ? detail.admin_sign_image.url : '')"></u-image>
</view>
</view>
</view>
</view>
</view>
<u-button class="operate-item"
:custom-style="{ width: '90%', 'margin-left': '5%' }"
type="primary"
size="medium"
ripple
@click="pageTo('/package_sub/pages/addQuality/addQuality' + '?customer_id=' + detail.customer_id + '&id=' + detail.id)">编辑</u-button>
</view>
</view>
</template>
<script>
import { getSexByIdcard } from "@/common/util"
export default {
data() {
return {
id: "",
detail: {},
};
},
methods: {
showimg(url) {
if (url)
this.$showimg({
imgs: [url],
current: 0
})
},
getSexByIdcard,
pageTo (url) {
uni.navigateTo({
url
})
},
async getDetail () {
const res = await this.$u.api.adminQualityDetail(this.id)
console.log(res)
this.detail = res;
},
/**
* 拼接选中项的名称
* @param {Array} options - 选项数组每个元素包含 name checked 属性
* @returns {string} - 选中项的名称用逗号拼接的字符串若无选中项则返回空字符串
*/
joinSelectedOptions(options) {
// checked true
const selectedOptions = options.filter(option => option.checked === true);
// name 使
return selectedOptions.map(option => option.name).join('');
}
},
onLoad(option) {
this.id = option.id;
this.getDetail()
},
}
</script>
<style lang="scss">
.container {
padding-bottom: 20rpx;
}
.nursing-list {
margin-top: 20rpx;
.list-item {
width: 710rpx;
background: #FFFFFF;
box-shadow: 0rpx 4rpx 10rpx 0rpx rgba(219, 218, 218, 0.5);
margin: 0 20rpx 26rpx 22rpx;
.top {
display: flex;
justify-content: space-between;
align-items: center;
padding: 22rpx 0;
.time {
height: 40rpx;
font-size: 40rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
padding-left: 20rpx;
}
.status {
display: flex;
align-items: center;
.status-icon {
margin-right: 10rpx;
}
.status-text {
width: 84rpx;
height: 40rpx;
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
margin-right: 20rpx;
}
}
}
.line {
width: 670rpx;
height: 2rpx;
border: 2rpx solid #EEEFF5;
margin: 0 auto;
}
.center {
display: flex;
justify-content: space-between;
padding-top: 24rpx;
.avatar {
padding-top: 4rpx;
padding-left: 20rpx;
}
.user-info {
flex: 1;
padding-left: 24rpx;
.name {
width: 270rpx;
height: 48rpx;
font-size: 32rpx;
font-weight: 500;
color: #333333;
}
.tel {
height: 40rpx;
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
display: flex;
margin-top: 18rpx;
.text {
margin-left: 10rpx;
}
}
.address {
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
display: flex;
margin-top: 14rpx;
.text {
margin-left: 10rpx;
}
}
}
.sex {
width: 40rpx;
height: 40rpx;
background: #FDECEC;
opacity: 0.5;
display: flex;
justify-content: center;
align-items: center;
margin-right: 20rpx;
.sex-text {
width: 28rpx;
height: 34rpx;
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 34rpx;
}
}
}
.bottom {
margin-top: 16rpx;
padding: 10rpx 60rpx 26rpx;
&-item {
display: flex;
justify-content: space-between;
line-height: 2;
padding-left: 30rpx;
position: relative;
flex-wrap: wrap;
&__title {
flex-shrink: 0;
flex-basis: 100%;
}
&__value {
word-break: break-all;
font-weight: bolder;
display: flex;
}
&::before {
content: "";
width: 12rpx;
height: 12rpx;
border-radius: 100%;
background: #3877f6;
position: absolute;
top: calc(28rpx - 6rpx);
left: 0;
}
}
}
}
}
.sign ::v-deep .u-image {
transform: rotate(270deg);
}
</style>

@ -0,0 +1,408 @@
<template>
<view>
<cpn-navbar title="回访记录" :is-back="true"></cpn-navbar>
<view>
<view>
<u-button :custom-style="{'margin':'24rpx 24rpx 0 24rpx'}" type="warning" @click="refresh"></u-button>
</view>
<!-- 搜索 -->
<!-- <view class="search">
<view class="input-content">
<u-input :clearable="false" :value="select.keyword" placeholder="请输入要搜索的内容" height="30" :custom-style="inputStyle"
@input="searchInput"></u-input>
</view>
<view class="icon">
<u-icon name="search" size="46" color="#ABAEBE"></u-icon>
</view>
</view> -->
<!-- 护理列表 -->
<view class="nursing-list">
<view v-if="nursingList && nursingList.length > 0">
<view v-for="(item,index) in nursingList" :key="index" class="list-item"
@click="pageTo('/package_sub/pages/quality/qualityDetail?id='+item.id)">
<view class="top">
<view class="time">
回访日期 <text style="color:#2979ff">{{ item.date }}</text></view>
</view>
<view class="line"></view>
<view class="center">
<view class="user-info">
<view class="name">护理员{{item.nurse_name?item.nurse_name:''}}</view>
<view class="name">总分{{item.total_score?item.total_score:0}}</view>
<view class="name">备注</view>
<view class="name">{{item.remark?item.remark:''}}</view>
<view class="name">回访地址</view>
<view class="name">
{{ `${(item.longitude || item.latitude) ? ('(' + item.longitude + ',' + item.latitude + ')') : ''}${item.address?item.address:''}` }}
</view>
</view>
</view>
</view>
</view>
<view v-else>
<u-empty mode="list" text="暂无记录"></u-empty>
</view>
</view>
</view>
<u-loadmore :status="status" :load-text='loadText' @loadmore='select.page++,getList()' />
<u-back-top :scroll-top="scrollTop"></u-back-top>
</view>
</template>
<script>
import { getAgeByIdcard } from "@/common/util";
export default {
data() {
return {
scrollTop: 0,
isShowCalendar: false,
inputStyle: {
width: "600rpx",
fontSize: "28rpx",
fontWeight: "500"
},
status: 'loadmore',
loadText: {
loadmore: '轻轻上拉或点击',
loading: '努力加载中',
nomore: '实在没有了'
},
nursingList: [],
select: {
page_size: 10,
page: 1,
},
}
},
methods: {
getAgeByIdcard,
refresh(){
this.select = {
page_size: 10,
page: 1,
}
this.nursingList = []
this.getList()
},
async getList() {
const response = await this.$u.api.adminQualityList(this.select)
console.log("response",response)
let res = response
if (res.data.length > 0 && res.data) {
this.nursingList.push(...res.data)
this.status = 'loadmore'
} else {
this.status = 'nomore'
if (this.select.page > 1) {
this.select.page--
}
}
},
pageTo (url) {
uni.navigateTo({
url
})
}
},
computed: {
defaultAddress () {
return function (address) {
return address&&address.length>0?address[0]: {}
}
}
},
watch: {
},
onReachBottom() {
this.select.page++
this.status = 'loading';
this.getList()
},
onShow() {
this.select.page = 1
this.nursingList = []
this.getList()
},
onPageScroll(e) {
this.scrollTop = e.scrollTop;
}
}
</script>
<style scoped lang="scss">
//
/deep/.u-dropdown__menu {
justify-content: space-evenly !important;
}
/deep/.u-dropdown__menu__item {
width: 224rpx !important;
flex: none !important;
height: 70rpx;
background: #FFFFFF;
border-radius: 10rpx;
justify-content: space-between !important;
}
/deep/.u-dropdown__menu__item .u-flex {
flex: 1;
}
/deep/.u-dropdown__menu__item__text {
flex: 1 !important;
font-size: 32rpx !important;
font-weight: 500 !important;
color: #333333 !important;
text-align: center;
padding: 12rpx 0rpx !important;
}
/deep/.u-dropdown__menu__item__arrow {
width: 70rpx !important;
height: 70rpx !important;
background: rgba(20, 121, 255, 0.1) !important;
border-radius: 10rpx !important;
border: 2rpx solid #FFFFFF !important;
display: flex;
justify-content: center;
align-items: center;
}
/deep/.u-load-more-wrap {
padding: 10px 0;
}
.search {
width: 710rpx;
height: 70rpx;
background: #FFFFFF;
border: 2rpx solid #FAFBFC;
display: flex;
align-items: center;
justify-content: space-between;
margin: 24rpx 24rpx 0 24rpx;
.input-content {
margin-left: 30rpx;
}
.icon {
margin-right: 30rpx;
}
}
.drop-down {
margin-top: 20rpx;
}
.nursing-list {
margin-top: 10rpx;
.list-item {
width: 710rpx;
background: #FFFFFF;
box-shadow: 0rpx 4rpx 10rpx 0rpx rgba(219, 218, 218, 0.5);
margin: 0 20rpx 26rpx 22rpx;
.top {
display: flex;
justify-content: space-between;
align-items: center;
padding: 22rpx 0;
.time {
font-size: 32rpx;
font-weight: 500;
color: #36596A;
line-height: 32rpx;
padding-left: 20rpx;
}
.status {
display: flex;
align-items: center;
.status-icon {
margin-right: 10rpx;
}
.status-text {
height: 40rpx;
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
margin-right: 20rpx;
}
}
}
.line {
width: 670rpx;
height: 2rpx;
border: 2rpx solid #EEEFF5;
margin: 0 auto;
}
.center {
display: flex;
justify-content: space-between;
padding-top: 24rpx;
padding-bottom:24rpx;
.avatar {
padding-top: 4rpx;
padding-left: 20rpx;
}
.user-info {
flex: 1;
padding-left: 24rpx;
.name {
width: 100%;
// font-size: 32rpx;
margin-bottom:10rpx;
// font-weight: 500;
// color: #333333;
}
.tel {
height: 40rpx;
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
display: flex;
margin-top: 18rpx;
.text {
margin-left: 10rpx;
}
}
.address {
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 40rpx;
display: flex;
margin-top: 14rpx;
.text {
margin-left: 10rpx;
}
}
}
.sex {
width: 40rpx;
height: 40rpx;
background: #FDECEC;
opacity: 0.5;
display: flex;
justify-content: center;
align-items: center;
margin-right: 20rpx;
.sex-text {
width: 28rpx;
height: 34rpx;
font-size: 28rpx;
font-weight: 500;
color: #36596A;
line-height: 34rpx;
}
}
.age {
@extend .address;
}
.dislevel {
@extend .address;
}
.contact {
@extend .address;
}
}
.bottom {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16rpx;
padding-bottom: 26rpx;
.distance {
align-items: center;
display: flex;
.distance-icon {
padding-left: 20rpx;
}
.distance-text {
height: 34rpx;
font-size: 24rpx;
font-weight: 500;
color: #A7AFBC;
line-height: 34rpx;
padding-left: 20rpx;
}
}
.to-there {
display: flex;
align-items: center;
margin-right: 20rpx;
.to-there-text {
height: 34rpx;
font-size: 24rpx;
font-weight: 500;
color: #A7AFBC;
line-height: 34rpx;
margin-right: 14rpx;
}
.to-there-icon {
margin-left: 3rpx;
}
}
}
}
}
</style>

@ -170,6 +170,29 @@
"style": {
"navigationStyle": "custom"
}
},{
"path": "pages/addQuality/addQuality",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/quality/quality",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/quality/qualityHistory",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/quality/qualityDetail",
"style": {
"navigationStyle": "custom"
}
}
]
}

Loading…
Cancel
Save