Browse Source

Merge pull request '增加性别、出生年份选择' (#13) from bug-rework-2025-0603 into master

Reviewed-on: kuaileadmin/acupuncture_register_system_uniapp#13
master
超级管理员 3 months ago
parent
commit
d9a28b0d94
  1. 119
      components/FormItem.vue
  2. 167
      components/SelectYear.vue
  3. 264
      components/SexPop.vue
  4. 9
      components/inputBox.vue
  5. 1143
      pages/index/index.vue
  6. 381
      pages/modify_visitor/modify_visitor.vue
  7. 4
      pages/usercenter/usercenter.vue
  8. 13
      pages/visitors/visitors.vue

119
components/FormItem.vue

@ -0,0 +1,119 @@
<template>
<view class="form-item">
<!-- 左侧标签 + 必填提示 -->
<view class="label">
<!-- <text v-if="required" class="required">*</text> -->
<text>{{ label }}</text>
<image v-if="required" class="required" :src="cssUrl + 'required.svg'" />
</view>
<!-- 输入模式 -->
<input
v-if="type === 'input'"
class="input"
:placeholder="placeholder"
:value="value"
@input="handleInput"
:disabled="disabled"
/>
<!-- 点击选择模式如性别年份 -->
<view
v-else
class="picker"
@click="handleClick"
>
<text :class="{ 'placeholder': !value }">
{{ displayValue || placeholder }}
</text>
<image class="right_img" :src="cssUrl + 'input_ex.png'" />
<!-- <uni-icons type="arrowright" size="14" color="#999" /> -->
</view>
</view>
</template>
<script>
export default {
name: 'FormItem',
props: {
label: String, // ""
type: { // input picker
type: String,
default: 'input',
validator: (val) => ['input', 'picker'].includes(val),
},
value: [String, Number], //
placeholder: String, //
required: Boolean, //
disabled: Boolean, //
displayValue: String, // picker"/"
},
data(){
return{
cssUrl: this.cssUrl,
}
},
methods: {
handleInput(e) {
this.$emit('input', e.detail.value);
},
handleClick() {
if (!this.disabled) {
this.$emit('click'); //
}
},
},
};
</script>
<style scoped>
.form-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 36rpx 24rpx;
border-bottom: 1rpx solid #eee;
}
.right_img{
width: 26rpx;
height: 26rpx;
}
.label {
display: flex;
align-items: center;
font-family: PingFang SC;
font-size: 32rpx;
font-weight: 500;
line-height: 44rpx;
letter-spacing: 2rpx;
color: #222222;
}
.required {
margin-left: 8rpx;
color: #f56c6c;
font-size: 12px;
width: 16rpx;
height: 16rpx;
}
.input,
.picker {
flex: 1;
text-align: right;
padding-left: 20rpx;
color: #333;
}
.placeholder {
font-family: PingFang SC;
font-size: 32rpx;
font-weight: normal;
line-height: 44rpx;
text-align: right;
letter-spacing: 0.24rpx;
color: #999999;
}
</style>

167
components/SelectYear.vue

