diff --git a/coupon/app.json b/coupon/app.json index eb8d817..dfc1937 100644 --- a/coupon/app.json +++ b/coupon/app.json @@ -1,6 +1,7 @@ { "pages": [ "pages/index/index", + "pages/service/service", "pages/peihu/peihu", "pages/tuiguang/extensionInfor", "pages/my/my", @@ -48,6 +49,12 @@ "iconPath": "pages/image/income.png", "selectedIconPath": "pages/image/income_a.png" }, + { + "pagePath": "pages/service/service", + "text": "服务", + "iconPath": "pages/image/service.png", + "selectedIconPath": "pages/image/service.png" + }, { "pagePath": "pages/order/order", "text": "订单", diff --git a/coupon/pages/image/syy.png b/coupon/pages/image/syy.png deleted file mode 100644 index 768133a..0000000 Binary files a/coupon/pages/image/syy.png and /dev/null differ diff --git a/coupon/pages/index/index.js b/coupon/pages/index/index.js index edd76ab..bbf793c 100644 --- a/coupon/pages/index/index.js +++ b/coupon/pages/index/index.js @@ -1,83 +1,17 @@ -//获取应用实例 var app = getApp(); +const { request } = require('../../utils/request') + Page({ - /** - * 页面的初始数据00 - */ data: { - order_list: [], + hospitalData: [], bannerdata: [ "home-banner", "home-banner", "home-banner" ], - itemView: [{ - icon: "全程陪诊", - title: "全程陪诊", - price: "200", - describe: "服务内容:排队 预约 挂号 缴费 取药 记录医嘱", - ordertype: "1" - }, - { - icon: "诊前约号", - title: "诊前约号", - price: "20", - describe: "服务内容:协助办理完成挂号", - ordertype: "2" - }, - // { - // icon: "代办买药", - // title: "代办买药", - // price:"30", - // describe:"服务内容:协助代理买药" - // }, - { - icon: "代办问诊", - title: "代办问诊", - price: "100", - describe: "服务内容:使用病人或者家属取检查报告化验单向医生问明情况", - ordertype: "4" - }, - { - icon: "取送结果", - title: "取送结果", - price: "50", - describe: "服务内容:包括给病人或者家属取检查报告化验单等", - ordertype: "3" - }, - // { - // icon: "出入院办理", - // title: "出入院办理", - // price:"100", - // describe:"协助办理出入院手续" - // } - ], - hospitalData: [{ - img: "https://bkimg.cdn.bcebos.com/pic/8326cffc1e178a828fa58d64f303738da877e85d?x-bce-process=image/resize,m_lfit,w_536,limit_1", - name: "西安市高新医院", - grade: "三甲", - genre: "综合医院", - skiled: "消化内科、心血管内科", - address: "高新区高新六路", - id: "1" - }, { - img: "https://bkimg.cdn.bcebos.com/pic/8326cffc1e178a828fa58d64f303738da877e85d?x-bce-process=image/resize,m_lfit,w_536,limit_1", - name: "第四军医大学西京医院", - grade: "三甲", - genre: "综合医院", - skiled: "消化内科、心血管内科", - address: "莲湖区康复路", - id: "2" - }, { - img: "https://bkimg.cdn.bcebos.com/pic/8326cffc1e178a828fa58d64f303738da877e85d?x-bce-process=image/resize,m_lfit,w_536,limit_1", - name: "陕西省人民医院", - grade: "三甲", - genre: "综合医院", - skiled: "消化内科、心血管内科", - address: "碑林区友谊西路", - id: "3" - }] + itemView: [], + firstLoad: true }, /** @@ -101,6 +35,10 @@ Page({ } }, + goService() { + wx.switchTab({ url: '/pages/service/service' }) + }, + go_peizhen() { if (wx.getStorageSync('phone') == '') { wx.setStorageSync('uidFlag', false); @@ -124,33 +62,60 @@ Page({ }) } }, + //获取服务类型列表(从后台动态加载,最多显示8个热门推荐) + getServiceTypes: function () { + var that = this; + request({ + url: '/system/serviceType/enabled', + method: 'GET', + data: { category: '2' }, + loading: false + }).then(res => { + var services = res.data || []; + if (services.length) { + var mapped = services.slice(0, 8).map(function (s) { + return { + icon: s.icon || s.name, + title: s.name, + price: s.price, + describe: s.description || '', + ordertype: s.typeCode + } + }) + that.setData({ itemView: mapped }) + } + }).catch(() => {}) + }, + //获取医院列表 getHospitalList: function () { var that = this; - wx.request({ - url: app.globalData.url + '/system/hospital/list', - header: app.getRequestHeader(), + request({ + url: '/system/hospital/list', method: 'GET', - data: {}, - success: (res) => { - var resp = res.data; - console.log("resp==" + resp) - if (res.data.code == 200) { - if (this.data.pageNum == 1) { - that.setData({ - order_list: resp.data, - pageNum: this.data.pageNum + 1 - }); - - } else { - that.setData({ - order_list: resp.data, - - }); + loading: false + }).then(res => { + var hospitals = res.rows || res.data || []; + if (hospitals.length) { + var baseUrl = app.globalData.url; + var mapped = hospitals.map(function (h) { + var imgUrl = h.hospitalImage || ''; + if (imgUrl && imgUrl.indexOf('http') !== 0) { + imgUrl = baseUrl + imgUrl; } - } + return { + id: h.id, + name: h.hospitalname, + grade: h.hospitalLevel || '三甲', + genre: h.hospitalType || '综合医院', + skiled: h.hospitalDesc || '', + address: h.address || '', + img: imgUrl + } + }) + that.setData({ hospitalData: mapped }) } - }); + }).catch(() => {}) }, /** * 医院列表 @@ -205,28 +170,17 @@ Page({ * 生命周期函数--监听页面显示 */ onShow() { - this.getHospitalList() + if (this.data.firstLoad) { + this.setData({ firstLoad: false }) + this.getServiceTypes() + this.getHospitalList() + } }, - /** - * 生命周期函数--监听页面隐藏 - */ - onHide() { - - }, - - /** - * 生命周期函数--监听页面卸载 - */ - onUnload() { - - }, - - /** - * 页面相关事件处理函数--监听用户下拉动作 - */ onPullDownRefresh() { - + this.getServiceTypes() + this.getHospitalList() + wx.stopPullDownRefresh() }, /** diff --git a/coupon/pages/index/index.wxml b/coupon/pages/index/index.wxml index f54ebf2..1f79d15 100644 --- a/coupon/pages/index/index.wxml +++ b/coupon/pages/index/index.wxml @@ -12,11 +12,15 @@ + + 热门服务 + 查看更多 > + - + {{item.title}} @@ -52,7 +56,7 @@ - + {{item.name}} diff --git a/coupon/pages/index/index.wxss b/coupon/pages/index/index.wxss index 9386c75..700ce9e 100644 --- a/coupon/pages/index/index.wxss +++ b/coupon/pages/index/index.wxss @@ -40,7 +40,8 @@ .item-view{ display: flex; flex-direction: row; - margin: 30rpx 40rpx 0; + flex-wrap: wrap; + margin: 20rpx 30rpx 0; } #block{ @@ -50,8 +51,8 @@ .item-list{ display: flex; flex-direction: column; - padding: 0 10rpx; - width: 160rpx; + padding: 0 8rpx; + width: calc(25% - 16rpx); height: 160rpx; align-items: center; justify-content: center; @@ -207,4 +208,22 @@ margin-top: 24rpx; font-size: 30rpx; color: #666666; +} + +.section-header{ + display: flex; + justify-content: space-between; + align-items: center; + padding: 30rpx 40rpx 0 40rpx; +} + +.section-title{ + font-size: 32rpx; + font-weight: bold; + color: #333; +} + +.section-more{ + font-size: 24rpx; + color: #999; } \ No newline at end of file diff --git a/coupon/pages/mechanism/mechanism.js b/coupon/pages/mechanism/mechanism.js index 03a3051..9126717 100644 --- a/coupon/pages/mechanism/mechanism.js +++ b/coupon/pages/mechanism/mechanism.js @@ -1,207 +1,95 @@ -// pages/mechanism/mechanism.js -//获取应用实例 const app = getApp() +const { request } = require('../../utils/request') + Page({ - /** - * 页面的初始数据 - */ data: { - navHeight: '', - menuButtonInfo: {}, - searchMarginTop: 0, // 搜索框上边距 - searchWidth: 0, // 搜索框宽度 - searchHeight: 0, // 搜索框高度 - page:1, - search: '' + navHeight: '', + menuButtonInfo: {}, + searchMarginTop: 0, + searchWidth: 0, + searchHeight: 0, + page: 1, + search: '', + lists: [], + firstLoad: true }, - /** - * 监听搜索内容 - */ - changeSearch: function(e) { - let that = this; - var inputSearch = e.detail.value; - that.setData({ - search: inputSearch - }) - app.console(inputSearch+"================="+'22dddddddddddddddddddddd2') - // this.dataRefresh() - this.getMechanismList(); - }, - back(){ + + changeSearch: function (e) { + this.setData({ search: e.detail.value }) + this.getMechanismList() + }, + + back() { wx.navigateBack() }, - //机构详情跳转 - mechanism(e){ - app.console("================="+'dddddddddddddddd') - wx.navigateTo({ - url: "/pages/institutions/institutions?id=" + e.currentTarget.dataset.id, - }) + + mechanism(e) { + wx.navigateTo({ + url: "/pages/institutions/institutions?id=" + e.currentTarget.dataset.id, + }) }, - Seemore(){ - console.log('点击了查看更多'); + + Seemore() { + console.log('点击了查看更多') }, - /** - * 生命周期函数--监听页面加载 - */ + onLoad: function (options) { - this.setData({ - menuButtonInfo: wx.getMenuButtonBoundingClientRect() - }) - console.log(this.data.menuButtonInfo) - const { top, width, height, right } = this.data.menuButtonInfo - wx.getSystemInfo({ + this.setData({ menuButtonInfo: wx.getMenuButtonBoundingClientRect() }) + const { top, width, height, right } = this.data.menuButtonInfo + wx.getSystemInfo({ success: (res) => { - const { statusBarHeight } = res - const margin = top - statusBarHeight - this.setData({ - navHeight: (height + statusBarHeight + (margin * 2) ), - searchMarginTop: statusBarHeight + margin, // 状态栏 + 胶囊按钮边距 - searchHeight: height, // 与胶囊按钮同高 - searchWidth: right - width // 胶囊按钮右边坐标 - 胶囊按钮宽度 = 按钮左边可使用宽度 - }) + const { statusBarHeight } = res + const margin = top - statusBarHeight + this.setData({ + navHeight: (height + statusBarHeight + (margin * 2)), + searchMarginTop: statusBarHeight + margin, + searchHeight: height, + searchWidth: right - width + }) }, - }) + }) }, - /** - * 生命周期函数--监听页面初次渲染完成 - */ - onReady: function () { - - }, - - /** - * 生命周期函数--监听页面显示 - */ onShow: function () { - this.getMechanismList(); - }, - - /** - * 生命周期函数--监听页面隐藏 - */ - onHide: function () { - - }, - - /** - * 生命周期函数--监听页面卸载 - */ - onUnload: function () { - - }, - - /** - * 页面相关事件处理函数--监听用户下拉动作 - */ - onPullDownRefresh: function () { - - }, - - /** - * 页面上拉触底事件的处理函数 - */ - onReachBottom: function () { - - }, - - /** - * 用户点击右上角分享 - */ - onShareAppMessage: function () { - - }, - /** - * 页面相关事件处理函数--监听用户下拉动作 - */ - onPullDownRefresh: function () { - - //this.getOrderList(); - var that = this; - wx.request({ - url: app.buildUrl("/app/Goddesswechat/getOrderList"), - header: app.getRequestHeader(), - method:'POST', - data: { - version_number: app.globalData.version_number, - uid:wx.getStorageSync('uid'), - token:wx.getStorageSync('token'), - page:1, - page_size:10, - style: that.data.status[ that.data.currentType ] - - }, - success: (res)=> { - var resp = res.data; - - if(resp.erro==0){ - that.setData({ - page:1, - order_list:resp.order, - }); - wx.stopPullDownRefresh({ - success: (res) => {}, - }) - } + if (this.data.firstLoad) { + this.setData({ firstLoad: false }) + this.getMechanismList() } - }); - - }, + }, - /** - * 页面上拉触底事件的处理函数 - */ - onReachBottom: function () { - - this.getMechanismList(); - }, + onPullDownRefresh: function () { + this.setData({ page: 1 }) + this.getMechanismList().finally(() => wx.stopPullDownRefresh()) + }, - //机构列表 - getMechanismList: function () { - var that = this; - wx.request({ - url: app.buildUrl("/app/Goddesswechat/getMechanismList"), - header: app.getRequestHeader(), - method:'POST', - data: { - version_number: app.globalData.version_number, - uid:wx.getStorageSync('uid'), - token:wx.getStorageSync('token'), - page:'1', - page_size:500, - city:'', - style_id:'', - serach_name:this.data.search, - px_type:'', - lng:'', - lat:'', - }, - success: function (res) { - var resp = res.data; - if(resp.erro==0){ - that.setData({ - lists:resp.lists - - }); + onReachBottom: function () { + this.getMechanismList() + }, - - - // if(that.data.page==1){ - // that.setData({ - // lists:resp.lists, - // page:that.data.page+1 - // }); - - // }else{ - // that.setData({ - // lists:that.data.lists.concat(resp.lists), - // page:that.data.page+1 - // }); - // } - } - } - }); -}, + getMechanismList: function () { + var that = this + return request({ + url: '/app/Goddesswechat/getMechanismList', + method: 'POST', + loading: true, + data: { + uid: wx.getStorageSync('uid'), + token: wx.getStorageSync('token'), + page: '1', + page_size: 50, + city: '', + style_id: '', + serach_name: this.data.search, + px_type: '', + lng: '', + lat: '', + } + }).then(res => { + if (res.erro === 0) { + that.setData({ lists: res.lists || [] }) + } + }).catch(() => {}) + }, }) \ No newline at end of file diff --git a/coupon/pages/mechanism/mechanism.wxml b/coupon/pages/mechanism/mechanism.wxml index e831778..22dee3c 100644 --- a/coupon/pages/mechanism/mechanism.wxml +++ b/coupon/pages/mechanism/mechanism.wxml @@ -19,47 +19,31 @@ - - - - - - - - - {{item.mechanism_name}} - {{item.address}} - - - - + + + + + + + {{item.mechanism_name}} + {{item.address}} + + + + + + {{item.score}} + 已预约{{item.appointment}} - {{item.score}} - 已预约{{item.appointment}} - - - + + + 暂无机构 + diff --git a/coupon/pages/my/my.js b/coupon/pages/my/my.js index 4d1493a..59a3738 100644 --- a/coupon/pages/my/my.js +++ b/coupon/pages/my/my.js @@ -1,47 +1,32 @@ var Token = require('../../utils/util.js'); - const app = getApp() +const { request } = require('../../utils/request') Page({ - /** - * 页面的初始数据 - */ data: { canIUseOpenData: wx.canIUse('open-data.type.userAvatarUrl') && wx.canIUse('open-data.type.userNickName'), phone: wx.getStorageSync('phone'), uidFlag: wx.getStorageSync('uidFlag'), status: '', code: '', - height: '' + height: '', + logging: false }, - /** - * 生命周期函数--监听页面加载 - */ onLoad: function (options) { var statusBarHeight = wx.getSystemInfoSync().statusBarHeight; - app.console(statusBarHeight + "=================xxxxxxxxxxxxxxxxxxxxxxxx") this.setData({ statusBarHeight: statusBarHeight, height: 46 + statusBarHeight, }); - app.console(this.data.height + "=================xxxxxxxxxxxxxxxxxxxxxxxx") - //调试参数 - // wx.setStorageSync('phone', 18133922183); - // wx.setStorageSync('uid', 'adfdffb45a364fc23162f1ebfe9dd66f'); - // wx.setStorageSync('token', '797d4q7XT8/PM9bU+T+CNpEpNfsWeVb5j+eXnrRXW0yq+wKXY9fBNSvb7qPiCRMlzZmwG3u1ods8R8Rh3gITxCkyiZGUtLZf'); - // wx.setStorageSync('uidFlag', true); }, - /** - * 生命周期函数--监听页面显示 - */ + onShow: function () { - this.getLogin() - console.log("code==" + this.data.code) - // if (wx.getStorageSync('phone') != '') { - // this.getUserBaseInfo() - // } + // 仅在未登录时获取 code + if (!this.data.uidFlag) { + this.getLogin() + } }, // 登录 @@ -49,74 +34,62 @@ Page({ var that = this; wx.login({ success: res => { - console.log(res) app.globalData.code = res.code - that.setData({ - code: res.code - }) + that.setData({ code: res.code }) } }) }, - // 获取电话号码 - getPhoneNumber(e) { + // 获取电话号码并登录 + async getPhoneNumber(e) { + if (this.data.logging) return + this.setData({ logging: true }) - console.log("encryptedData==" + e.detail.encryptedData) - console.log("iv==" + e.detail.iv) - console.log("wxcode==" + app.globalData.code) - - var _this = this; - console.log(e) - wx.request({ - url: app.globalData.url + '/weixinLogin', - data: { - encryptedData: e.detail.encryptedData, - iv: e.detail.iv, - wxcode: app.globalData.code - }, - method: 'POST', - header: { - 'content-type': 'application/x-www-form-urlencoded' - }, - success(res) { - if (res.data.code == 200) { - app.globalData.phone = res.data.phone; - wx.setStorageSync('phone', res.data.data.phoneNumber); - wx.setStorageSync('uid', res.data.data.userId); - wx.setStorageSync('token', res.data.data.token); - wx.setStorageSync('uidFlag', true); - _this.setData({ - uidFlag: true, - phone: res.data.phone - }) - app.globalData.uid = res.data.uid; - wx.switchTab({ - url: '/pages/index/index' - }) - // Token.token() - _this.getToken() - - _this.ceshi() - - wx.showToast({ - title: res.data.msg, - icon: 'none' - }); - - - } else { - wx.showToast({ - title: res.data.msg, - icon: 'none' - }); - + try { + var res = await request({ + url: '/weixinLogin', + method: 'POST', + loading: true, + loadingText: '登录中...', + data: { + encryptedData: e.detail.encryptedData, + iv: e.detail.iv, + wxcode: app.globalData.code } - } - }) - }, - //上传测试数据 - ceshi() { + }) + app.globalData.phone = res.phone; + wx.setStorageSync('phone', res.data.phoneNumber); + wx.setStorageSync('uid', res.data.userId); + wx.setStorageSync('token', res.data.token); + wx.setStorageSync('uidFlag', true); + + this.setData({ + uidFlag: true, + phone: res.phone + }) + app.globalData.uid = res.data.userId; + + wx.showToast({ title: '登录成功', icon: 'success' }) + + // 同步登录信息到服务端(失败不影响登录) + this.syncLogin() + + // 返回上一页,若无法返回则到首页 + var pages = getCurrentPages() + if (pages.length > 1) { + wx.navigateBack() + } else { + wx.switchTab({ url: '/pages/index/index' }) + } + + } catch (e) { + this.setData({ logging: false }) + } + }, + + // 同步登录信息 + syncLogin() { wx.request({ url: app.buildUrltwo("/member/logintwo"), header: app.getRequestHeader(), @@ -125,49 +98,22 @@ Page({ name: wx.getStorageSync('uid'), phone: wx.getStorageSync('phone'), storename: wx.getStorageSync('token') - - }, - success: function (res) { - var resp = res.data; - if (resp.code != 200) { - app.alert({ - "content": resp.msg - }); - return; - } - // 跳转 - wx.navigateBack({}); } }) }, - //获取token + getUserBaseInfo: function () { var that = this; - //假数据 - // if (this.data.resp.erro == 0) { - // that.setData({ - // infor: this.data.resp.infor - // }); - // } - wx.request({ - url: app.globalData.url + '/getUserToken', - header: app.getRequestHeader(), + request({ + url: '/getUserToken', method: 'GET', - data: { - phonenumber: '18133922183' - }, - success: function (res) { - var resp = res.data; - console.log(resp + "=================dddddddddddddddddddddddddddddddddddddddddddd") - console.log(resp.erro + "=================dddddddddddddddddddddddddddddddddddddddddddd") - if (resp.erro == 0) { - - that.setData({ - infor: resp.infor - }); - } + data: { phonenumber: this.data.phone }, + loading: false + }).then(resp => { + if (resp.erro == 0) { + that.setData({ infor: resp.infor }) } - }); + }).catch(() => {}) }, /** diff --git a/coupon/pages/order/order.js b/coupon/pages/order/order.js index 8a905a5..b71f351 100644 --- a/coupon/pages/order/order.js +++ b/coupon/pages/order/order.js @@ -1,36 +1,37 @@ -//获取应用实例 var app = getApp(); +const { request } = require('../../utils/request') + Page({ - /** - * 页面的初始数据 - */ data: { order_list: [], nav_type: 1, all: true, height: '', url: app.globalData.url, - statusType: ["全部", "待接单", "已接单", "服务中", "待支付", "已完成","已取消"], status: ["", "0", "1", "2", "3","4","-2"], currentType: 0, tabClass: ["", "", "", "", "", "", ""], - pageNum: 1 + pageNum: 1, + hasMore: true, + firstLoad: true, + loading: false }, + statusTap: function (e) { var curType = e.currentTarget.dataset.index; this.setData({ currentType: curType, - pageNum: 1 + pageNum: 1, + order_list: [] }); this.getOrderList(); }, + onClickLeft() { wx.navigateBack() }, - /** - * 生命周期函数--监听页面加载 - */ + onLoad: function (options) { var statusBarHeight = wx.getSystemInfoSync().statusBarHeight; this.setData({ @@ -38,142 +39,98 @@ Page({ height: 46 + statusBarHeight, }); }, - //条目点击---订单状态:-2:已取消 -1:拒绝接单 0:待接单 1:已接单 2:服务中 3:待支付 4:已完成 5申请退款 6退款中 7已退款 8:已结算 - itemclick(e) { - console.log("列表条目的index==" + e.currentTarget.dataset.index) - //去订单详情页 if (e.currentTarget.dataset.item != 0) { let item = JSON.stringify(e.currentTarget.dataset.item); - //console.log("传递的item" + item); wx.navigateTo({ url: "/pages/orderdetail/orderdetail?item=" + item, }); - } - }, - //跳转订单详情(去付款) - go_pay(e) { - console.log(e.currentTarget.dataset.id) - // wx.navigateTo({ - // url: '/pages/orderdetail/orderdetail?' + "type=" + '2' + '&id=' + e.currentTarget.dataset.id, - // }) - - if (e.currentTarget.dataset.item != 0) { - let item = JSON.stringify(e.currentTarget.dataset.item); - console.log("传递的item" + item); - wx.navigateTo({ - url: "/pages/orderdetail/orderdetail?item=" + item, - - }); } }, - /** - * 生命周期函数--监听页面初次渲染完成 - */ - onReady: function () {}, + go_pay(e) { + if (e.currentTarget.dataset.item != 0) { + let item = JSON.stringify(e.currentTarget.dataset.item); + wx.navigateTo({ + url: "/pages/orderdetail/orderdetail?item=" + item, + }); + } + }, - /** - * 生命周期函数--监听页面显示 - */ onShow: function () { - this.setData({ - order_list: [], - pageNum: 1 - }) - this.getOrderList(); + // 首次加载或从其他页返回刷新 + if (this.data.firstLoad) { + this.setData({ firstLoad: false }) + this.getOrderList() + } }, - /** - * 生命周期函数--监听页面隐藏 - */ - onHide: function () { - - }, - - /** - * 生命周期函数--监听页面卸载 - */ - onUnload: function () { - - }, - - /** - * 页面相关事件处理函数--监听用户下拉动作 - */ onPullDownRefresh: function () { - //this.getOrderList(); - var that = this; - wx.request({ - url: app.globalData.url + '/system/view/list', - header: app.getRequestHeader(), - method: 'GET', - data: { - pageNum: 1, - pageSize: 30, - status: that.data.status[that.data.currentType], - usercId: wx.getStorageSync('uid') - }, - success: (res) => { - var resp = res.data; - if (res.data.code == 200) { - that.setData({ - pageNum: 1, - order_list: resp.rows || resp.data, - }); - wx.stopPullDownRefresh({ - success: (res) => {}, - }) - } - } - }); + this.setData({ pageNum: 1 }) + this.refreshList().finally(() => wx.stopPullDownRefresh()) }, - /** - * 页面上拉触底事件的处理函数 - */ onReachBottom: function () { - console.log("==页面上拉触底事件的处理函数==") - // this.getOrderList(); + if (this.data.hasMore && !this.data.loading) { + this.getOrderList() + } }, - /** - * 用户点击右上角分享 - */ - onShareAppMessage: function () { - - }, - //订单状态:-2:已取消 -1:拒绝接单 0:待接单 1:已接单 2:服务中 3:待支付 4:已完成 5申请退款 6退款中 7已退款 8:已结算 - getOrderList: function () { - var that = this; - wx.request({ - url: app.globalData.url + '/system/view/list', - header: app.getRequestHeader(), - method: 'GET', - data: { - pageNum: this.data.pageNum, - pageSize: 30, - status: that.data.status[that.data.currentType], - usercId: wx.getStorageSync('uid') - }, - success: (res) => { - var resp = res.data; - if (res.data.code == 200) { - if (this.data.pageNum == 1) { - that.setData({ - order_list: resp.rows || resp.data, - pageNum: this.data.pageNum + 1 - }); - - } else { - that.setData({ - order_list: this.data.order_list.concat(resp.rows || resp.data), - pageNum: this.data.pageNum + 1 - }); - } + async refreshList() { + this.setData({ loading: true }) + try { + var res = await request({ + url: '/system/view/list', + method: 'GET', + loading: false, + data: { + pageNum: 1, + pageSize: 30, + status: this.data.status[this.data.currentType], + usercId: wx.getStorageSync('uid') } + }) + var list = res.rows || res.data || [] + this.setData({ + order_list: list, + pageNum: 2, + hasMore: list.length >= 30 + }) + } catch (e) {} + this.setData({ loading: false }) + }, + + async getOrderList() { + if (this.data.loading) return + this.setData({ loading: true }) + try { + var res = await request({ + url: '/system/view/list', + method: 'GET', + loading: this.data.pageNum === 1, + data: { + pageNum: this.data.pageNum, + pageSize: 30, + status: this.data.status[this.data.currentType], + usercId: wx.getStorageSync('uid') + } + }) + var newList = res.rows || res.data || [] + if (this.data.pageNum === 1) { + this.setData({ + order_list: newList, + pageNum: 2, + hasMore: newList.length >= 30 + }) + } else { + this.setData({ + order_list: this.data.order_list.concat(newList), + pageNum: this.data.pageNum + 1, + hasMore: newList.length >= 30 + }) } - }); + } catch (e) {} + this.setData({ loading: false }) }, }) \ No newline at end of file diff --git a/coupon/pages/order/order.wxml b/coupon/pages/order/order.wxml index 2d6cdaf..4b1d260 100644 --- a/coupon/pages/order/order.wxml +++ b/coupon/pages/order/order.wxml @@ -35,7 +35,7 @@ - + {{item.userbName}} {{item.serviceTime}}天 diff --git a/coupon/pages/orderdetail/orderdetail.js b/coupon/pages/orderdetail/orderdetail.js index 46baf4e..b2af0be 100644 --- a/coupon/pages/orderdetail/orderdetail.js +++ b/coupon/pages/orderdetail/orderdetail.js @@ -1,11 +1,9 @@ //获取应用实例 var app = getApp(); -// pages/peihu.js +const { request } = require('../../utils/request') + Page({ - /** - * 页面的初始数据 - */ data: { dingdan_msg: false, ewm_msg: false, @@ -20,6 +18,8 @@ Page({ refund: '', charge_phone: '', refund_box: false, + paying: false, + refund_reason: '', list: [{ text: '不喜欢/不想要', }, @@ -42,74 +42,50 @@ Page({ show: false, height: '' }, + // 确认支付 - confirm_pay: async function () { - var that = this; - if (!this.data.is_ok_click) { - that.setData({ - is_ok_click: true - }) - - - //你要执行的操作 - this.godetail() - setTimeout(function () { - that.setData({ - is_ok_click: false - }) - }, 10000) //阻止一秒点击多次 - } else { - // wx.showToast({ - // title: '请勿重复操作!', - // }) - } + confirm_pay() { + if (this.data.paying) return + this.setData({ paying: true }) + this.godetail() }, - godetail() { - console.log('点击了去付款'); - var _this = this; - console.log(this.data.id + 'tttttttttttttttttttttttttttttttttt') - wx.request({ - // url: app.globalData.url + '/system/weixinPayNew/12345666', - //order_id为8位数字 - url: app.globalData.url + '/system/order/weixinPay/' + this.data.order_id, - header: app.getRequestHeader(), - method: 'GET', - data: { + async godetail() { + var _this = this + try { + // 1. 获取支付参数 + var res = await request({ + url: '/system/order/weixinPay/' + _this.data.order_id, + method: 'GET', + loading: true, + loadingText: '加载支付信息...', + showError: true + }) - }, - success(res) { - if (res.data.code == 200) { - console.log('timeStamp==' + res.data.data.timeStamp) - console.log('nonceStr==' + res.data.data.nonceStr) - console.log('package==' + res.data.data.package) - console.log('signType==' + res.data.data.signType) - console.log('paySign==' + res.data.data.paySign) - wx.requestPayment({ - timeStamp: res.data.data.timeStamp, - nonceStr: res.data.data.nonceStr, - package: res.data.data.package, - signType: res.data.data.signType, - paySign: res.data.data.paySign, - success(res) { - console.log('支付成功'); - wx.navigateTo({ - url: '/shopping/pages/paysuccess/paysuccess?order_id=' + _this.data.order_id + '&paid=1', - }) - }, - fail(res) { - console.log('支付失败'); - } + // 2. 调起微信支付 + wx.requestPayment({ + timeStamp: res.data.timeStamp, + nonceStr: res.data.nonceStr, + package: res.data.package, + signType: res.data.signType, + paySign: res.data.paySign, + success() { + wx.redirectTo({ + url: '/shopping/pages/paysuccess/paysuccess?order_id=' + _this.data.order_id + '&paid=1', }) - } else { + }, + fail(payErr) { wx.showToast({ - title: res.data.msg, - icon: 'error', + title: '支付取消或失败', + icon: 'none', duration: 2000 }) } - } - }) + }) + } catch (e) { + // 错误提示已由 request 工具处理 + } + _this.setData({ paying: false }) }, /** @@ -189,24 +165,18 @@ Page({ }, //订单详情 - getOrderDetail: function () { - var that = this; - wx.request({ - url: app.globalData.url + '/system/view/getInfo', - header: app.getRequestHeader(), - method: 'get', - data: { - orderId: this.data.order_id - }, - success: function (res) { - var resp = res.data; - if (res.data.code == 200) { - that.setData({ - item: res.data.data - }); - } + async getOrderDetail() { + try { + var res = await request({ + url: '/system/view/getInfo', + method: 'GET', + loading: true, + data: { orderId: this.data.order_id } + }) + if (res.data) { + this.setData({ item: res.data }) } - }); + } catch (e) {} }, /** * 订单状态:0:待接单 1:已接单 时候的取消订单 @@ -357,6 +327,15 @@ Page({ mengceng: true }) }, + /** + * 已完成订单申请退款 + */ + applyRefund() { + this.setData({ + refund_box: true, + mengceng: true + }) + }, /** * 填写原因开始退款 */ @@ -364,27 +343,36 @@ Page({ this.refund() }, /** - * 退款 + * 退款申请 */ refund() { var that = this; + var reason = this.data.refund_reason || ''; wx.request({ - url: app.globalData.url + '/system/order/refundOrder/' + this.data.order_id, - header: app.getRequestHeader(), - method: 'GET', + url: app.globalData.url + '/system/order/applyRefund/' + this.data.order_id, + header: { + 'content-type': 'application/json', + 'Authorization': 'Bearer ' + app.getCache("token") + }, + method: 'POST', data: { - // id: this.data.item.orderId, + remark: reason }, success: (res) => { var resp = res.data; if (res.data.code == 200) { wx.showToast({ - title: '退款申请成功', + title: resp.msg || '退款申请成功', + icon: 'none' }) setTimeout(function () { - //要延时执行的代码 wx.navigateBack() - }, 2000) //延迟时间 这里是2秒 + }, 2000) + } else { + wx.showToast({ + title: resp.msg || '申请失败', + icon: 'none' + }) } } }); @@ -394,6 +382,13 @@ Page({ mengceng: false }) }, + radioChange(e) { + var idx = e.detail.value; + var list = this.data.list; + this.setData({ + refund_reason: list[idx] ? list[idx].text : '' + }) + }, nocancel() { this.setData({ dingdan_msg: false, @@ -415,26 +410,6 @@ Page({ phoneNumber: this.data.charge_phone, }) }, - //订单详情 - getOrderDetail: function () { - var that = this; - wx.request({ - url: app.globalData.url + '/system/view/getInfo/', - header: app.getRequestHeader(), - method: 'get', - data: { - orderId: this.data.order_id - }, - success: function (res) { - var resp = res.data; - if (res.data.code == 200) { - that.setData({ - item: res.data.data - }); - } - } - }); - }, /** * 用户点击右上角分享 */ diff --git a/coupon/pages/orderdetail/orderdetail.wxml b/coupon/pages/orderdetail/orderdetail.wxml index 0a084e6..9f4c063 100644 --- a/coupon/pages/orderdetail/orderdetail.wxml +++ b/coupon/pages/orderdetail/orderdetail.wxml @@ -59,6 +59,13 @@ + + + + 申请退款 + + + 联系信息 @@ -153,7 +160,7 @@ {{item.text}} - + @@ -173,7 +180,7 @@ {{item.text}} - + diff --git a/coupon/pages/orderdetail/orderdetailbeifen.js b/coupon/pages/orderdetail/orderdetailbeifen.js index f0f9e40..53a52e6 100644 --- a/coupon/pages/orderdetail/orderdetailbeifen.js +++ b/coupon/pages/orderdetail/orderdetailbeifen.js @@ -339,7 +339,12 @@ Page({ }) }, fail(res) { - console.log('支付失败'); + console.log('支付失败', res); + wx.showToast({ + title: '支付取消或失败', + icon: 'none', + duration: 2000 + }); } }) } else { diff --git a/coupon/pages/peihu/peihu.js b/coupon/pages/peihu/peihu.js index 768963f..e616245 100644 --- a/coupon/pages/peihu/peihu.js +++ b/coupon/pages/peihu/peihu.js @@ -1,70 +1,69 @@ //获取应用实例 var app = getApp(); +const { request } = require('../../utils/request') + Page({ data: { - option1: [{ - text: '切换医院', - value: 0 - }, - { - text: '西安市高新医院', - value: 1 - }, - { - text: '第四军医大学西京医院', - value: 2 - }, - { - text: '陕西省人民医院', - value: 3 - }, - ], + option1: [{ text: '切换医院', value: 0 }], value1: 0, version_number: '1.0', height: '', url: app.globalData.url, - hospitalId:'' + hospitalId: '', + special_offer: [], + firstLoad: true }, shuChange: function (e) { - console.log(e.detail); - - this.setData({ - hospitalId: e.detail - }) - this.getBannerAndCat(); + this.setData({ hospitalId: e.detail }) + this.getCaregiverList(); }, - //生命周期回调——监听小程序启动或切前台。 onShow: function () { - this.getBannerAndCat(); + if (this.data.firstLoad) { + this.setData({ firstLoad: false }) + this.getHospitals() + this.getCaregiverList() + } }, - //首页信息 - getBannerAndCat: function () { + + // 加载医院列表 + getHospitals: function () { var that = this; - wx.request({ - url: app.globalData.url + '/system/hospitalUserView/list', - header: app.getRequestHeader(), + request({ + url: '/system/hospital/list', method: 'GET', - data: { - hospitalId:this.data.hospitalId - }, - success: (res) => { - var resp = res.data; - if (res.data.code == 200) { - that.setData({ - special_offer: resp.data, - }); - } - } - }); + loading: false + }).then(res => { + var hospitals = res.rows || res.data || []; + var options = [{ text: '切换医院', value: 0 }]; + hospitals.forEach(function (h, i) { + options.push({ + text: h.hospitalname, + value: i + 1 + }) + }) + that.setData({ option1: options, hospitalData: hospitals }) + }).catch(() => {}) + }, + + // 加载陪护人员列表 + getCaregiverList: function () { + var that = this; + request({ + url: '/system/hospitalUserView/list', + method: 'GET', + loading: true, + data: { hospitalId: this.data.hospitalId } + }).then(res => { + that.setData({ special_offer: res.data || [] }) + }).catch(() => {}) }, // 跳转详情 details(e) { if (e.currentTarget.dataset.item != 0) { let item = JSON.stringify(e.currentTarget.dataset.item); - console.log("传递的item" + item); wx.navigateTo({ url: "/shopping/pages/goddess/goddess?item=" + item, }); @@ -78,15 +77,10 @@ Page({ this.setData({ statusBarHeight: statusBarHeight, height: 46 + statusBarHeight, + hospitalId: e.hospitalId || '' }); - console.log('e.hospitalId' + e.hospitalId); - this.setData({ - hospitalId: e.hospitalId - }) }, changeDateTime1(e) { - this.setData({ - dateTime1: e.detail.value - }); + this.setData({ dateTime1: e.detail.value }); }, }) \ No newline at end of file diff --git a/coupon/pages/peihu/peihu.wxml b/coupon/pages/peihu/peihu.wxml index de32a38..2c73400 100644 --- a/coupon/pages/peihu/peihu.wxml +++ b/coupon/pages/peihu/peihu.wxml @@ -43,5 +43,9 @@ + + + 暂无陪护人员 + \ No newline at end of file diff --git a/coupon/pages/peizhen/peizhen.js b/coupon/pages/peizhen/peizhen.js index fe37ceb..42a081f 100644 --- a/coupon/pages/peizhen/peizhen.js +++ b/coupon/pages/peizhen/peizhen.js @@ -1,102 +1,77 @@ //获取应用实例 var app = getApp(); +const { request } = require('../../utils/request') + Page({ data: { - option1: [{ - text: '全部医院', - value: 0 - }, - { - text: '西京医院', - value: 1 - }, - { - text: '陕西省人民医院', - value: 2 - }, - { - text: '陕西省人民医院1', - value: 5 - }, - ], + option1: [{ text: '全部医院', value: 0 }], value1: 0, version_number: '1.0', height: '', url: app.globalData.url, - itemView: [ - { - icon: "诊前约号", - title: "全程陪诊", - price:"200", - describe:"服务内容:排队 预约 挂号 缴费 取药 记录医嘱" - }, - { - icon: "诊前约号", - title: "诊前约号", - price:"20", - describe:"服务内容:协助办理完成挂号" - }, - // { - // icon: "代办买药", - // title: "代办买药", - // price:"30", - // describe:"服务内容:协助代理买药" - // }, - { - icon: "取送结果", - title: "代办问诊", - price:"50", - describe:"服务内容:使用病人或者家属取检查报告化验单向医生问明情况" - }, - { - icon: "取送结果", - title: "取送结果", - price:"50", - describe:"服务内容:包括给病人或者家属取检查报告化验单等" - }, - // { - // icon: "出入院办理", - // title: "出入院办理", - // price:"100", - // describe:"协助办理出入院手续" - // } - ], + itemView: [], + firstLoad: true }, onChange(e) { - this.setData({ - timeData: e.detail, - }); + this.setData({ timeData: e.detail }); }, - //生命周期回调——监听小程序启动或切前台。 - onShow: function () { - this.getBannerAndCat(); - }, - //首页信息 - getBannerAndCat: function () { - var that = this; - wx.request({ - url: app.globalData.url + '/system/hospitalUserView/list', - header: app.getRequestHeader(), - method: 'GET', - data: { - }, - success: (res) => { - var resp = res.data; - if (res.data.code == 200) { - that.setData({ - special_offer: resp.data, - }); - } + onShow: function () { + if (this.data.firstLoad) { + this.setData({ firstLoad: false }) + this.getHospitals() + this.getServiceTypes() + } + }, + + // 加载医院列表(用于下拉选择) + getHospitals: function () { + var that = this; + request({ + url: '/system/hospital/list', + method: 'GET', + loading: false + }).then(res => { + var hospitals = res.rows || res.data || []; + var options = [{ text: '全部医院', value: 0 }]; + hospitals.forEach(function (h, i) { + options.push({ + text: h.hospitalname, + value: i + 1 + }) + }) + that.setData({ option1: options, hospitalData: hospitals }) + }).catch(() => {}) + }, + + // 加载服务类型 + getServiceTypes: function () { + var that = this; + request({ + url: '/system/serviceType/enabled', + method: 'GET', + data: { category: '2' }, + loading: false + }).then(res => { + var services = res.data || []; + if (services.length) { + var mapped = services.map(function (s) { + return { + icon: s.icon || s.name, + title: s.name, + price: s.price, + describe: s.description || '' + } + }) + that.setData({ itemView: mapped }) } - }); + }).catch(() => {}) }, // 跳转详情 details(e) { if (e.currentTarget.dataset.item != 0) { let item = JSON.stringify(e.currentTarget.dataset.item); - console.log("传递的item" + item); wx.navigateTo({ url: "/pages/peizhen/peizhendetail?item=" + item, }); @@ -113,8 +88,6 @@ Page({ }); }, changeDateTime1(e) { - this.setData({ - dateTime1: e.detail.value - }); + this.setData({ dateTime1: e.detail.value }); }, }) \ No newline at end of file diff --git a/coupon/pages/peizhen/peizhen.wxml b/coupon/pages/peizhen/peizhen.wxml index 535060b..610628e 100644 --- a/coupon/pages/peizhen/peizhen.wxml +++ b/coupon/pages/peizhen/peizhen.wxml @@ -21,39 +21,29 @@ - - - - - - - - - - {{item.title}} - - {{"男"}} - {{"女"}} - {{item.price}}元 - - - {{item.describe}} - - + + + + + + + + {{item.title}} + + {{"男"}} + {{"女"}} + {{item.price}}元 + + {{item.describe}} + + - - - + + + + + 暂无服务 - - - - - - - - - diff --git a/coupon/pages/peizhen/peizhendetail.js b/coupon/pages/peizhen/peizhendetail.js index 1fc7d65..6331adc 100644 --- a/coupon/pages/peizhen/peizhendetail.js +++ b/coupon/pages/peizhen/peizhendetail.js @@ -1,18 +1,16 @@ const app = getApp() +const { request } = require('../../utils/request') Page({ - /** - * 页面的初始数据 - */ data: { task: { phone: '', phonedisplay: false, placeholder: '请输入联系方式' }, - objectArray: [{ text: '加载中...', value: 0 }], // 医院列表(动态加载) - objectIndex: 0, // 默认显示位置 + objectArray: [{ text: '请选择医院', value: 0 }], + objectIndex: 0, show: true, height: '', seckill_start: '请输入开始日期', @@ -33,16 +31,17 @@ Page({ id: 0, order_id: '', timeData: {}, - Height: "", //这是swiper要动态设置的高度属性 + Height: "", charge_phone: '', - is_ok_click: false, + submitting: false, clock: '', address: '', - danjia: 0.01, //单位为元 + danjia: 0.01, beizhu: '', name: '', jzphone: '', - idcard: '' // 患者身份证号(可选) + idcard: '', + hospitalsLoaded: false }, inputtaskphone(e) { @@ -85,58 +84,29 @@ Page({ }) }, - // 确认支付 - confirm_payy: async function () { - // wx.showToast({ - // title: '点击了', - // icon: 'none' - // }); - var that = this; - if (!this.data.is_ok_click) { - that.setData({ - is_ok_click: true - }) - //你要执行的操作 - this.payment() - setTimeout(function () { - that.setData({ - is_ok_click: false - }) - }, 10000) //阻止一秒点击多次 - } else { - // wx.showToast({ title:'请勿重复操作!', icon: 'none' }); - } + // 提交订单 + confirm_payy() { + if (this.data.submitting) return + this.payment() }, + //加载医院列表 loadHospitals: function () { var that = this; - wx.request({ - url: app.globalData.url + '/system/hospital/list', - header: app.getRequestHeader(), + request({ + url: '/system/hospital/list', method: 'GET', - data: {}, - success: (res) => { - console.log('医院列表响应:', res.statusCode, JSON.stringify(res.data).substring(0, 200)); - if (res.data.code == 200) { - var hospitals = res.data.rows || res.data.data || []; - var list = [{ text: '请选择医院', value: 0 }]; - hospitals.forEach(function (h) { - list.push({ - text: h.hospitalname, - value: h.id - }); - }); - that.setData({ objectArray: list }); - } else { - console.error('医院列表加载失败 code:', res.data.code, 'msg:', res.data.msg); - that.setData({ objectArray: [{ text: '加载失败,点击重试', value: 0 }] }); - } - }, - fail: (err) => { - console.error('医院列表网络错误:', err); - that.setData({ objectArray: [{ text: '网络错误,点击重试', value: 0 }] }); - } - }); + loading: false + }).then(res => { + var hospitals = res.rows || res.data || []; + var list = [{ text: '请选择医院', value: 0 }]; + hospitals.forEach(function (h) { + list.push({ text: h.hospitalname, value: h.id }); + }); + that.setData({ objectArray: list, hospitalsLoaded: true }); + }).catch(() => { + that.setData({ objectArray: [{ text: '加载失败,点击重试', value: 0 }] }); + }) }, getprice: function () { var that = this; @@ -190,80 +160,54 @@ Page({ return true } }, - payment() { - + async payment() { + // 表单校验 if (this.data.objectIndex == 0) { - wx.showToast({ - title: '请输选择医院', - icon: 'none' - }); + wx.showToast({ title: '请选择医院', icon: 'none' }) return } - if (this.data.jzphone == '') { - wx.showToast({ - title: '请输入就诊人电话', - icon: 'none' - }); + if (!this.data.jzphone) { + wx.showToast({ title: '请输入就诊人电话', icon: 'none' }) + return + } + if (!/^1[3-9]\d{9}$/.test(this.data.jzphone)) { + wx.showToast({ title: '手机号码格式不正确', icon: 'none' }) + return + } + if (this.data.seckill_start == '请输入开始日期') { + wx.showToast({ title: '请选择开始日期', icon: 'none' }) return } - - var regtel = new RegExp('(^1[3|4|5|7|8][0-9]{9}$)', 'g'); - if (!regtel.exec(this.data.jzphone )) { - wx.showToast({ - title: '手机号码格式不正确!', - icon: 'none' - }); - return + this.setData({ submitting: true }) + try { + var res = await request({ + url: '/system/order/insertOrderPz', + method: 'POST', + loading: true, + loadingText: '提交订单中...', + data: { + cId: wx.getStorageSync('uid'), + yuguMoney: this.data.item.price, + yuliu9: "2", + yuliu10: this.data.item.ordertype, + hospitalId: this.data.objectArray[this.data.objectIndex].value, + startTime: this.data.seckill_start, + yuliu11: this.data.jzphone, + yuliu12: this.data.beizhu, + yuliu13: this.data.address, + yuliu14: this.data.idcard + }, + header: { 'content-type': 'application/json' } + }) + // redirectTo 防止返回重复下单 + wx.redirectTo({ + url: '/shopping/pages/paysuccess/paysuccess?order_id=' + res.data.orderId, + }) + } catch (e) { + // 错误提示已由 request 处理 + this.setData({ submitting: false }) } - - if (this.data.seckill_start == "请输入开始日期") { - wx.showToast({ - title: '请输入开始日期', - icon: 'none' - }); - return - } - - var that = this; - wx.request({ - url: app.globalData.url + '/system/order/insertOrderPz', - data: { - cId: wx.getStorageSync('uid'), //患者id - yuguMoney: this.data.item.price, //金额 - yuliu9: "2", //订单大类1-陪护 2-陪诊 - yuliu10: this.data.item.ordertype, //订单类型1-全程陪诊,2-诊前约号3-送取报告4-待办问诊 - hospitalId: this.data.objectArray[this.data.objectIndex].value, - startTime: this.data.seckill_start, - yuliu11: this.data.jzphone, //患者电话 - yuliu12: this.data.beizhu, //特殊要求 - yuliu13: this.data.address, //送达地址 - yuliu14: this.data.idcard //患者身份证号(可选) - }, - method: 'POST', - header: { - 'content-type': 'application/json', - 'Authorization': 'Bearer ' + app.getCache("token") - }, - success(res) { - if (res.data.code == 200) { - that.setData({ - order_id: res.data.data.orderId, - }) - console.log('order_id==' + res.data.data.orderId) - app.console(that.data.order_id + "======要传的order_id") - wx.navigateTo({ - url: '/shopping/pages/paysuccess/paysuccess?' + "order_id=" + that.data.order_id, - }); - } else { - wx.showToast({ - title: res.data.msg, - icon: 'error', - duration: 2000 - }) - } - } - }) }, bindDateChange: function (e) { @@ -340,10 +284,9 @@ Page({ * 生命周期函数--监听页面显示 */ onShow: function () { - var that = this; - this.loadHospitals(); - this.getprice(); - this.getpriceType(); + if (!this.data.hospitalsLoaded) { + this.loadHospitals() + } }, /** diff --git a/coupon/pages/service/service.js b/coupon/pages/service/service.js new file mode 100644 index 0000000..3225183 --- /dev/null +++ b/coupon/pages/service/service.js @@ -0,0 +1,87 @@ +// 服务模块 +var app = getApp(); +const { request } = require('../../utils/request') + +Page({ + data: { + servicesPeizhen: [], + servicesPeihu: [], + activeTab: 0, + firstLoad: true + }, + + onShow() { + if (this.data.firstLoad) { + this.setData({ firstLoad: false }) + this.loadServices() + } + }, + + onPullDownRefresh() { + this.loadServices().finally(() => wx.stopPullDownRefresh()) + }, + + async loadServices() { + try { + // 同时加载陪诊和陪护服务 + var [res1, res2] = await Promise.all([ + request({ url: '/system/serviceType/enabled', data: { category: '2' }, loading: true, loadingText: '加载服务...' }), + request({ url: '/system/serviceType/enabled', data: { category: '1' }, loading: false }) + ]) + this.setData({ + servicesPeizhen: (res1.data || []).map(function(s) { + return { + id: s.id, + name: s.name, + title: s.name, + price: s.price, + desc: s.description, + describe: s.description, + icon: s.icon, + ordertype: s.typeCode, + typeCode: s.typeCode + } + }), + servicesPeihu: (res2.data || []).map(function(s) { + return { + id: s.id, + name: s.name, + title: s.name, + price: s.price, + desc: s.description, + describe: s.description, + icon: s.icon, + ordertype: s.typeCode, + typeCode: s.typeCode, + nickName: s.name, + introduce: s.description + } + }) + }) + } catch (e) {} + }, + + switchTab(e) { + this.setData({ activeTab: e.currentTarget.dataset.index }) + }, + + // 点击服务-跳转下单页 + onServiceTap(e) { + if (wx.getStorageSync('phone') == '') { + wx.showModal({ + title: '提示', + content: '请先登录', + success(res) { + if (res.confirm) wx.switchTab({ url: '/pages/my/my' }) + } + }) + return + } + var item = JSON.stringify(e.currentTarget.dataset.item) + if (this.data.activeTab === 0) { + wx.navigateTo({ url: '/pages/peizhen/peizhendetail?item=' + item }) + } else { + wx.navigateTo({ url: '/shopping/pages/goddess/goddess?item=' + item }) + } + } +}) diff --git a/coupon/pages/service/service.json b/coupon/pages/service/service.json new file mode 100644 index 0000000..428eb4e --- /dev/null +++ b/coupon/pages/service/service.json @@ -0,0 +1,4 @@ +{ + "usingComponents": {}, + "navigationBarTitleText": "服务项目" +} diff --git a/coupon/pages/service/service.wxml b/coupon/pages/service/service.wxml new file mode 100644 index 0000000..bfc6311 --- /dev/null +++ b/coupon/pages/service/service.wxml @@ -0,0 +1,69 @@ + + + + 全部服务 + 专业陪诊陪护,全程无忧 + + + + + + 就医陪诊 + + + + 院内陪护 + + + + + + + + + + + + + + + {{item.name}} + {{item.desc}} + + + ¥{{item.price}} + 预约 + + + + + + 暂无服务 + + + + + + + + + + + + + + {{item.name}} + {{item.desc}} + + + ¥{{item.price}} + 预约 + + + + + + 暂无服务 + + + diff --git a/coupon/pages/service/service.wxss b/coupon/pages/service/service.wxss new file mode 100644 index 0000000..0eb2c59 --- /dev/null +++ b/coupon/pages/service/service.wxss @@ -0,0 +1,134 @@ +.page { + min-height: 100vh; + background: #f5f6fa; + padding-bottom: 20rpx; +} + +.header { + background: linear-gradient(135deg, #4A90D9, #357ABD); + padding: 60rpx 40rpx 40rpx; + color: #fff; +} + +.header-title { + font-size: 40rpx; + font-weight: bold; +} + +.header-sub { + font-size: 24rpx; + margin-top: 10rpx; + opacity: 0.85; +} + +.tab-bar { + display: flex; + background: #fff; + padding: 0 30rpx; + border-bottom: 1rpx solid #eee; +} + +.tab-item { + flex: 1; + text-align: center; + padding: 24rpx 0; + font-size: 28rpx; + color: #666; + position: relative; +} + +.tab-item.active { + color: #4A90D9; + font-weight: bold; +} + +.tab-line { + width: 60rpx; + height: 6rpx; + background: #4A90D9; + border-radius: 3rpx; + margin: 8rpx auto 0; +} + +.service-list { + padding: 24rpx; +} + +.service-card { + display: flex; + align-items: center; + background: #fff; + border-radius: 16rpx; + padding: 28rpx 24rpx; + margin-bottom: 20rpx; + box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04); +} + +.card-left { + margin-right: 20rpx; +} + +.card-icon { + width: 90rpx; + height: 90rpx; + border-radius: 16rpx; + background: #f0f4ff; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.card-icon image { + width: 60rpx; + height: 60rpx; +} + +.card-body { + flex: 1; + overflow: hidden; +} + +.card-name { + font-size: 30rpx; + font-weight: 600; + color: #333; + margin-bottom: 8rpx; +} + +.card-desc { + font-size: 24rpx; + color: #999; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.card-right { + text-align: center; + margin-left: 16rpx; +} + +.card-price { + font-size: 32rpx; + font-weight: bold; + color: #FF8F1F; + margin-bottom: 10rpx; +} + +.card-btn { + background: linear-gradient(135deg, #4A90D9, #357ABD); + color: #fff; + font-size: 24rpx; + padding: 8rpx 28rpx; + border-radius: 30rpx; +} + +.empty { + display: flex; + flex-direction: column; + align-items: center; + padding-top: 160rpx; + color: #999; + font-size: 28rpx; +} diff --git a/coupon/pages/wodeqianbao/qianbao.js b/coupon/pages/wodeqianbao/qianbao.js index a3ab275..34297c4 100644 --- a/coupon/pages/wodeqianbao/qianbao.js +++ b/coupon/pages/wodeqianbao/qianbao.js @@ -1,194 +1,62 @@ -//获取应用实例 var app = getApp(); +const { request } = require('../../utils/request') + Page({ - /** - * 页面的初始数据 - */ data: { - resp:{ - "currentPage": 1, - "detail": [ - { - "account_type_name": "微信提现", - "money": "-5.79", - "time": "2022-02-24 10:40:03", - "type": "-1" - }, - { - "account_type_name": "订单退款", - "money": "0.01", - "time": "2022-02-23 15:22:05", - "type": "1" - }, - { - "account_type_name": "购买商品", - "money": "-0.01", - "time": "2022-02-23 09:27:31", - "type": "-1" - }, - { - "account_type_name": "购买商品", - "money": "-0.10", - "time": "2022-02-22 11:48:20", - "type": "-1" - }, - { - "account_type_name": "订单退款", - "money": "0.01", - "time": "2022-02-18 16:31:09", - "type": "1" - }, - { - "account_type_name": "订单退款", - "money": "0.01", - "time": "2022-02-17 11:06:04", - "type": "1" - }, - { - "account_type_name": "订单退款", - "money": "2.00", - "time": "2022-02-17 10:59:13", - "type": "1" - }, - { - "account_type_name": "购买商品", - "money": "-2.00", - "time": "2022-02-16 10:31:46", - "type": "-1" - }, - { - "account_type_name": "订单退款", - "money": "2.00", - "time": "2022-02-16 10:25:33", - "type": "1" - }, - { - "account_type_name": "购买商品", - "money": "-2.00", - "time": "2022-02-16 10:01:25", - "type": "-1" - } - ], - "erro": 0, - "lastPage": 6, - "msg": "获取数据成功", - "total": 52 - }, - order_list:[], + order_list: [], navHeight: '', menuButtonInfo: {}, - searchMarginTop: 0, // 搜索框上边距 - searchWidth: 0, // 搜索框宽度 - searchHeight: 0 // 搜索框高度 + searchMarginTop: 0, + searchWidth: 0, + searchHeight: 0, + firstLoad: true }, - back(){ + + back() { wx.navigateBack() }, - /** - * 生命周期函数--监听页面加载 - */ + onLoad: function (options) { - wx.setNavigationBarTitle({ - title: '我的钱包' - }) - + wx.setNavigationBarTitle({ title: '我的钱包' }) }, - /** - * 生命周期函数--监听页面初次渲染完成 - */ - onReady: function () { - - }, - - /** - * 生命周期函数--监听页面显示 - */ onShow: function () { - this.getGoodsList(); + if (this.data.firstLoad) { + this.setData({ firstLoad: false }) + this.getGoodsList() + } }, - /** - * 生命周期函数--监听页面隐藏 - */ - onHide: function () { - - }, - - /** - * 生命周期函数--监听页面卸载 - */ - onUnload: function () { - - }, - - /** - * 页面相关事件处理函数--监听用户下拉动作 - */ onPullDownRefresh: function () { - + this.getGoodsList().finally(() => wx.stopPullDownRefresh()) }, - /** - * 页面上拉触底事件的处理函数 - */ - onReachBottom: function () { - - }, - - /** - * 用户点击右上角分享 - */ - onShareAppMessage: function () { - - }, - // 跳转详情 - activity(e){ - + activity(e) { if (e.currentTarget.dataset.id != 0) { - console.log("你选择的课程id是"+e.currentTarget.dataset.id); - wx.navigateTo({ - url: "/shopping/pages/goddess/goddess?id=" + e.currentTarget.dataset.id, - }); - } - - }, - //商品列表 + wx.navigateTo({ + url: "/shopping/pages/goddess/goddess?id=" + e.currentTarget.dataset.id, + }); + } + }, + + // 交易明细列表 getGoodsList: function () { var that = this; - - - //假数据 - if(this.data.resp.erro==0){ - that.setData({ - order_list:this.data.resp.detail - }); - } - - - wx.request({ - url: app.buildUrl("/app/Goddesswechat/getCapitalDetails"), - header: app.getRequestHeader(), - method:'POST', - data: { - version_number: this.data.version_number, - uid:wx.getStorageSync('uid'), - token:wx.getStorageSync('token'), - page:'1', - page_size:10, - }, - success: function (res) { - var resp = res.data; - console.log(resp+"=================dddddddddddddddddddddddddddddddddddddddddddd") - console.log(resp.erro+"=================dddddddddddddddddddddddddddddddddddddddddddd") - if(resp.erro==0){ - that.setData({ - order_list:resp.detail - }); - } - } - }); - }, - + return request({ + url: '/app/Goddesswechat/getCapitalDetails', + method: 'POST', + loading: true, + data: { + uid: wx.getStorageSync('uid'), + token: wx.getStorageSync('token'), + page: '1', + page_size: 10, + } + }).then(res => { + if (res.erro === 0) { + that.setData({ order_list: res.detail || [] }) + } + }).catch(() => {}) + }, }) \ No newline at end of file diff --git a/coupon/pages/wodeqianbao/qianbao.wxml b/coupon/pages/wodeqianbao/qianbao.wxml index 0c01c26..b3be8f9 100644 --- a/coupon/pages/wodeqianbao/qianbao.wxml +++ b/coupon/pages/wodeqianbao/qianbao.wxml @@ -1,20 +1,20 @@ - - - - - - 退款到账 - 提现 - {{item.time}} - - - - - ¥{{item.money}} + + + + 退款到账 + 提现 + {{item.time}} + + + ¥{{item.money}} + + + + + 暂无交易记录 - \ No newline at end of file diff --git a/coupon/shopping/pages/goddess/goddess.js b/coupon/shopping/pages/goddess/goddess.js index de9413b..19c0713 100644 --- a/coupon/shopping/pages/goddess/goddess.js +++ b/coupon/shopping/pages/goddess/goddess.js @@ -1,10 +1,8 @@ const app = getApp() +const { request } = require('../../../utils/request') Page({ - /** - * 页面的初始数据 - */ data: { height: '', seckill_start: '请输入开始日期', @@ -26,175 +24,81 @@ Page({ id: 0, order_id: '', timeData: {}, - Height: "", //这是swiper要动态设置的高度属性 + Height: "", charge_phone: '', - is_ok_click: false, + submitting: false, clock: '', address: '', - danjia: 0.01,//单位为元 + danjia: 0.01, beizhu: '', }, - // 确认支付 - confirm_pay: async function () { - var that = this; - if (!this.data.is_ok_click) { - that.setData({ - is_ok_click: true - }) - //你要执行的操作 - this.payment() - setTimeout(function () { - that.setData({ - is_ok_click: false - }) - }, 10000) //阻止一秒点击多次 - } else { - // wx.showToast({ title:'请勿重复操作!', icon: 'none' }); - } + confirm_pay() { + if (this.data.submitting) return + this.payment() }, - //获取单价 - getprice: function () { - var that = this; - wx.request({ - url: app.globalData.url + '/system/config/configKey/price', - header: app.getRequestHeader(), - method: 'GET', - data: {}, - success: (res) => { - var resp = res.data; - if (res.data.code == 200) { - that.setData({ - special_offer: resp.data, - }); - } - } - }); - }, - //获取类型 - getpriceType: function () { - var that = this; - wx.request({ - url: app.globalData.url + '/system/config/configKey/priceType', - header: app.getRequestHeader(), - method: 'GET', - data: {}, - success: (res) => { - var resp = res.data; - if (res.data.code == 200) { - that.setData({ - special_offer: resp.data, - }); - } - } - }); - }, - payment() { + + async payment() { if (this.data.seckill_start == "请输入开始日期") { - wx.showToast({ - title: '请输入开始日期', - icon: 'none' - }); + wx.showToast({ title: '请选择开始日期', icon: 'none' }) return } if (this.data.seckill_endx == "请输入结束日期") { - wx.showToast({ - title: '请输入结束日期', - icon: 'none' - }); + wx.showToast({ title: '请选择结束日期', icon: 'none' }) + return + } + // 校验日期先后 + var start = new Date(this.data.seckill_start.replace(/-/g, "/")) + var end = new Date(this.data.seckill_endx.replace(/-/g, "/")) + if (end < start) { + wx.showToast({ title: '结束日期不能早于开始日期', icon: 'none' }) return } - var that = this; - wx.request({ - url: app.globalData.url + '/system/order', - data: { - startTime: this.data.seckill_start, - endTime: this.data.seckill_endx, - bId: this.data.item.userId, - yuguMoney: this.data.paymoney, - price: this.data.danjia,// - priceType: "1",//1是按天 - hospitalId: this.data.item.hospitalId, - cId: wx.getStorageSync('uid'), - remark: this.data.beizhu, - }, - method: 'POST', - header: { - 'content-type': 'application/json', - 'Authorization': 'Bearer ' + app.getCache("token") - }, - success(res) { - if (res.data.code == 200) { - that.setData({ - order_id: res.data.data.orderId, - }) - console.log('order_id==' + res.data.data.orderId) - app.console(that.data.order_id + "======要传的order_id") - wx.navigateTo({ - url: '/shopping/pages/paysuccess/paysuccess?' + "order_id=" + that.data.order_id, - }); - // console.log('timeStamp==' + res.data.data.timeStamp) - // console.log('nonceStr==' + res.data.data.nonceStr) - // console.log('package==' + res.data.data.package) - // console.log('signType==' + res.data.data.signType) - // console.log('paySign==' + res.data.data.paySign) - // wx.requestPayment({ - // timeStamp: res.data.data.timeStamp, - // nonceStr: res.data.data.nonceStr, - // package: res.data.data.package, - // signType: res.data.data.signType, - // paySign: res.data.data.paySign, - // success(res) { - // console.log('支付成功'); - // app.console(that.data.order_id + "======要传的order_id") - // wx.navigateTo({ - // url: '/shopping/pages/paysuccess/paysuccess?' + "order_id=" + that.data.order_id, - // }); - // }, - // fail(res) { - // console.log('支付失败'); - // } - // }) - } else { - wx.showToast({ - title: res.data.msg, - icon: 'error', - duration: 2000 - }) - } - } - }) + this.setData({ submitting: true }) + try { + var res = await request({ + url: '/system/order', + method: 'POST', + loading: true, + loadingText: '提交订单中...', + data: { + startTime: this.data.seckill_start, + endTime: this.data.seckill_endx, + bId: this.data.item.userId, + yuguMoney: this.data.paymoney, + price: this.data.danjia, + priceType: "1", + hospitalId: this.data.item.hospitalId, + cId: wx.getStorageSync('uid'), + remark: this.data.beizhu, + }, + header: { 'content-type': 'application/json' } + }) + wx.redirectTo({ + url: '/shopping/pages/paysuccess/paysuccess?order_id=' + res.data.orderId, + }) + } catch (e) { + this.setData({ submitting: false }) + } }, bindDateChange: function (e) { - this.setData({ - seckill_start: e.detail.value - }) + this.setData({ seckill_start: e.detail.value }) }, bindDateChangetwo: function (e) { - this.setData({ - seckill_endx: e.detail.value, - }) + this.setData({ seckill_endx: e.detail.value }) var day = this.checkDate() - var totalmoney = this.data.danjia* day; - var money = totalmoney.toFixed(2) - this.setData({ - paymoney: money - }) + var money = (this.data.danjia * day).toFixed(2) + this.setData({ paymoney: money }) }, checkDate: function () { - //日期格式化 var start_date = new Date(this.data.seckill_start.replace(/-/g, "/")); var end_date = new Date(this.data.seckill_endx.replace(/-/g, "/")); - //转成毫秒数,两个日期相减 var ms = end_date.getTime() - start_date.getTime(); - //转换成天数 var day = parseInt(ms / (1000 * 60 * 60 * 24)); - //do something - console.log("day = ", day); - return day + 1 + return Math.max(day + 1, 0) }, onClickLeft() { wx.navigateBack() @@ -242,9 +146,6 @@ Page({ * 生命周期函数--监听页面显示 */ onShow: function () { - var that = this; - this.getprice(); - this.getpriceType(); }, /** diff --git a/coupon/utils/config.js b/coupon/utils/config.js index c992ce6..5ef7a41 100644 --- a/coupon/utils/config.js +++ b/coupon/utils/config.js @@ -2,8 +2,8 @@ const config = { // 开发环境(本地后端,需改为本机IP) // baseUrl: 'http://192.168.1.100:8039', - // 生产环境 - baseUrl: 'http://101.43.95.130:8039', + // 生产环境(通过 nginx 代理,HTTPS + 域名) + baseUrl: 'https://ruilaizipj.com/prod-api', } module.exports = config diff --git a/coupon/utils/request.js b/coupon/utils/request.js new file mode 100644 index 0000000..9e01554 --- /dev/null +++ b/coupon/utils/request.js @@ -0,0 +1,89 @@ +/** + * 通用网络请求工具 + * - 自动 loading / hideLoading + * - 自动 fail 错误提示 + * - Promise 风格,支持 async/await + * - code != 200 自动 toast 并 reject + */ +const app = getApp() + +// 请求计数,支持并发 loading +let requestCount = 0 + +/** + * @param {object} opts + * @param {string} opts.url - API 路径(不含 baseUrl) + * @param {string} opts.method - GET/POST,默认 GET + * @param {object} opts.data - 请求参数 + * @param {boolean} opts.loading - 是否显示 loading,默认 true + * @param {string} opts.loadingText - loading 文案,默认 "加载中..." + * @param {boolean} opts.showError - 失败时是否自动 toast,默认 true + * @param {object} opts.header - 自定义 header(会合并默认 header) + * @returns {Promise} res.data - 响应体 { code, msg, data } + */ +function request(opts) { + const { + url, + method = 'GET', + data = {}, + loading = true, + loadingText = '加载中...', + showError = true, + header = {} + } = opts + + if (loading) { + if (requestCount === 0) { + wx.showLoading({ title: loadingText, mask: true }) + } + requestCount++ + } + + return new Promise((resolve, reject) => { + wx.request({ + url: app.globalData.url + url, + method, + data, + header: { + 'content-type': 'application/x-www-form-urlencoded', + 'Authorization': 'Bearer ' + app.getCache('token'), + ...header + }, + success(res) { + if (res.data.code === 200) { + resolve(res.data) + } else { + if (showError) { + wx.showToast({ + title: res.data.msg || '请求失败', + icon: 'none', + duration: 2000 + }) + } + reject(res.data) + } + }, + fail(err) { + if (showError) { + wx.showToast({ + title: '网络异常,请稍后重试', + icon: 'none', + duration: 2000 + }) + } + reject(err) + }, + complete() { + if (loading) { + requestCount-- + if (requestCount <= 0) { + requestCount = 0 + wx.hideLoading() + } + } + } + }) + }) +} + +module.exports = { request } diff --git a/docs/SpringSecurity权限配置踩坑记录.md b/docs/SpringSecurity权限配置踩坑记录.md new file mode 100644 index 0000000..7877556 --- /dev/null +++ b/docs/SpringSecurity权限配置踩坑记录.md @@ -0,0 +1,68 @@ +# Spring Security anonymous() vs permitAll() 踩坑记录 + +## 现象 + +后台管理系统登录后,访问 `业务管理 > 医院管理` 页面提示"系统接口异常",后端返回: + +```json +{ + "timestamp": "2026-05-25T03:08:28.944+08:00", + "status": 403, + "error": "Forbidden", + "path": "/system/hospital/list" +} +``` + +直接用 curl 不带 token 调用接口正常返回 200,但带上 JWT token 就返回 403。 + +## 根因 + +`SecurityConfig.java` 中 `/system/hospital/list` 被配置为 `.anonymous()`: + +```java +.antMatchers("/login", ..., "/system/hospital/list", ...).anonymous() +``` + +**Spring Security 中两种访问控制的区别:** + +| 配置 | 含义 | 匿名用户 | 已认证用户 | +|------|------|----------|------------| +| `.permitAll()` | 所有用户均可访问 | 允许 | 允许 | +| `.anonymous()` | 仅匿名用户可访问 | 允许 | **拒绝(403)** | + +当后台管理员登录后,JWT 过滤器为请求注入了认证信息(不再是匿名用户),Spring Security 判定已认证用户无权访问 `.anonymous()` 资源,返回 403。 + +而小程序端调用该接口时不带 token(匿名),所以一直正常。 + +## 修复 + +将需要同时被"小程序(匿名)"和"后台(已认证)"访问的接口,从 `.anonymous()` 移至 `.permitAll()`: + +```java +// 仅匿名用户 —— 登录、注册、验证码等 +.antMatchers("/login","/appLogin","/system/msm","/smsLogin", + "/register","/captchaImage","/weixinLogin","/getUserToken", + "/system/weixin/wxPayNotify","/system/weixin/wxRefundNotify") + .anonymous() + +// 所有用户均可访问 —— 被小程序和后台共同使用的数据接口 +.antMatchers("/system/user/getAppIndexInfo","/system/hospital/list", + "/system/config/configKey/**","/system/nursing/published", + "/system/nursing/view/**","/system/serviceType/enabled") + .permitAll() +``` + +## 经验总结 + +1. **`.anonymous()` 是排他性的** —— 已登录用户访问会直接 403,不要用于需要同时支持前台和后台的 API +2. **`.permitAll()` vs `.anonymous()`** 选择标准: + - 登录/注册页面等"仅限未登录用户" → `.anonymous()` + - 公开数据接口(小程序+后台共用) → `.permitAll()` +3. **排查 403 时先看 SecurityConfig** —— 即使 API 直接 curl 正常,带 token 就可能因认证状态不同而变化 +4. **排查思路**:逐层验证 API → 数据库 → 菜单路由 → Nginx 代理 → Security 权限配置 + +## 相关文件 + +- `rlz/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java` +- `rlz-ui/src/views/system/hospital/index.vue` +- `rlz-ui/src/utils/request.js` (前端错误拦截器) diff --git a/docs/service_type_migration.sql b/docs/service_type_migration.sql new file mode 100644 index 0000000..9d7304d --- /dev/null +++ b/docs/service_type_migration.sql @@ -0,0 +1,19 @@ +-- 服务类型表增加 icon 字段 +ALTER TABLE rlz_service_type ADD COLUMN IF NOT EXISTS icon VARCHAR(255) DEFAULT '' COMMENT '服务图标URL'; + +-- 插入默认服务数据(如无数据) +INSERT INTO rlz_service_type (name, category, type_code, price, description, status, sort_order) +SELECT '全程陪诊', '2', '1', 200.00, '服务内容:排队 预约 挂号 缴费 取药 记录医嘱', '0', 1 +WHERE NOT EXISTS (SELECT 1 FROM rlz_service_type WHERE type_code = '1'); + +INSERT INTO rlz_service_type (name, category, type_code, price, description, status, sort_order) +SELECT '诊前约号', '2', '2', 20.00, '服务内容:协助办理完成挂号', '0', 2 +WHERE NOT EXISTS (SELECT 1 FROM rlz_service_type WHERE type_code = '2'); + +INSERT INTO rlz_service_type (name, category, type_code, price, description, status, sort_order) +SELECT '取送结果', '2', '3', 50.00, '服务内容:包括给病人或者家属取检查报告化验单等', '0', 3 +WHERE NOT EXISTS (SELECT 1 FROM rlz_service_type WHERE type_code = '3'); + +INSERT INTO rlz_service_type (name, category, type_code, price, description, status, sort_order) +SELECT '代办问诊', '2', '4', 100.00, '服务内容:使用病人或者家属取检查报告化验单向医生问明情况', '0', 4 +WHERE NOT EXISTS (SELECT 1 FROM rlz_service_type WHERE type_code = '4'); diff --git a/docs/商业化评估报告.md b/docs/商业化评估报告.md new file mode 100644 index 0000000..1527c7e --- /dev/null +++ b/docs/商业化评估报告.md @@ -0,0 +1,195 @@ +# 瑞来健康陪诊平台 — 商业化评估报告 + +> 评估日期:2026-05-25 +> 评估范围:后端(Spring Boot)、管理后台(Vue2)、Android App、微信小程序 + +--- + +## 一、项目现状总览 + +| 维度 | 状态 | 说明 | +|------|------|------| +| 技术栈 | 可维护 | Java 8 / Spring Boot 2.5.14 / Vue 2.6 / MyBatis / MySQL / 微信原生小程序 / Android Java | +| 部署 | 已跑通 | Tencent Cloud CVM + CynosDB + Docker Compose + Nginx | +| 后台管理 | 较完整 | RuoYi-Vue 框架,含医院/订单/财务/服务类型/护理资讯管理 | +| 微信小程序 | 部分完成 | 14 页面 + 3 Tab,核心流程可走,但存在假数据和空壳页面 | +| Android App | 需重构 | 从家装项目改造而来,遗留大量无关代码 | +| 安全性 | **不达标** | 支付密钥硬编码、JWT 弱密钥、手机号明文存储等多项致命问题 | + +--- + +## 二、安全问题(P0 — 商用前必须解决) + +### 2.1 致命级 + +| # | 问题 | 位置 | 风险 | +|---|------|------|------| +| 1 | 微信支付 RSA 私钥 + APIv3 Key 硬编码在源码中 | `WXPayConstantsAAA.java:23-50`、`PaySign.java:25-52`、`SysWeixinPayControllerNewBBBBB.java:34-42` | 源码泄露 = 资金被盗 | +| 2 | JWT 签名密钥 `abcdefghijklmnopqrstuvwxyz` | `application.yml:96` | 可暴力破解,伪造任意用户 Token | +| 3 | 短信接口无限流保护 | `MsmController.java` — 全平台零限流 | 可被刷短信,造成资损 | + +### 2.2 高级 + +| # | 问题 | 位置 | 风险 | +|---|------|------|------| +| 4 | 手机号明文存储 + API 返回明文 + `System.out.print` 打印 | `sys_user.phonenumber`、`SysLoginController.java:76,123`、`MsmController.java:34` | 用户隐私泄露 | +| 5 | SMS 验证码明文打印到控制台 | `MsmController.java:46` | 验证码泄露 | +| 6 | 操作日志表 `sys_oper_log` 记录完整请求体(含密码、手机号) | `LogAspect.java:142-154` | 日志库成为敏感数据泄露源 | +| 7 | WeChat AppSecret 硬编码在 URL 参数中 | `SysLoginController.java:178,226` | AppSecret 泄露 | +| 8 | Android WeChat AppSecret 硬编码 | `WXEntryActivity.java:46` | AppSecret 泄露 | + +### 2.3 中级 + +| # | 问题 | 位置 | 风险 | +|---|------|------|------| +| 9 | Druid 监控面板生产环境公开可访问(无 IP 白名单) | `SecurityConfig.java:116`、`application-docker.yml:53` | 数据库连接池信息暴露 | +| 10 | Swagger API 文档生产环境公开 | `application.yml:118`、`SecurityConfig.java:116` | API 结构暴露 | +| 11 | CSRF 全局关闭 + `X-Frame-Options` 关闭 | `SecurityConfig.java:104,120` | CSRF / 点击劫持风险 | +| 12 | ~95% 接口 `@PreAuthorize` 被注释 | 全平台 Controller | 任意认证用户可调用任意接口,无细粒度权限控制 | + +--- + +## 三、小程序功能缺陷(P1) + +| # | 页面 | 问题 | 影响 | +|---|------|------|------| +| 1 | `reforderdetail` 退款订单详情 | **全假数据**,真实 API 调用被整段注释 | 退款流程完全不可用 | +| 2 | `tuiguang/extensionInfor` 推广页 | **完全空壳**,所有生命周期函数为空 | 推广模块不存在 | +| 3 | `guanyu/guanyu` 关于/反馈页 | 表单提交调用不存在的函数 `this.payment()` | 反馈功能不可用 | +| 4 | `mechanism/mechanism` 机构页 | 机构详情跳转到不存在的 `pages/institutions/institutions` | 机构详情 404 | +| 5 | `peizhen/peizhen` + `peihu/peihu` | 医院选择器数据硬编码(不从后台接口读取) | 数据不实时 | +| 6 | `index/index` | 服务价格硬编码(200/20/100/50) | 调价需改代码发版 | +| 7 | `myshoucang/myshoucang_index` | 收藏列表全假数据 | 收藏功能不可用 | +| 8 | `my/my` | 砍价功能写死"暂无开放" | 营销功能缺失 | +| 9 | `orderdetail/orderdetail` | 客服电话硬编码 `18133233292` | 换号需改代码 | +| 10 | 全页面 | 错误处理均为 `.catch(() => {})` 静默吞错 | 用户无错误反馈 | + +--- + +## 四、Android App 问题(P2) + +| # | 问题 | +|---|------| +| 1 | 含大量原家装项目遗留代码(`DesignActivity`、`StyleActivity`、`Douyin` 接口等) | +| 2 | 两套并行网络层:Retrofit(`MyApi.java`)+ OkHttp(`NetApi.java`),架构混乱 | +| 3 | `API.java` 引用已废弃的 Bmob 云后端,含硬编码测试密钥 | +| 4 | 引用 `/device/AppCustomer/*`、`/device/AppDouyin/*` 等可能不存在的接口 | +| 5 | 包名和类名仍保留原产品命名(`kehu`、`find`、`goddess` 等) | + +--- + +## 五、基础设施缺失(P3) + +| # | 问题 | +|---|------| +| 1 | 无自动化数据库备份策略 | +| 2 | 无日志聚合 / 监控告警系统 | +| 3 | 自定义建表 SQL(`rlz_order`、`sys_hospital` 等)未入仓 | +| 4 | 无 Staging 测试环境 | +| 5 | CI/CD 仅覆盖后端 Docker 构建,小程序和 Android 无自动化 | +| 6 | 数据库 `rlz_order` 表使用 20 个通用字段 `yuliu1~20`,Schema 不透明 | + +--- + +## 六、数据库问题 + +| # | 表 | 问题 | +|---|------|------| +| 1 | `rlz_order` | 20 个 `yuliu1`~`yuliu20` 通用 varchar 列,无文档说明 | +| 2 | `rlz_order` / `sys_hospital` / `rlz_service_type` 等 | 建表 DDL 未入仓(仅 `ry_20220625.sql` + `nursing_article.sql` 在 repo 中) | +| 3 | `order_view` / `hospital_user_view` / `user_hospital_view` | 视图 DDL 未入仓 | +| 4 | `sys_user.phonenumber` | 手机号明文存储,无加密 | +| 5 | `sys_oper_log` | 存储完整请求/响应 JSON,含密码和手机号 | + +--- + +## 七、开发工作量估算 + +### 方案 A:最小可用方案(安全整改 + 关键缺陷修复)— 3~4 个月 + +| 阶段 | 工作内容 | 人月 | +|------|----------|------| +| 安全整改 | 密钥迁移环境变量、JWT 加固、全接口限流、日志脱敏、Druid/Swagger 生产关闭 | 1.5 | +| 小程序修复 | 退款页/推广页/关于页/机构页修复、硬编码改为接口调用、错误状态完善 | 1.5 | +| 基础设施 | 备份策略、基础监控告警 | 0.5 | +| 全流程测试 | 回归测试 | 0.5 | +| **合计** | | **4 人月** | + +> 2 人团队 × 2 个月,费用约 **8~15 万 RMB** + +--- + +### 方案 B:完整商用方案 — 6~8 个月 + +| 阶段 | 工作内容 | 人月 | +|------|----------|------| +| 安全整改 | 同上 + 敏感数据加密存储 + 渗透测试 + 等保咨询 | 2 | +| 小程序完整修复 | 全部页面真实接口、通知推送、图文混排优化 | 2 | +| 功能补全 | 营销模块(砍价/拼团)、收藏、反馈系统、CMS 完善 | 1.5 | +| Android 重构 | 移除遗留代码、统一网络层、端到端测试 | 2 | +| 基础设施 | CI/CD 全流程、监控告警、日志平台、多环境管理、灾备方案 | 1 | +| 合规 + 文档 | 隐私政策、用户协议、数据删除/导出、运维文档 | 1 | +| 测试 | 全平台回归 + 性能压测 | 1 | +| **合计** | | **10.5 人月** | + +> 2~3 人团队 × 4~6 个月,费用约 **25~50 万 RMB** + +--- + +## 八、运维费用估算(月度) + +| 项目 | 规格 | 费用(元/月) | +|------|------|---------------| +| 腾讯云 CVM | 4C8G | 300 ~ 600 | +| CynosDB MySQL | 按量 | 500 ~ 2,000 | +| 带宽 + CDN | 按量 | 200 ~ 800 | +| 域名 | ruilaizipj.com | ~10 | +| 微信小程序认证 | 年付 300 | 25 | +| 微信商户手续费 | 0.6% 交易额 | 按交易量 | +| 短信服务 | 验证码 + 通知 | 100 ~ 500 | +| SSL 证书 | Let's Encrypt | 0 | +| 云监控基础版 | - | 200 ~ 500 | +| 对象存储 COS | 图片/文件 | 50 ~ 300 | +| **月度合计** | | **约 1,500 ~ 5,000** | + +--- + +## 九、数据合规要求(中国法律) + +陪诊平台属于医疗健康相关服务,涉及用户敏感个人信息,需关注: + +| 法规 | 要求 | 当前状态 | +|------|------|----------| +| 《个人信息保护法》 | 收集用户信息需明确告知 + 获取同意 | 小程序无隐私弹窗 | +| 《个人信息保护法》 | 敏感个人信息(医疗健康、行踪轨迹)需单独同意 | 未实现 | +| 《个人信息保护法》 | 用户有权查阅/更正/删除/导出其个人信息 | 未实现 | +| 《网络安全法》 | 网络运营者应采取技术措施保障信息安全 | 多项安全问题未解决 | +| 《数据安全法》 | 重要数据处理者需定期风险评估 | 无 | +| 等保 2.0 | 医疗健康类系统至少等保二级 | 未做 | +| 微信小程序运营规范 | 涉及医疗健康需提供相关资质 | 需确认 | + +--- + +## 十、建议实施路径 + +``` +第 1 个月 ─── 安全整改(密钥管理 + 限流 + 日志脱敏 + 生产配置加固) +第 2 个月 ─── 小程序缺陷修复(假数据 → 真接口 + 空壳页面补全) +第 3 个月 ─── 基础设施(备份 + 监控 + CI/CD)+ 全流程测试 +第 4 个月 ─── 上线试运营,收集用户反馈 +第 5-6 个月 ─ 功能增强(营销模块、通知推送、数据看板) +第 7-8 个月 ─ Android 重构 + 合规材料准备 +``` + +--- + +## 十一、总结 + +| | 方案 A(最小可用) | 方案 B(完整商用) | +|------|------|------| +| 开发周期 | 3 ~ 4 个月 | 6 ~ 8 个月 | +| 开发费用 | 8 ~ 15 万 | 25 ~ 50 万 | +| 月度运维 | 1,500 ~ 5,000 元 | 1,500 ~ 5,000 元 | +| 交付水平 | 核心业务流程可运转 | 具备完整 SaaS 商业化能力 | + +**最紧迫事项**:安全漏洞修复(支付密钥、JWT、限流)必须在任何商用推广前完成。源码一旦泄露,攻击者可直接操作微信支付。 diff --git a/docs/域名与nginx配置方案.md b/docs/域名与nginx配置方案.md new file mode 100644 index 0000000..ad70087 --- /dev/null +++ b/docs/域名与nginx配置方案.md @@ -0,0 +1,201 @@ +# 瑞来健康 — 域名与 Nginx 配置方案 + +> 日期:2026-05-24 +> 服务器:101.43.95.130 +> 现状:与"生长激素预测模型"项目共用服务器和域名 ruilaizipj.com + +--- + +## 一、现状分析 + +### 1.1 域名占用 + +``` +ruilaizipj.com / www.ruilaizipj.com (80/443) + └── host nginx (/etc/nginx/conf.d/default.conf) + ├── / → /www/wwwroot/code/public (ThinkPHP - 生长激素项目) + └── /adminghd/ → ThinkPHP 后台管理 +``` + +### 1.2 RLZ 服务(Docker 内部) + +| 服务 | 容器 | 内部端口 | 外部访问 | +|------|------|---------|---------| +| 后端 API | rlz-backend | 8039 | `http://101.43.95.130:8039` | +| 管理后台 | rlz-ui-server | 80 | `http://101.43.95.130:8050` | +| Redis | rlz-redis | 6379 | 127.0.0.1:6379 | + +### 1.3 微信小程序约束 + +微信小程序已在微信后台登记 `https://ruilaizipj.com` 作为合法 API 域名,不能更改。 + +### 1.4 RLZ API 路径前缀 + +- 后台管理:`/dev-api/`(由前端 `VUE_APP_BASE_API` 控制) +- 微信小程序/Android:`/prod-api/`(直接请求后端路径) + +--- + +## 二、方案设计 + +### 2.1 核心思路 + +**API 走主域名路径代理,管理后台走子域名。** + +- 小程序 API → 主域名路径 `/prod-api/*`(不改微信登记,与 ThinkPHP 无冲突) +- 管理后台 → 子域名 `admin.ruilaizipj.com`(SPA 路由清爽,独立 server 块) + +### 2.2 目标架构 + +``` + 101.43.95.130 (host nginx 80/443) + │ + ┌─────────────────────────┼─────────────────────────┐ + │ │ │ + ruilaizipj.com ruilaizipj.com admin.ruilaizipj.com + (生长激素) (RLZ API) (RLZ 管理后台) + │ │ │ + / → code/public /prod-api/* / → rlz-ui-server + /adminghd/ /dev-api/* (127.0.0.1:8050) + │ (127.0.0.1:8039) + ▼ │ + ThinkPHP 5.0 ▼ + (PHP-FPM) rlz-backend + (Spring Boot) +``` + +### 2.3 SSL 证书 + +当前 Let's Encrypt 证书覆盖 `ruilaizipj.com` + `www.ruilaizipj.com`。 + +需要新增 `admin.ruilaizipj.com` 的 SAN 覆盖。两种方式: +- **方式 A**:宝塔面板 → 网站 → SSL → 重新签发(添加子域名) +- **方式 B**:手动 certbot 扩展 + +推荐方式 A,宝塔自动续期。 + +--- + +## 三、实施步骤 + +### Step 1:DNS 添加子域名 + +在域名 DNS 管理后台添加记录: +``` +admin.ruilaizipj.com → A → 101.43.95.130 +``` + +### Step 2:扩展 SSL 证书 + +通过宝塔面板重新签发证书,添加 `admin.ruilaizipj.com` 到域名列表。 + +### Step 3:修改 host nginx 配置 + +编辑 `/etc/nginx/conf.d/default.conf`,在现有 server 块中新增: + +```nginx +# === 现有 ThinkPHP 配置保持不变 === + +# 新增:RLZ 后端 API 代理 +location /prod-api/ { + proxy_pass http://127.0.0.1:8039/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_read_timeout 60s; +} + +location /dev-api/ { + proxy_pass http://127.0.0.1:8039/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_read_timeout 60s; +} +``` + +新建 `/etc/nginx/conf.d/rlz-admin.conf`: + +```nginx +server { + listen 80; + server_name admin.ruilaizipj.com; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + server_name admin.ruilaizipj.com; + + ssl_certificate /www/server/panel/vhost/cert/101.43.95.130/fullchain.pem; + ssl_certificate_key /www/server/panel/vhost/cert/101.43.95.130/privkey.pem; + ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; + ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:!MD5; + ssl_prefer_server_ciphers on; + + charset utf-8; + + location / { + proxy_pass http://127.0.0.1:8050; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_read_timeout 60s; + } + + location /prod-api/ { + proxy_pass http://127.0.0.1:8039/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + +### Step 4:验证 + +```bash +# 重载 nginx +nginx -t && nginx -s reload + +# 测试 API +curl -s https://ruilaizipj.com/prod-api/captchaImage + +# 测试管理后台 +curl -s -o /dev/null -w "%{http_code}" https://admin.ruilaizipj.com/ +``` + +### Step 5:更新前端配置 + +- `rlz-ui/.env.production`:`VUE_APP_BASE_API` 改为 `/prod-api` +- 微信小程序 `config.js`:`baseUrl` 改为 `https://ruilaizipj.com/prod-api` +- Android `BuildConfig`:Release 的 `API_BASE_URL` 改为 `https://ruilaizipj.com/prod-api` + +--- + +## 四、访问地址总览(实施后) + +| 端 | 地址 | +|------|------| +| 管理后台 | `https://admin.ruilaizipj.com` | +| 后端 API | `https://ruilaizipj.com/prod-api/` | +| 微信小程序 API | `https://ruilaizipj.com/prod-api/` | +| 生长激素项目(不变) | `https://www.ruilaizipj.com` | +| 生长激素后台(不变) | `https://www.ruilaizipj.com/adminghd/` | + +--- + +## 五、注意事项 + +1. **不要改 `server_name` 的 ThinkPHP 部分**,否则影响生长激素项目正常运行 +2. **nginx 修改前备份**:`cp /etc/nginx/conf.d/default.conf /tmp/default.conf.bak` +3. **宝塔可能覆盖 nginx 配置**,修改后注意观察 +4. **API 路径冲突检查**:确认 ThinkPHP 项目没有 `/prod-api/` 或 `/dev-api/` 路由 +5. **RLZ 前端的 `VUE_APP_BASE_API` 需要同步更新**为 `/prod-api` 并重新构建部署 diff --git a/docs/服务项目管理动态化方案.md b/docs/服务项目管理动态化方案.md new file mode 100644 index 0000000..033b15c --- /dev/null +++ b/docs/服务项目管理动态化方案.md @@ -0,0 +1,235 @@ +# 服务项目管理动态化方案 + +> 日期:2026-05-25 +> 目标:将小程序端硬编码的服务项目改为后台动态配置,支持上架/下架/调价/排序 + +--- + +## 一、现状分析 + +### 1.1 现有基础设施(已存在,可直接使用) + +| 组件 | 状态 | 位置 | +|------|------|------| +| 数据库表 `rlz_service_type` | 已有 | MySQL | +| 实体类 `RlzServiceType` | 已有 | `ruoyi-system/.../domain/RlzServiceType.java` | +| Mapper / Service / Controller | 已有 | CRUD + 分页 + Excel 导出 | +| 价格变更历史 `rlz_price_history` | 已有 | 调价自动记录操作人/时间/原因 | +| 管理后台 Vue 页面 | 已有 | `rlz-ui/src/views/system/serviceType/index.vue` | + +### 1.2 现有字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| name | String | 服务名称 | +| category | String | 类别: 1=陪护, 2=陪诊 | +| typeCode | String | 类型编码 | +| price | BigDecimal | 价格 | +| description | String | 服务内容描述 | +| status | String | 0=启用, 1=停用 | +| sortOrder | Integer | 排序(越小越靠前) | +| createBy/createTime | - | 创建信息 | +| updateBy/updateTime | - | 更新信息 | +| remark | String | 备注 | + +### 1.3 当前问题 + +小程序端服务项目全部硬编码: + +- `pages/index/index.js:13-41` — 4 个服务项,价格写死(200/20/100/50) +- `pages/peizhen/peizhen.js:26-56` — 陪诊服务列表写死 +- `pages/peihu/peihu.js` — 陪护服务列表写死 +- 图标通过 `../image/home/{{item.icon}}.png` 本地引用,无法动态配置 + +--- + +## 二、改造方案 + +### 2.1 数据库变更 + +`rlz_service_type` 表新增 `icon` 字段: + +```sql +ALTER TABLE rlz_service_type ADD COLUMN icon VARCHAR(255) DEFAULT '' COMMENT '服务图标URL'; +``` + +### 2.2 后端变更 + +#### a) 实体新增 icon 字段 + +`RlzServiceType.java` 增加 `private String icon;` 及 getter/setter。 + +#### b) Mapper XML 更新 + +`RlzServiceTypeMapper.xml` 的 resultMap / SQL / insert / update 中增加 icon 字段。 + +#### c) 新增小程序公开接口 + +```java +// 无需登录,返回所有启用状态的服务 +@GetMapping("/enabled") +public AjaxResult enabled(RlzServiceType rlzServiceType) +{ + rlzServiceType.setStatus("0"); // 只查启用的 + List list = rlzServiceTypeService + .selectRlzServiceTypeList(rlzServiceType); + return AjaxResult.success(list); +} +``` + +#### d) SecurityConfig 白名单 + +```java +.antMatchers("/system/serviceType/enabled").permitAll() +``` + +### 2.3 管理后台变更 + +`rlz-ui/src/views/system/serviceType/index.vue`: + +- 表格增加"图标"列(显示缩略图) +- 新增/编辑表单增加图标上传组件 +- 增加"上架/下架"快捷操作按钮 + +### 2.4 小程序变更 + +#### index.js(首页 4 个服务卡片) + +``` +修改前:data.itemView = [{ icon: "全程陪诊", title: "全程陪诊", price: "200", ... }] +修改后:onShow 调用 /system/serviceType/enabled?category=2 获取陪诊服务列表 +``` + +#### peizhen.js(陪诊页) + +``` +修改前:services = [{ name: "全程陪诊", price: "200" }, ...] +修改后:调用 /system/serviceType/enabled?category=2 动态加载 +``` + +#### peihu.js(陪护页) + +``` +修改前:services 硬编码 +修改后:调用 /system/serviceType/enabled?category=1 动态加载 +``` + +#### WXML 图标引用 + +```xml + + + + + +``` + +--- + +## 三、数据流 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 管理后台 (Vue Admin) │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ 服务类型管理 │ │ +│ │ [新增] [编辑] [删除] [调价] [上架/下架] │ │ +│ │ ┌──────┬──────┬──────┬──────┬──────┐ │ │ +│ │ │ 图标 │ 名称 │ 类别 │ 价格 │ 状态 │ │ │ +│ │ ├──────┼──────┼──────┼──────┼──────┤ │ │ +│ │ │ 🏥 │全程陪诊│陪诊 │ 200 │ 启用 │ │ │ +│ │ │ 📋 │诊前约号│陪诊 │ 20 │ 启用 │ │ │ +│ │ │ 💊 │代办买药│陪诊 │ 80 │ 停用 │ │ │ +│ │ └──────┴──────┴──────┴──────┴──────┘ │ │ +│ └─────────────────────────────────────────────────┘ │ +│ │ POST/PUT/DELETE │ +│ ▼ (需登录 + 权限) │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ 后端 Controller: /system/serviceType/* │ │ +│ └─────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────┐ +│ 微信小程序 │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ /system/serviceType/enabled?category=2 │ │ +│ │ [公开接口,无需登录] │ │ +│ └─────────────────────────────────────────────────┘ │ +│ │ GET │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ 返回: [{ name:"全程陪诊", price:200, icon:"...", │ │ +│ │ description:"排队预约挂号...", status:"0" │ │ +│ │ sortOrder:1 }, ...] │ │ +│ └─────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ 首页服务卡片区域 动态渲染 │ │ +│ │ [全程陪诊] [诊前约号] [代办买药] [取送结果] │ │ +│ └─────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## 四、实现步骤 + +| 步骤 | 内容 | 涉及文件 | +|------|------|----------| +| 1 | 数据库加 `icon` 字段 | DDL | +| 2 | 实体 + Mapper 更新 | `RlzServiceType.java`, `RlzServiceTypeMapper.xml` | +| 3 | Controller 增加 `/enabled` 公开接口 | `RlzServiceTypeController.java` | +| 4 | SecurityConfig 加白名单 | `SecurityConfig.java` | +| 5 | 管理后台增加图标字段 | `serviceType/index.vue` | +| 6 | 小程序首页改为 API 加载 | `index.js`, `index.wxml` | +| 7 | 小程序陪诊页改为 API 加载 | `peizhen.js`, `peizhen.wxml` | +| 8 | 小程序陪护页改为 API 加载 | `peihu.js`, `peihu.wxml` | +| 9 | 部署后端 + 测试 | Docker 构建部署 | + +--- + +## 五、接口设计 + +### GET /system/serviceType/enabled + +无需登录,根据 category 返回已启用的服务列表。 + +**请求参数:** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| category | String | 否 | 1=陪护, 2=陪诊,不传则返回全部 | + +**响应示例:** + +```json +{ + "code": 200, + "data": [ + { + "id": 1, + "name": "全程陪诊", + "category": "2", + "typeCode": "1", + "price": 200.00, + "description": "排队预约挂号缴费取药记录医嘱", + "icon": "https://ruilaizipj.com/profile/upload/icon_peizhen.png", + "status": "0", + "sortOrder": 1 + }, + { + "id": 2, + "name": "诊前约号", + "category": "2", + "typeCode": "2", + "price": 20.00, + "description": "协助办理完成挂号", + "icon": "https://ruilaizipj.com/profile/upload/icon_yuehao.png", + "status": "0", + "sortOrder": 2 + } + ] +} +``` diff --git a/docs/陪诊员服务费分账方案.md b/docs/陪诊员服务费分账方案.md new file mode 100644 index 0000000..3025e9f --- /dev/null +++ b/docs/陪诊员服务费分账方案.md @@ -0,0 +1,256 @@ +# 陪诊员服务费分账方案(A+B 结合) + +## 一、需求概述 + +订单完成后,将服务费按比例分配给平台和陪诊员,实现自动计算 + 人工审核确认的结算流程。 + +## 二、业务流程 + +``` +订单完成(status=3,待支付) + → 患者微信支付 + → 支付成功(status=4,已完成) + → 系统自动计算分账金额(根据配置的抽成比例) + → 写入结算金额到订单字段 + → 管理员后台审核确认 + → 陪诊员余额增加(yue)、累计收益更新(leijijine) + → 生成交易记录(jiaoyi_detail) + → 订单标记为已结算(status=8) +``` + +## 三、数据库改动 + +### 3.1 新增平台配置表 `rlz_platform_config` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | bigint | PK | +| `config_key` | varchar(50) | 配置键 | +| `config_value` | varchar(255) | 配置值 | +| `description` | varchar(200) | 描述 | +| `create_time` | datetime | 创建时间 | +| `update_time` | datetime | 更新时间 | + +初始化数据: +```sql +INSERT INTO rlz_platform_config (config_key, config_value, description) VALUES +('commission_rate', '20', '平台抽成比例(%)'), +('auto_settle', 'false', '是否自动结算(true/false)'); +``` + +### 3.2 现有表字段利用 + +`rlz_order` 表已有字段(无需改动): +- `jiesuan_money` — 结算金额(订单总金额) +- `kouchu_money` — 平台抽成金额 +- `fanxian_money` — 陪诊员所得金额 +- `fanxian_tax` — 抽成比例(%) +- `is_zhifu` — 打款状态(0=未打款, 1=已打款) + +`sys_user` 表已有字段(无需改动): +- `yue` — 余额(陪诊员可用余额) +- `leijijine` — 累计收益 + +`jiaoyi_detail` 表已有字段(无需改动): +- `type` — 1=收入, 2=支出 +- `jine` — 金额 +- `name` — 交易名称 +- `yue` — 交易后余额 +- `user_id` — 用户ID + +## 四、后端实现 + +### 4.1 新增类文件 + +| 类 | 路径 | 职责 | +|----|------|------| +| `RlzPlatformConfig.java` | `domain/` | 平台配置实体 | +| `RlzPlatformConfigMapper.java` | `mapper/` | 配置 Mapper | +| `RlzPlatformConfigMapper.xml` | `mapper/system/` | 配置 SQL | +| `IRlzPlatformConfigService.java` | `service/` | 配置服务接口 | +| `RlzPlatformConfigServiceImpl.java` | `service/impl/` | 配置服务实现 | +| `RlzSettlementService.java` | `service/` | 结算服务(核心逻辑) | +| `RlzSettlementController.java` | `controller/` | 结算接口 | +| `RlzTransactionController.java` | `controller/` | 交易记录查询接口 | + +### 4.2 核心服务 RlzSettlementService + +```java +@Service +public class RlzSettlementService { + + // 获取抽成比例(默认20%) + public BigDecimal getCommissionRate() { + String rate = platformConfigService.getConfigValue("commission_rate", "20"); + return new BigDecimal(rate); + } + + // 订单支付成功后自动计算分账 + public void calculateOrderFee(RlzOrder order) { + BigDecimal totalAmount = new BigDecimal(order.getYuguMoney()); + BigDecimal rate = getCommissionRate(); // 20% + BigDecimal kouchu = totalAmount.multiply(rate).divide(new BigDecimal("100")); // 平台抽成 + BigDecimal fanxian = totalAmount.subtract(kouchu); // 陪诊员所得 + + order.setJiesuanMoney(order.getYuguMoney()); // 结算金额=订单金额 + order.setKouchuMoney(kouchu.toString()); // 平台抽成 + order.setFanxianMoney(fanxian.toString()); // 陪诊员所得 + order.setFanxianTax(rate.toString()); // 抽成比例 + order.setIsZhifu("0"); // 未打款 + rlzOrderService.updateOrder(order); + } + + // 管理员确认结算(打款给陪诊员) + @Transactional + public void settleOrder(Long orderId) { + RlzOrder order = rlzOrderService.selectRlzOrderById(orderId); + if (!"4".equals(order.getStatus())) { + throw new BusinessException("订单未支付,无法结算"); + } + if ("1".equals(order.getIsZhifu())) { + throw new BusinessException("已打款,请勿重复操作"); + } + + Long caregiverId = order.getBId(); // 陪诊员ID + BigDecimal fanxianMoney = new BigDecimal(order.getFanxianMoney()); + + // 1. 更新订单打款状态 + order.setIsZhifu("1"); + order.setStatus("8"); // 已结算 + rlzOrderService.updateOrder(order); + + // 2. 更新陪诊员余额和累计收益 + SysUser caregiver = sysUserService.selectUserById(caregiverId); + BigDecimal oldBalance = new BigDecimal(caregiver.getYue() != null ? caregiver.getYue() : "0"); + BigDecimal newBalance = oldBalance.add(fanxianMoney); + caregiver.setYue(newBalance.toString()); + + BigDecimal oldTotal = new BigDecimal(caregiver.getLeijijine() != null ? caregiver.getLeijijine() : "0"); + caregiver.setLeijijine(oldTotal.add(fanxianMoney).toString()); + sysUserService.updateUser(caregiver); + + // 3. 生成交易记录 + insertTransaction(caregiverId, fanxianMoney, newBalance, order.getYuliu1()); + } + + private void insertTransaction(Long userId, BigDecimal jine, BigDecimal newBalance, String orderNo) { + JiaoyiDetail detail = new JiaoyiDetail(); + detail.setUserId(userId); + detail.setType("1"); // 收入 + detail.setJine(jine.toString()); + detail.setYue(newBalance.toString()); + detail.setName("订单结算 - " + orderNo); + jiaoyiDetailService.insertJiaoyiDetail(detail); + } +} +``` + +### 4.3 结算接口 RlzSettlementController + +```java +@RestController +@RequestMapping("/system/settlement") +public class RlzSettlementController extends BaseController { + + @Autowired + private RlzSettlementService settlementService; + + // 支付回调中自动触发计算(在 WxPayNotify 中调用) + // 此处提供手动重新计算的接口 + + // 管理员确认结算 + @PostMapping("/settle/{orderId}") + public AjaxResult settle(@PathVariable Long orderId) { + settlementService.settleOrder(orderId); + return AjaxResult.success("结算完成"); + } + + // 批量结算 + @PostMapping("/batchSettle") + public AjaxResult batchSettle(@RequestBody Long[] orderIds) { + for (Long id : orderIds) { + settlementService.settleOrder(id); + } + return AjaxResult.success("批量结算完成"); + } + + // 查询待结算订单列表(status=4 已支付但未打款) + @GetMapping("/pending") + public TableDataInfo pending() { + // 查询 status=4 AND is_zhifu='0' 的订单 + ... + } +} +``` + +### 4.4 支付回调中自动触发计算 + +在 `WxPayNotify` 处理中增加一行调用: + +```java +// 支付成功,更新 status=4 +order.setStatus("4"); +// ... 存储微信交易号 ... +rlzOrderService.updateOrder(order); + +// === 新增:自动计算分账金额 === +settlementService.calculateOrderFee(order); +``` + +## 五、后台管理面板改动 + +### 5.1 新增菜单 + +| 父菜单 | 菜单名称 | 路径 | 组件 | +|--------|----------|------|------| +| 财务管理(3000) | 结算管理 | `settlement` | `finance/settlement/index` | +| 财务管理(3000) | 交易明细 | `transaction` | `finance/transaction/index` | +| 财务管理(3000) | 平台配置 | `platformConfig` | `finance/platformConfig/index` | + +### 5.2 结算管理页面 `finance/settlement/index.vue` + +功能: +- 列表展示已支付待结算订单 +- 搜索:订单号、陪诊员姓名、日期范围 +- 列:订单号、陪诊员、订单金额、平台抽成、应发金额、支付时间、状态 +- 操作:单个结算 / 批量结算 +- 点击结算弹出确认框,显示分账明细预览 + +### 5.3 平台配置页面 `finance/platformConfig/index.vue` + +功能: +- 平台抽成比例设置(默认20%) +- 自动结算开关(暂不启用) + +## 六、SecurityConfig 更新 + +```java +// 新增匿名访问(若小程序也需要查询余额/交易记录) +// 结算接口需要认证(仅后台管理员操作) +.antMatchers("/system/settlement/**", "/system/transaction/**").authenticated() +``` + +## 七、实施步骤 + +| 步骤 | 内容 | 预估 | +|------|------|------| +| 1 | 创建 `rlz_platform_config` 表 + 初始化数据 | 10min | +| 2 | 创建配置管理后端代码(CRUD) | 20min | +| 3 | 实现 `RlzSettlementService` 核心分账逻辑 | 30min | +| 4 | 在支付回调中集成自动计算 | 10min | +| 5 | 创建结算管理后台页面 | 30min | +| 6 | 创建平台配置后台页面 | 20min | +| 7 | 部署、测试完整流程 | 20min | + +## 八、分账示例 + +假设平台抽成比例 20%,订单金额 500 元: + +| 项目 | 金额 | 计算 | +|------|------|------| +| 订单金额 | 500.00 | 患者支付 | +| 平台抽成(20%) | 100.00 | 500 × 20% | +| 陪诊员所得 | 400.00 | 500 × 80% | +| 陪诊员余额变化 | +400.00 | yue += 400 | + +管理员可在后台"平台配置"中调整抽成比例,新比例对后续订单生效。 diff --git a/peizhen/app/build.gradle b/peizhen/app/build.gradle index 7694d26..8679f61 100644 --- a/peizhen/app/build.gradle +++ b/peizhen/app/build.gradle @@ -14,14 +14,6 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true //突破应用方法数65535的一个限制 } - signingConfigs { - release { - storeFile file('july.jks') - storePassword "july" - keyAlias "123456" - keyPassword "123456" - } - } signingConfigs { release { storeFile file('ruilaizi.jks') @@ -39,7 +31,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } release { - // 生产环境 —— 连接公网服务器 + // 生产环境 —— 通过域名访问 buildConfigField "String", "API_BASE_URL", '"http://101.43.95.130:8039"' minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' diff --git a/peizhen/app/src/main/AndroidManifest.xml b/peizhen/app/src/main/AndroidManifest.xml index 9cc2b80..8b5ceb4 100644 --- a/peizhen/app/src/main/AndroidManifest.xml +++ b/peizhen/app/src/main/AndroidManifest.xml @@ -35,6 +35,7 @@ + + + + diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/BaseApplication.java b/peizhen/app/src/main/java/com/ruilaizi/service/BaseApplication.java index 4186f24..d41fb1d 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/BaseApplication.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/BaseApplication.java @@ -116,14 +116,14 @@ public class BaseApplication extends Application { //https相关设置,以下几种方案根据需要自己设置 //方法一:信任所有证书,不安全有风险 - HttpsUtils.SSLParams sslParams1 = HttpsUtils.getSslSocketFactory(); + //HttpsUtils.SSLParams sslParams1 = HttpsUtils.getSslSocketFactory(); //方法二:自定义信任规则,校验服务端证书 HttpsUtils.SSLParams sslParams2 = HttpsUtils.getSslSocketFactory(new SafeTrustManager()); //方法三:使用预埋证书,校验服务端证书(自签名证书) //HttpsUtils.SSLParams sslParams3 = HttpsUtils.getSslSocketFactory(getAssets().open("srca.cer")); //方法四:使用bks证书和密码管理客户端证书(双向认证),使用预埋证书,校验服务端证书(自签名证书) //HttpsUtils.SSLParams sslParams4 = HttpsUtils.getSslSocketFactory(getAssets().open("xxx.bks"), "123456", getAssets().open("yyy.cer")); - builder.sslSocketFactory(sslParams1.sSLSocketFactory, sslParams1.trustManager); + builder.sslSocketFactory(sslParams2.sSLSocketFactory, sslParams2.trustManager); //配置https的域名匹配规则,详细看demo的初始化介绍,不需要就不要加入,使用不当会导致https握手失败 builder.hostnameVerifier(new SafeHostnameVerifier()); diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/main/activity/SplashActivity.java b/peizhen/app/src/main/java/com/ruilaizi/service/main/activity/SplashActivity.java index 07a8584..2cfcbea 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/main/activity/SplashActivity.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/main/activity/SplashActivity.java @@ -9,6 +9,7 @@ import android.view.WindowManager; import com.ruilaizi.service.base.BaseActivity; import com.ruilaizi.service.utils.ToastUtils; +import com.ruilaizi.service.utils.checkVersionsUtils.ProfileSpUtils; import java.lang.ref.WeakReference; import java.util.List; @@ -69,13 +70,11 @@ public class SplashActivity extends BaseActivity implements EasyPermissions.Perm private void startActivity() { - startActivity(MainActivity.class); - //此类逻辑有是否登录,没有登录则跳验证码登录 -// if (ProfileSpUtils.getInstance().isLogin()) { -// startActivity(MainActivity.class); -// } else { -// startActivity(LoginPasswordActivity.class); -// } + if (ProfileSpUtils.getInstance().isLogin()) { + startActivity(MainActivity.class); + } else { + startActivity(LoginPasswordActivity.class); + } finish(); } @@ -88,9 +87,13 @@ public class SplashActivity extends BaseActivity implements EasyPermissions.Perm @AfterPermissionGranted(RC_CAMERA_AND_LOCATION) private void methodRequiresTwoPermission() { - String[] perms = {Manifest.permission.WRITE_EXTERNAL_STORAGE - , Manifest.permission.CAMERA - }; + String[] perms; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + // Android 13+: WRITE_EXTERNAL_STORAGE 已废弃 + perms = new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_MEDIA_IMAGES}; + } else { + perms = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}; + } if (EasyPermissions.hasPermissions(this, perms)) { handler.sendEmptyMessageDelayed(0, 2000); diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/main/activity/mvp/presenterImpl/LoginPasswordPresenterImpl.java b/peizhen/app/src/main/java/com/ruilaizi/service/main/activity/mvp/presenterImpl/LoginPasswordPresenterImpl.java index eede5ee..126d368 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/main/activity/mvp/presenterImpl/LoginPasswordPresenterImpl.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/main/activity/mvp/presenterImpl/LoginPasswordPresenterImpl.java @@ -3,6 +3,7 @@ package com.ruilaizi.service.main.activity.mvp.presenterImpl; import android.text.TextUtils; import android.util.Log; +import com.lzy.okgo.OkGo; import com.lzy.okgo.model.HttpParams; import com.lzy.okgo.model.Response; import com.ruilaizi.service.main.activity.MainActivity; @@ -77,9 +78,14 @@ public class LoginPasswordPresenterImpl extends MvpBasePresenter() { @Override public void onNext(Response response) { diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/Bean/kehuListBean.java b/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/Bean/kehuListBean.java index a1cfad6..a05094f 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/Bean/kehuListBean.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/Bean/kehuListBean.java @@ -13,6 +13,7 @@ public class kehuListBean extends BaseModel { private int code; private String msg; private List data; + private List rows; public int getTotal() { return total; @@ -39,13 +40,21 @@ public class kehuListBean extends BaseModel { } public List getData() { - return data; + return rows != null ? rows : data; } public void setData(List data) { this.data = data; } + public List getRows() { + return rows; + } + + public void setRows(List rows) { + this.rows = rows; + } + // public static class DataBean extends BaseObservable implements Serializable { // private Object searchValue; // private Object createBy; diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/CanFragment.java b/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/CanFragment.java index 58aef34..deef394 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/CanFragment.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/CanFragment.java @@ -231,7 +231,7 @@ public class CanFragment extends MobanFragment { public void onNext(Response response) { String body = (String) response.body(); kehuListBean data = JsonUtils.fromJson(body, kehuListBean.class); - if (data.getCode() == 200) { + if (data.getCode() == 200 || data.getCode() == 0) { List data1 = data.getData(); mSwl.setRefreshing(false); diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/ComFragment.java b/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/ComFragment.java index 8ffe8fb..e004915 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/ComFragment.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/ComFragment.java @@ -232,7 +232,7 @@ public class ComFragment extends MobanFragment { public void onNext(Response response) { String body = (String) response.body(); kehuListBean data = JsonUtils.fromJson(body, kehuListBean.class); - if (data.getCode() == 200) { + if (data.getCode() == 200 || data.getCode() == 0) { List data1 = data.getData(); mSwl.setRefreshing(false); diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/CustomerFragment.java b/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/CustomerFragment.java index 4b0c51b..69f6ebe 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/CustomerFragment.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/CustomerFragment.java @@ -231,7 +231,7 @@ public class CustomerFragment extends MobanFragment { public void onNext(Response response) { String body = (String) response.body(); kehuListBean data = JsonUtils.fromJson(body, kehuListBean.class); - if (data.getCode() == 200) { + if (data.getCode() == 200 || data.getCode() == 0) { List data1 = data.getData(); mSwl.setRefreshing(false); diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/SerFragment.java b/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/SerFragment.java index 930a13f..331125f 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/SerFragment.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/SerFragment.java @@ -232,7 +232,7 @@ public class SerFragment extends MobanFragment { public void onNext(Response response) { String body = (String) response.body(); kehuListBean data = JsonUtils.fromJson(body, kehuListBean.class); - if (data.getCode() == 200) { + if (data.getCode() == 200 || data.getCode() == 0) { List data1 = data.getData(); mSwl.setRefreshing(false); diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/TobeSerFragment.java b/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/TobeSerFragment.java index 70e69a3..0d2b71c 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/TobeSerFragment.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/main/kehu/fragment/TobeSerFragment.java @@ -244,7 +244,7 @@ public class TobeSerFragment extends MobanFragment { public void onNext(Response response) { String body = (String) response.body(); kehuListBean data = JsonUtils.fromJson(body, kehuListBean.class); - if (data.getCode() == 200) { + if (data.getCode() == 200 || data.getCode() == 0) { List data1 = data.getData(); mSwl.setRefreshing(false); diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/main/my/activity/ShimingActivity.java b/peizhen/app/src/main/java/com/ruilaizi/service/main/my/activity/ShimingActivity.java index 5d96379..c2eccfa 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/main/my/activity/ShimingActivity.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/main/my/activity/ShimingActivity.java @@ -37,6 +37,10 @@ import com.zhihu.matisse.MimeType; import com.zhihu.matisse.engine.impl.PicassoEngine; import com.zhihu.matisse.internal.entity.CaptureStrategy; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import java.io.ByteArrayOutputStream; import java.io.File; import java.util.List; import java.util.concurrent.TimeUnit; @@ -123,7 +127,7 @@ public class ShimingActivity extends BaseActivity { toast("请输入姓名"); return; } - if("".equals(recFollowBean.getRealName())){ + if("".equals(recFollowBean.getIdentityNo())){ toast("请输入身份证号"); return; } @@ -197,10 +201,43 @@ public class ShimingActivity extends BaseActivity { } + /** + * 将图片文件压缩后转为 base64,限制最大宽度并控制 JPEG 质量 + */ + private String compressAndToBase64(File file, int maxWidth) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(), options); + int sampleSize = 1; + int width = options.outWidth; + if (width > 0) { + while (width / sampleSize > maxWidth) { + sampleSize *= 2; + } + } + options.inJustDecodeBounds = false; + options.inSampleSize = sampleSize; + Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options); + if (bitmap == null) { + return ""; + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.JPEG, 50, baos); + bitmap.recycle(); + return "data:image/jpg;base64," + android.util.Base64.encodeToString(baos.toByteArray(), android.util.Base64.NO_WRAP); + } + private void getPermissions() { RxPermissions rxPermissions = new RxPermissions(this); - rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA) + String[] permissions; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + // Android 13+: WRITE_EXTERNAL_STORAGE 已废弃,需用 READ_MEDIA_IMAGES + permissions = new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_MEDIA_IMAGES}; + } else { + permissions = new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE}; + } + rxPermissions.request(permissions) .subscribe(new io.reactivex.Observer() { @Override public void onSubscribe(Disposable d) { @@ -287,10 +324,9 @@ public class ShimingActivity extends BaseActivity { @Override public void onSuccess(File file) { - //转换成功后的文件 - String resultt = AbImageUtil.bitmapToBase64(ImageUtils.getBitmap(file)); - uploadPic(resultt,0); - + showProgressDialog("上传中...", ShimingActivity.this); + String resultt = compressAndToBase64(file, 600); + uploadPic(resultt, 0); } @Override @@ -327,10 +363,9 @@ public class ShimingActivity extends BaseActivity { @Override public void onSuccess(File file) { - //转换成功后的文件 - String resultt = AbImageUtil.bitmapToBase64(ImageUtils.getBitmap(file)); - - uploadPic(resultt,1); + showProgressDialog("上传中...", ShimingActivity.this); + String resultt = compressAndToBase64(file, 1200); + uploadPic(resultt, 1); } @Override @@ -367,9 +402,9 @@ public class ShimingActivity extends BaseActivity { @Override public void onSuccess(File file) { - //转换成功后的文件 - String resultt = AbImageUtil.bitmapToBase64(ImageUtils.getBitmap(file)); - uploadPic(resultt,2); + showProgressDialog("上传中...", ShimingActivity.this); + String resultt = compressAndToBase64(file, 1200); + uploadPic(resultt, 2); } @Override diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/main/my/entity/loginBean.java b/peizhen/app/src/main/java/com/ruilaizi/service/main/my/entity/loginBean.java index 394cc4a..f1da660 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/main/my/entity/loginBean.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/main/my/entity/loginBean.java @@ -105,6 +105,8 @@ public class loginBean implements Serializable { private String leijijine; private String yue; private String token; + private String monthIncome; + private String monthOrderCount; public String getUserId() { return userId; @@ -169,5 +171,21 @@ public class loginBean implements Serializable { public void setToken(String token) { this.token = token; } + + public String getMonthIncome() { + return monthIncome; + } + + public void setMonthIncome(String monthIncome) { + this.monthIncome = monthIncome; + } + + public String getMonthOrderCount() { + return monthOrderCount; + } + + public void setMonthOrderCount(String monthOrderCount) { + this.monthOrderCount = monthOrderCount; + } } } diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/main/task/ViewHolderOne.java b/peizhen/app/src/main/java/com/ruilaizi/service/main/task/ViewHolderOne.java index de3665d..5f144f9 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/main/task/ViewHolderOne.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/main/task/ViewHolderOne.java @@ -27,8 +27,8 @@ public class ViewHolderOne extends AbstractViewTypeHolder { @Override public void bindHolder(robCustomerInfoBean dataBean, int position, Context mContext, MarKQuestionItemviewOnClickListener mviewListener) { super.bindHolder(dataBean, position, mContext, mviewListener); - WidgetTools.setTextsev(my_shouru, "", ProfileSpUtils.getInstance().getUserProfie().getData().getYue()); - WidgetTools.setTextsev(my_dingdan_num, "", ProfileSpUtils.getInstance().getUserProfie().getData().getYue()); + WidgetTools.setTextsev(my_shouru, "", ProfileSpUtils.getInstance().getUserProfie().getData().getMonthIncome()); + WidgetTools.setTextsev(my_dingdan_num, "", ProfileSpUtils.getInstance().getUserProfie().getData().getMonthOrderCount()); } } diff --git a/peizhen/app/src/main/java/com/ruilaizi/service/wxapi/WXPayEntryActivity.java b/peizhen/app/src/main/java/com/ruilaizi/service/wxapi/WXPayEntryActivity.java index 3df3e41..bcbd98b 100644 --- a/peizhen/app/src/main/java/com/ruilaizi/service/wxapi/WXPayEntryActivity.java +++ b/peizhen/app/src/main/java/com/ruilaizi/service/wxapi/WXPayEntryActivity.java @@ -10,6 +10,7 @@ import com.tencent.mm.opensdk.modelbase.BaseReq; import com.tencent.mm.opensdk.modelbase.BaseResp; import com.tencent.mm.opensdk.openapi.IWXAPI; import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler; +import com.tencent.mm.opensdk.openapi.WXAPIFactory; import androidx.annotation.Nullable; @@ -57,12 +58,11 @@ public class WXPayEntryActivity extends BaseActivity implements IWXAPIEventHandl protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); initData(); - } private void initData() { - // api = WXAPIFactory.createWXAPI(this, PayUtils.APP_ID); + api = WXAPIFactory.createWXAPI(this, WXEntryActivity.WEIXIN_APP_ID, true); api.handleIntent(getIntent(), this); } -} \ No newline at end of file +} diff --git a/rlz-ui-dist-new.tar.gz b/rlz-ui-dist-new.tar.gz new file mode 100644 index 0000000..af3954d Binary files /dev/null and b/rlz-ui-dist-new.tar.gz differ diff --git a/rlz-ui/src/api/finance/settlement.js b/rlz-ui/src/api/finance/settlement.js new file mode 100644 index 0000000..25aef83 --- /dev/null +++ b/rlz-ui/src/api/finance/settlement.js @@ -0,0 +1,70 @@ +import request from '@/utils/request' + +// 查询待结算订单列表 +export function listPendingSettlement(query) { + return request({ + url: '/system/settlement/pending', + method: 'get', + params: query + }) +} + +// 单个结算 +export function settleOrder(orderId) { + return request({ + url: '/system/settlement/settle/' + orderId, + method: 'post' + }) +} + +// 批量结算 +export function batchSettleOrder(orderIds) { + return request({ + url: '/system/settlement/batchSettle', + method: 'post', + data: orderIds + }) +} + +// 查询平台配置列表 +export function listPlatformConfig(query) { + return request({ + url: '/system/platformConfig/list', + method: 'get', + params: query + }) +} + +// 获取平台配置详情 +export function getPlatformConfig(id) { + return request({ + url: '/system/platformConfig/' + id, + method: 'get' + }) +} + +// 新增平台配置 +export function addPlatformConfig(data) { + return request({ + url: '/system/platformConfig', + method: 'post', + data: data + }) +} + +// 修改平台配置 +export function updatePlatformConfig(data) { + return request({ + url: '/system/platformConfig', + method: 'put', + data: data + }) +} + +// 删除平台配置 +export function delPlatformConfig(id) { + return request({ + url: '/system/platformConfig/' + id, + method: 'delete' + }) +} diff --git a/rlz-ui/src/views/finance/platformConfig/index.vue b/rlz-ui/src/views/finance/platformConfig/index.vue new file mode 100644 index 0000000..2ac0d40 --- /dev/null +++ b/rlz-ui/src/views/finance/platformConfig/index.vue @@ -0,0 +1,146 @@ + + + diff --git a/rlz-ui/src/views/finance/settlement/index.vue b/rlz-ui/src/views/finance/settlement/index.vue index 2c9ac7e..369c87e 100644 --- a/rlz-ui/src/views/finance/settlement/index.vue +++ b/rlz-ui/src/views/finance/settlement/index.vue @@ -1,18 +1,8 @@