@ -0,0 +1,167 @@
<template>
<uni-popup ref="selectDate" :safe-area="false">
<view class="popup-container">
<view class="header">
<view class="title PfScMedium">出生年份</view>
<image class="close-icon" @tap="closePop" :src="cssUrl + 'close.svg'" />
</view>
<view class="date">请选择阳历出生年份</view>
<view class="yearPicker date-wraper">
<picker-view :value="value" @change="handleYearChange" class="picker-view">
<picker-view-column class="picker-column">
<view class="item" v-for="(item, index) in years" :key="index">{{ item }}</view>
</picker-view-column>
</picker-view>
</view>
<view class="confirm-button-wrapper btPadding">
<view class="confirm-button PfScMedium" @click="comfirmYear">
确认
</view>
</view>
</view>
</uni-popup>
</template>
<script>
export default {
data() {
const date = new Date()
const years = []
const year = date.getFullYear()
for (let i = 1990; i <= date.getFullYear(); i++) {
years.push(i)
}
return {
cssUrl:this.cssUrl,
years: years, //
currentYear: new Date().getFullYear(),
};
},
methods: {
handleYearChange(e) {
this.currentYear = this.years[e.detail.value];
},
comfirmYear(){
this.$emit('change', this.currentYear)
this.closePop()
},
show(){
this.$refs.selectDate.open('bottom')
},
closePop() {
this.$refs.selectDate.close()
},
},
};
</script>
<style lang="scss" scoped>
.yearPicker {
height: 470rpx;
background: #F7F7F7;
.picker-view {
height: 420rpx;
margin: 0 auto;
padding: 0 35rpx;
background: #F7F7F7;
}
.item {
line-height: 70rpx;
font-size: 28rpx;
font-weight: 600;
text-align: center;
}
}
.popup-container {
position: relative;
max-height: calc(100vh - 200rpx);
overflow-y: scroll;
background: #ffffff;
border-radius: 40rpx 40rpx 0px 0px;
padding: 42rpx 54rpx 16rpx;
.header {
position: relative;
text-align: center;
.title {
font-weight: 500;
font-size: 32rpx;
color: #000000;
line-height: 36rpx;
}
.close-icon {
width: 40rpx;
height: 40rpx;
position: absolute;
right: 0;
bottom: 0;
}
}
.content-container {
padding: 24rpx 0rpx 68rpx;
font-family: PingFang SC;
.content-item {
background: #FAFAFA;
&:first-child {
border-top: none;
padding: 25rpx 24rpx;
}
padding: 19rpx 24rpx;
border-top: 2rpx solid #F0F0F0;
span {}
.bold {
font-weight: 500;
font-size: 30rpx;
color: #000000;
}
.top {
margin-top: 12rpx;
margin-bottom: 12rpx;
}
.special {
color: #949699;
letter-spacing: 2rpx;
font-size: 30rpx;
.time {
margin: 0 6rpx;
}
}
}
}
.confirm-button {
font-weight: 500;
font-size: 32rpx;
color: #ffffff;
margin-top: 46rpx;
line-height: 92rpx;
text-align: center;
width: 100%;
height: 92rpx;
background: #39d067;
border-radius: 16rpx;
}
.date {
margin: 12rpx 36rpx 42rpx;
text-align: center;
color: #000000;
font-size: 26rpx;
font-family: PingFang SC;
}
}
</style>

264
components/SexPop.vue

@ -0,0 +1,264 @@
<template>
<uni-popup ref="sexWrap" :safe-area="false">
<view class="popup-container">
<view class="header">
<view class="title PfScMedium">性别</view>
<image class="close-icon" @tap="closePop" :src="cssUrl + 'close.svg'" />
</view>
<!-- <radio-group class="radio-group" @change="handleChange">
<label class="radio-item" v-for="item in options" :key="item.value">
<radio :value="item.value" :checked="value === item.value" />
<text>{{ item.label }}</text>
</label>
</radio-group> -->
<view>
<uni-data-checkbox mode="button" v-model="tempValue" :localdata="options"
@change="handleChange"></uni-data-checkbox>
</view>
<view class="confirm-button-wrapper btPadding">
<view class="confirm-button PfScMedium" @click="handleConfirm">
确认
</view>
</view>
</view>
</uni-popup>
</template>
<script>
export default {
name: 'MedicalInsuranceSelector',
options: { styleIsolation: "shared" },
props: {
value: { // 'yes' 'no'
type: String,
default: ''
}
},
data() {
return {
options: [
{ text: '男', value: 0 },
{ text: '女', value: 1 }
],
tempValue: this.value,//
cssUrl: this.cssUrl,
}
},
methods: {
show() {
this.$refs.sexWrap.open('bottom')
},
closePop() {
this.$refs.sexWrap.close()
},
handleChange(e) {
this.tempValue = e.detail.value
},
handleConfirm() {
// this.$emit('input', this.tempValue)
this.$emit('confirm', this.tempValue)
this.closePop()
}
}
}
</script>
<style lang="scss" scoped>
.medical-insurance-selector {
padding: 20rpx;
background-color: #fff;
border-radius: 12rpx;
}
.title {
margin-bottom: 30rpx;
}
.title text {
display: block;
font-size: 16px;
color: #333;
}
.subtitle {
font-size: 14px;
color: #999;
margin-top: 10rpx;
}
.radio-group {
display: flex;
flex-direction: column;
}
.radio-item {
display: flex;
align-items: center;
height: 90rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.radio-item text {
margin-left: 20rpx;
font-size: 15px;
}
.popup-container {
position: relative;
max-height: calc(100vh - 200rpx);
overflow-y: scroll;
background: #ffffff;
border-radius: 40rpx 40rpx 0px 0px;
padding: 42rpx 54rpx 16rpx;
.header {
position: relative;
text-align: center;
.title {
font-weight: 500;
font-size: 32rpx;
color: #000000;
line-height: 36rpx;
}
.close-icon {
width: 40rpx;
height: 40rpx;
position: absolute;
right: 0;
bottom: 0;
}
}
.content-container {
padding: 24rpx 0rpx 68rpx;
font-family: PingFang SC;
.content-item {
background: #FAFAFA;
&:first-child {
border-top: none;
padding: 25rpx 24rpx;
}
padding: 19rpx 24rpx;
border-top: 2rpx solid #F0F0F0;
span {}
.bold {
font-weight: 500;
font-size: 30rpx;
color: #000000;
}
.top {
margin-top: 12rpx;
margin-bottom: 12rpx;
}
.special {
color: #949699;
letter-spacing: 2rpx;
font-size: 30rpx;
.time {
margin: 0 6rpx;
}
}
}
}
.confirm-button {
font-weight: 500;
font-size: 32rpx;
color: #ffffff;
margin-top: 46rpx;
line-height: 92rpx;
text-align: center;
width: 100%;
height: 92rpx;
background: #39d067;
border-radius: 16rpx;
}
.date {
margin: 12rpx 36rpx 42rpx;
text-align: center;
color: #000000;
font-size: 26rpx;
font-family: PingFang SC;
}
::v-deep .uni-data-checklist .checklist-group {
column-gap: 24rpx;
margin-bottom: 24rpx;
.checklist-box {
margin: 0;
width: calc(50% - 14rpx);
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
.checklist-content {
flex: none;
}
.checklist-content .checklist-text {
font-size: 30rpx;
font-weight: normal;
letter-spacing: 0.22rpx;
margin-left: 12rpx;
color: #666666;
}
.radio__inner {
width: 36rpx;
height: 36rpx;
border: 2rpx solid #BCBCBC;
border-radius: 36rpx;
}
&.is--button {
margin-right: 0rpx;
padding: 20rpx 42rpx 20rpx 24rpx;
box-sizing: border-box;
border: none;
min-height: 82rpx;
border-radius: 8rpx;
transition: border-color 0.2s;
background: #F8F8F8;
&.is-checked {
background: #F5FFF4;
.radio__inner {
border-color: #00C160;
}
.radio__inner-icon {
background: #00C160;
}
.checklist-text {
color: #333333;
}
}
}
}
}
}
</style>

9
components/inputBox.vue

@ -5,6 +5,7 @@
<view class="top-wrapper flex">
<view class="left PfScMedium">
<view>{{label}}</view>
<image v-if="required" class="required" :src="cssUrl + 'required.svg'" />
</view>
<view class="right">
<textarea class="textarea" v-if="autoHeight" :auto-height="true" :value="value" :placeholder="holder" @blur="handleBlur" @input="handleInput"></textarea>
@ -157,6 +158,7 @@ export default {
}
.left{
display: flex;
align-items: center;
min-width: 158rpx;
max-width: 238rpx;
text{
@ -209,6 +211,13 @@ export default {
}
}
}
.required {
margin-left: 8rpx;
color: #f56c6c;
font-size: 12px;
width: 16rpx;
height: 16rpx;
}
.errmsg{
margin: 0 auto;
width: 100%;

1143
pages/index/index.vue

File diff suppressed because it is too large

381
pages/modify_visitor/modify_visitor.vue

@ -1,16 +1,26 @@
<template>
<view class="visitor-form-page">
<view class="block">
<input-box class="inputcom-wrapper" v-model="name" holder="填写姓名" label="患者姓名" @blurEvent="nameBlurEvent"></input-box>
<input-box class="inputcom-wrapper" v-model="idcard" holder="填写身份证号码" label="身份证号" @blurEvent="idCardBlurEvent" rule="idcard"></input-box>
<input-box class="inputcom-wrapper" v-model="phone" holder="填写患者手机号" label="患者手机号" @blurEvent="phoneBlurEvent" rule="phone"></input-box>
<input-box class="inputcom-wrapper" v-model="name" holder="必填项" label="姓名"
@blurEvent="nameBlurEvent" required></input-box>
<!-- <input-box class="inputcom-wrapper" v-model="idcard" holder="填写身份证号码" label="身份证号" @blurEvent="idCardBlurEvent" rule="idcard"></input-box> -->
<!-- <input-box class="inputcom-wrapper" v-model="phone" holder="方便预约后联系,非必填" label="手机号"
@blurEvent="phoneBlurEvent" rule="phone"></input-box> -->
<!-- <FormItem label="姓名" type="input" v-model="name" placeholder="必填项" required /> -->
<FormItem label="性别" type="picker" :display-value="genders[sex] || ''" placeholder="必选" required
@click="showGenderPicker" />
<FormItem label="出生年份" type="picker" :display-value="year ? `${year}` : ''" placeholder="必选"
required @click="showYearPicker" />
<FormItem label="手机号" type="input" v-model="phone" rule="phone" placeholder="方便预约后联系,非必填" />
</view>
<view class="submit-wrapper btPadding" v-if="!vid">
<view :class="'btn btn3 PfScMedium submit'+(canSubmit?' primary':' noclick')" :hover-class="(canSubmit?'hover':'')" @click="submit" v-if="!vid">
<view :class="'btn btn3 PfScMedium submit' + (canSubmit ? ' primary' : ' noclick')"
:hover-class="(canSubmit ? 'hover' : '')" @click="submit" v-if="!vid">
确认新增
</view>
<view :class="'btn btn3 PfScMedium submit'+(canSubmit?' primary':' noclick')" :hover-class="(canSubmit?'hover':'')" @click="submit" v-else>
<view :class="'btn btn3 PfScMedium submit' + (canSubmit ? ' primary' : ' noclick')"
:hover-class="(canSubmit ? 'hover' : '')" @click="submit" v-else>
确认修改
</view>
</view>
@ -20,185 +30,234 @@
<view class="btn primary btn1" hover-class="hover" @click="submit">确认</view>
</view>
</view>
<SelectYear ref="yearPicker" @change="handleYearChange" />
<SexPop ref="genderPopup" v-model="sex" @confirm="handleConfirm" />
</view>
</template>
<script>
import InputBox from '@/components/inputBox.vue';
export default {
data() {
return {
canSubmit:false,
name:'',
nameRule:false,
idcard:'',
idcardExtRule:false,
phone:'',
phoneRule:false,
type:1,
adding:false,
vid:false,
visitor_info:{}
}
import InputBox from '@/components/inputBox.vue';
import FormItem from '@/components/FormItem.vue';
import SelectYear from '@/components/SelectYear.vue';
import SexPop from '@/components/SexPop.vue';
export default {
data() {
return {
canSubmit: false,
cssUrl: this.cssUrl,
year: '',
name: '',
// form: {
// year: '',
// name: '',
// sex: '',
// phone: ''
// },
sex:'',
nameRule: false,
idcard: '',
idcardExtRule: false,
phone: '',
phoneRule: false,
type: 1,
adding: false,
vid: false,
visitor_info: {},
genders: {
0:'男',
1:'女'
},
}
},
components: {
InputBox,
FormItem,
SexPop,
SelectYear
},
onLoad(option) {
if (option.type) this.type = option.type
if (option.vid) {
this.vid = option.vid
uni.setNavigationBarTitle({
title: '修改预约人'
});
} else {
uni.setNavigationBarTitle({
title: '新增预约人'
});
}
},
onShow() {
if (this.vid) this.getVisitorInfo()
},
methods: {
handleConfirm(sex){
this.sex = sex;
this.checkSubmit()
},
components:{
InputBox
handleYearChange(year) {
this.year = year;
this.checkSubmit()
},
onLoad(option) {
if(option.type) this.type = option.type
if(option.vid) {
this.vid = option.vid
uni.setNavigationBarTitle({
title: '修改预约人'
});
}else{
uni.setNavigationBarTitle({
title: '新增预约人'
});
}
showYearPicker() {
this.$refs.yearPicker.show()
},
onShow() {
if(this.vid) this.getVisitorInfo()
showGenderPicker() {
this.$refs.genderPopup.show();
},
methods: {
add() {
if(this.adding) return
this.adding = true
var param = new Object()
param.name = this.name
param.idcard = this.idcard
param.phone = this.phone
var that = this
var addtimer = setTimeout(function() {
that.adding = false
}, 5000);
var req = 'api/user/add_visitor'
var method = 'POST'
if(this.vid){
param.id = this.vid
param.get_idcard = 1
req = 'api/user/update_visitor'
}
add() {
if (this.adding) return
this.adding = true
var param = new Object()
param.name = this.name
param.sex = this.sex
param.birth_year = this.year
param.phone = this.phone
// param.idcard = this.idcard
var that = this
var addtimer = setTimeout(function () {
that.adding = false
}, 5000);
this.$http.req(req, param, method).then(data=>{
if(data == -1) return
clearTimeout(addtimer)
this.adding = false
if(this.vid){
uni.removeStorageSync('visitor_info_'+ this.vid)
}
var req = 'api/user/add_visitor'
var method = 'POST'
if (this.vid) {
param.id = this.vid
param.get_idcard = 1
req = 'api/user/update_visitor'
}
uni.navigateBack()
});
},
getVisitorInfo() {
var param = new Object()
param.vid = this.vid
this.$http.req('api/user/get_modify_visitor', param, 'POST').then(data=>{
if(data == -1) return
this.name = data.name
this.idcard = data.idcard
this.phone = data.phone
this.nameRule = true
this.idcardExtRule = true
this.phoneRule = true
this.checkSubmit()
});
this.$http.req(req, param, method).then(data => {
if (data == -1) return
},
nameBlurEvent(value, res){
this.name = value
this.nameRule = res
this.checkSubmit()
},
idCardBlurEvent(value, res){
this.idcard = value
this.idcardExtRule = res
this.checkSubmit()
},
phoneBlurEvent(value, res){
this.phone = value
this.phoneRule = res
this.checkSubmit()
},
checkSubmit() {
this.canSubmit = false
if(!this.nameRule || !this.idcardExtRule || !this.phoneRule) return
this.canSubmit = true
},
reback(){
if(this.vid){
uni.removeStorageSync('visitor_info_'+ this.vid)
clearTimeout(addtimer)
this.adding = false
if (this.vid) {
uni.removeStorageSync('visitor_info_' + this.vid)
}
this.$func.toPage('/pages/visitors_new/visitors_new')
},
submit(){
uni.navigateBack()
});
},
getVisitorInfo() {
var param = new Object()
param.vid = this.vid
this.$http.req('api/user/get_modify_visitor', param, 'POST').then(data => {
if (data == -1) return
this.name = data.name
this.year = data.birth_year
this.sex = data.sex
// this.idcard = data.idcard
this.phone = data.phone
this.nameRule = true
// this.idcardExtRule = true
this.phoneRule = false
this.checkSubmit()
if(!this.canSubmit) return
this.add()
});
},
nameBlurEvent(value, res) {
this.name = value
this.nameRule = res
this.checkSubmit()
},
// idCardBlurEvent(value, res) {
// this.idcard = value
// this.idcardExtRule = res
// this.checkSubmit()
// },
// phoneBlurEvent(value, res) {
// this.phone = value
// this.phoneRule = res
// this.checkSubmit()
// },
checkSubmit() {
this.canSubmit = false
if (!this.name || !this.sex || !this.year) return
this.canSubmit = true
},
reback() {
if (this.vid) {
uni.removeStorageSync('visitor_info_' + this.vid)
}
this.$func.toPage('/pages/visitors_new/visitors_new')
},
submit() {
this.checkSubmit()
if (!this.canSubmit) return
this.add()
}
}
}
</script>
<style lang="scss" scoped>
.visitor-form-page{
background: #F8F8F8;
height: 100vh;
overflow-y: scroll;
.block{
background: #FFFFFF;
box-shadow: 0rpx 2rpx 24rpx 0rpx rgba(0,0,0,0.03);
border-radius: 8rpx;
width: 714rpx;
margin: 20rpx auto 0rpx;
overflow: hidden;
&:nth-child(3){
border: none !important;
}
.title{
font-size: 32rpx;
color: #000000;
width: 666rpx;
height: 50rpx;
line-height: 50rpx;
letter-spacing: 2rpx;
margin: 36rpx 0 36rpx 24rpx;
}
}
.submit-wrapper{
overflow: hidden;
position: fixed;
top: 88vh;
height: 92rpx;
left: 50%;
transform: translateX(-50%);
.visitor-form-page {
// background: #F8F8F8;
background: #FFFFFF;
height: 100vh;
overflow-y: scroll;
.block {
// background: #FFFFFF;
box-shadow: 0rpx 2rpx 24rpx 0rpx rgba(0, 0, 0, 0.03);
border-radius: 8rpx;
width: 714rpx;
margin: 20rpx auto 0rpx;
overflow: hidden;
&:nth-child(3) {
border: none !important;
}
.submit{
margin: 0 auto;
.title {
font-size: 32rpx;
color: #000000;
width: 666rpx;
height: 50rpx;
line-height: 50rpx;
letter-spacing: 2rpx;
margin: 36rpx 0 36rpx 24rpx;
}
.fixedBot{
position: fixed;
bottom: 0;
left: 0;
}
.submit-wrapper {
overflow: hidden;
position: fixed;
top: 88vh;
height: 92rpx;
left: 50%;
transform: translateX(-50%);
}
.submit {
margin: 0 auto;
}
.fixedBot {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 124rpx;
background: #FFFFFF;
box-shadow: 0rpx -2rpx 6rpx 0rpx rgba(181, 181, 181, 0.13);
backdrop-filter: blur(20rpx);
display: flex;
.submit {
width: 100%;
height: 124rpx;
background: #FFFFFF;
box-shadow: 0rpx -2rpx 6rpx 0rpx rgba(181,181,181,0.13);
backdrop-filter: blur(20rpx);
display: flex;
.submit{
width: 100%;
display: flex;
justify-content: center;
align-items: center;
column-gap: 50rpx;
}
justify-content: center;
align-items: center;
column-gap: 50rpx;
}
}
}
</style>

4
pages/usercenter/usercenter.vue

@ -9,12 +9,12 @@
<view class="menu">
<view class="item" @click="toVisitors">
<img :src="cssUrl+'user_icon.svg'" alt="">
<view>预约</view>
<view>就诊</view>
</view>
<view class="item" @click="toPrebookList">
<img :src="cssUrl+'message.svg'" alt="">
<view>预约记录</view>
<view>就诊记录</view>
</view>
</view>

13
pages/visitors/visitors.vue

@ -14,8 +14,11 @@
<view class="name PfScSemibold over2">{{item.name}}</view>
<view class="book-date" v-if="type == 0 && item.visit_time && !is_preview">已预约今日 {{ item.visit_time.split('-')[0] }} 预约</view>
</view>
<view class="bot line36">
身份证号{{item.idcard_txt}}
<view class="bot line36 flex">
<!-- <view class="flex"> <view class="gap">|</view> 25</view>
<view v-if="item.phone" class="flex"><view class="gap">|</view> 手机号1778989678</view> -->
<view class="flex">{{item.sex}}<view class="gap">|</view> {{ item.age }}</view>
<view v-if="item.phone" class="flex"><view class="gap">|</view>手机号{{ item.phone }}</view>
</view>
<img class="status" :src="cssUrl+'visitor_select_active.svg'">
<img class="bg" :src="cssUrl+'visitor_list__bg.svg'">
@ -427,7 +430,11 @@
.bot{
margin-top: 32rpx;
font-size: 30rpx;
color: #949699;
color: #333333;
}
.gap{
margin: 0 23rpx;
color: #DBDBDB;
}
img{
position: absolute;

Loading…
Cancel
Save