Browse Source

修改 bug

master
王泽彦 5 months ago
parent
commit
2324c32458
  1. 43
      niucloud/app/api/controller/apiController/StudentManager.php
  2. 1
      niucloud/app/api/route/route.php
  3. 3
      niucloud/app/service/api/apiService/StudentService.php
  4. 1
      niucloud/app/service/api/student/StudentService.php
  5. 1464
      uniapp/api/apiRoute.js
  6. 2
      uniapp/components/client-info-card/client-info-card.vue
  7. 733
      uniapp/components/order-list-card/index.vue
  8. 207
      uniapp/components/order-list-card/qrcode-payment-dialog.vue
  9. 254
      uniapp/components/schedule/ScheduleDetail.vue
  10. 47
      uniapp/components/student-edit-popup/student-edit-popup.less
  11. 66
      uniapp/components/student-edit-popup/student-edit-popup.vue
  12. 2
      uniapp/components/student-info-card/student-info-card.vue
  13. 48
      uniapp/pages-coach/coach/schedule/schedule_table.vue
  14. 1
      uniapp/pages-market/clue/class_arrangement_detail.vue
  15. 718
      uniapp/pages-market/clue/clue_info.vue
  16. 23
      uniapp/pages-market/clue/edit_clues.vue

43
niucloud/app/api/controller/apiController/StudentManager.php

@ -147,4 +147,47 @@ class StudentManager extends BaseApiService
return fail('添加学员失败:' . $e->getMessage());
}
}
/**
* 获取学员基本信息(员工端)
* @param int $id 学员ID
* @return \think\Response
*/
public function info($id)
{
// 验证参数
if (empty($id) || !is_numeric($id)) {
return fail('学员ID无效');
}
try {
// 直接查询学员基本信息,不需要权限验证
$student = \think\facade\Db::table('school_student')
->where('id', $id)
->where('deleted_at', 0)
->find();
if (!$student) {
return fail('学员信息不存在');
}
// 返回学员基本信息
$studentInfo = [
'id' => $student['id'],
'name' => $student['name'],
'gender' => $student['gender'],
'gender_text' => $student['gender'] == 1 ? '男' : '女',
'birthday' => $student['birthday'],
'campus_id' => $student['campus_id'] ?? null,
'headimg' => $student['headimg'] ? get_image_url($student['headimg']) : '',
'emergency_contact' => $student['emergency_contact'],
'contact_phone' => $student['contact_phone']
];
return success($studentInfo, '获取学员信息成功');
} catch (\Exception $e) {
return fail('获取学员信息失败:' . $e->getMessage());
}
}
}

1
niucloud/app/api/route/route.php

@ -277,6 +277,7 @@ Route::group(function () {
Route::post('student/edit', 'apiController.StudentManager/edit');
//销售端-学员-列表
Route::get('student/list', 'apiController.StudentManager/list');
Route::get('student/info/:id', 'apiController.StudentManager/info');
//教练端-学员-我的学员列表
Route::get('coach/students/my', 'apiController.CoachStudent/getMyStudents');

3
niucloud/app/service/api/apiService/StudentService.php

@ -146,8 +146,7 @@ class StudentService extends BaseApiService
// 查询该学员的课程安排记录,按日期排序获取一访和二访信息
$visitRecords = Db::table('school_person_course_schedule')
->where([
['person_id', '=', $student['id']],
['person_type', '=', 'student']
['student_id', '=', $student['id']]
])
->order('course_date', 'asc')
->select()

1
niucloud/app/service/api/student/StudentService.php

@ -184,6 +184,7 @@ class StudentService extends BaseService
'contact_phone' => $student['contact_phone'],
'note' => $student['note'],
'headimg' => $student['headimg'] ? get_image_url($student['headimg']) : '',
'campus_id' => $student['campus_id'] ?? null, // 添加校区ID
];
// 处理体测信息

1464
uniapp/api/apiRoute.js

File diff suppressed because it is too large

2
uniapp/components/client-info-card/client-info-card.vue

@ -35,7 +35,7 @@
</view>
<view class="detail-row">
<text class="info-label">分配顾问</text>
<text class="info-value">{{ $util.safeGet(clientInfo, 'customerResource.consultant_name', '未知顾问') }}</text>
<text class="info-value">{{ $util.safeGet(clientInfo, 'customerResource.consultant_name', '---') }}</text>
</view>
<view class="detail-row">
<text class="info-label">性别</text>

733
uniapp/components/order-list-card/index.vue

@ -1,9 +1,17 @@
<!--订单列表内容组件-->
<!--订单列表组件 - 独立业务组件-->
<template>
<view class="order-list-card">
<!-- 加载状态 -->
<view v-if="loading" class="loading-state">
<text class="loading-icon"></text>
<text class="loading-text">加载中...</text>
</view>
<!-- 订单内容 -->
<view v-else>
<!-- 操作按钮区域 -->
<view class="action-header" v-if="orderList && orderList.length > 0">
<view class="add-order-btn" @click.stop="handleAddOrder">
<view class="action-header" v-if="showAddButton && orderList.length > 0">
<view class="add-order-btn" @click.stop="openAddOrder">
<text class="add-icon">+</text>
<text class="add-text">新增订单</text>
</view>
@ -12,28 +20,27 @@
<!-- 订单列表 -->
<scroll-view
class="order-list"
v-if="orderList && orderList.length > 0"
v-if="orderList.length > 0"
scroll-y="true"
:enhanced="true"
style="height: 70vh;"
:style="{height: maxHeight}"
>
<view
class="order-item"
v-for="(order, index) in orderList"
:key="order && order.id ? order.id : `order-${index}`"
:class="{ 'pending-payment': order && order.status === 'pending' }"
@click.stop="order ? handleOrderClick(order) : null"
v-if="order"
v-for="order in orderList"
:key="order.id"
:class="{ 'pending-payment': order.status === 'pending' }"
@click.stop="handleOrderClick(order)"
>
<view class="order-header">
<view class="order-info">
<view class="order-no">订单号{{ order.order_no || 'N/A' }}</view>
<view class="order-no">订单号{{ order.order_no }}</view>
<view class="order-time" v-if="order.create_time">
{{ formatTime(order.create_time) }}
</view>
</view>
<view :class="['order-status',getStatusClass(order && order.status)]">
{{ getStatusText(order && order.status) }}
<view :class="['order-status', getStatusClass(order.status)]">
{{ getStatusText(order.status) }}
</view>
</view>
@ -48,17 +55,17 @@
<view class="order-details">
<!-- 价格信息 -->
<view class="detail-row" v-if="order.total_amount">
<view class="detail-row">
<text class="detail-label">订单金额</text>
<text class="detail-value price">¥{{ order.total_amount }}</text>
</view>
<view class="detail-row" v-if="order.paid_amount !== undefined">
<view class="detail-row">
<text class="detail-label">已付金额</text>
<text class="detail-value paid">¥{{ order.paid_amount }}</text>
</view>
<view class="detail-row" v-if="order.unpaid_amount !== undefined">
<view class="detail-row">
<text class="detail-label">未付金额</text>
<text class="detail-value unpaid">¥{{ order.unpaid_amount }}</text>
</view>
@ -81,7 +88,7 @@
</view>
<!-- 备注信息 -->
<view class="order-remark">
<view class="order-remark" v-if="order.remark">
<view class="remark-label">备注</view>
<view class="remark-content">{{ order.remark }}</view>
</view>
@ -94,57 +101,660 @@
<view class="empty-icon">📋</view>
<view class="empty-text">暂无订单记录</view>
<view class="empty-tip">客户还未产生任何订单</view>
<view class="empty-add-btn" @click.stop="handleAddOrder">
<view class="empty-add-btn" v-if="showAddButton" @click.stop="openAddOrder">
<text>新增订单</text>
</view>
</view>
</view>
<!-- 新增订单弹窗 -->
<uni-popup ref="orderFormPopup" type="bottom">
<OrderFormPopup
:visible="showOrderForm"
:student-info="studentInfo"
:resource-id="resourceId"
@cancel="closeOrderForm"
@confirm="handleOrderCreated"
/>
</uni-popup>
<!-- 二维码支付弹窗 -->
<uni-popup ref="qrCodePopup" type="center" @close="closeQRCodeModal">
<QRCodePaymentDialog
v-if="qrCodePaymentData"
:payment-data="qrCodePaymentData"
@close="closeQRCodeModal"
@confirm="confirmQRCodePayment"
/>
</uni-popup>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js'
import dictUtilSimple from '@/common/dictUtilSimple.js'
import OrderFormPopup from '@/components/order-form-popup/index.vue'
import QRCodePaymentDialog from './qrcode-payment-dialog.vue'
export default {
name: 'OrderListCard',
components: {
OrderFormPopup,
QRCodePaymentDialog
},
props: {
//
orderList: {
type: Array,
default: () => []
// ID
studentId: {
type: [Number, String],
default: null
},
// ID
resourceId: {
type: [Number, String],
default: null
},
//
autoLoad: {
type: Boolean,
default: true
},
//
showAddButton: {
type: Boolean,
default: true
},
//
maxHeight: {
type: String,
default: '70vh'
}
},
data() {
return {
//
orderList: [],
loading: false,
//
showOrderForm: false,
showQRCodeModal: false,
//
currentOrder: null,
//
qrCodePaymentData: null,
//
paymentTypeDict: [],
// ()
studentInfo: {}
}
},
mounted() {
//
this.loadDictData()
//
if (this.studentId) {
this.buildStudentInfo()
}
//
if (this.autoLoad && (this.studentId || this.resourceId)) {
this.loadOrderList()
}
},
watch: {
// ID,
studentId(newVal) {
if (newVal && this.autoLoad) {
this.loadOrderList()
this.buildStudentInfo()
}
}
},
methods: {
//
viewOrderDetail(order) {
this.$emit('view-detail', order)
/**
* 构建学生信息对象
* 通过员工端API获取学生详情包含 campus_id
*/
async buildStudentInfo() {
if (!this.studentId) {
this.studentInfo = {}
return
}
try {
// 使
const res = await apiRoute.getStudentBasicInfo({ student_id: this.studentId })
if (res.code === 1 && res.data) {
this.studentInfo = {
id: res.data.id,
name: res.data.name,
campus_id: res.data.campus_id,
gender: res.data.gender,
gender_text: res.data.gender_text,
birthday: res.data.birthday,
headimg: res.data.headimg,
emergency_contact: res.data.emergency_contact,
contact_phone: res.data.contact_phone
}
console.log('学生信息加载成功:', this.studentInfo)
} else {
console.error('获取学生信息失败:', res.msg)
// : 使
this.studentInfo = {
id: this.studentId,
name: '学员',
campus_id: null
}
}
} catch (error) {
console.error('获取学生信息异常:', error)
//
this.studentInfo = {
id: this.studentId,
name: '学员',
campus_id: null
}
}
},
//
handleAddOrder() {
this.$emit('add-order')
/**
* 加载字典数据
*/
async loadDictData() {
try {
const dictResult = await dictUtilSimple.getBatchDict(['payment_type'])
if (dictResult.payment_type && Array.isArray(dictResult.payment_type)) {
this.paymentTypeDict = dictResult.payment_type
} else {
// 使
this.paymentTypeDict = [
{ name: '现金支付', value: 'cash' },
{ name: '扫码支付', value: 'scan_code' },
{ name: '订阅支付', value: 'subscription' },
{ name: '微信在线代付', value: 'wxpay_online' },
{ name: '客户自行付款', value: 'client_wxpay' },
{ name: '定金', value: 'deposit' }
]
}
} catch (error) {
console.error('加载支付方式字典失败:', error)
// 使
this.paymentTypeDict = [
{ name: '现金支付', value: 'cash' },
{ name: '扫码支付', value: 'scan_code' },
{ name: '订阅支付', value: 'subscription' },
{ name: '微信在线代付', value: 'wxpay_online' },
{ name: '客户自行付款', value: 'client_wxpay' },
{ name: '定金', value: 'deposit' }
]
}
},
//
handleOrderClick(order) {
console.log('点击订单:', order)
// order
if (!order) {
console.warn('订单数据不存在,无法处理点击事件')
/**
* 加载订单列表
*/
async loadOrderList() {
if (!this.studentId && !this.resourceId) {
console.warn('OrderListCard: 缺少必要参数 studentId 或 resourceId')
return
}
this.loading = true
try {
const params = {}
if (this.studentId) {
params.student_id = this.studentId
} else if (this.resourceId) {
params.resource_id = this.resourceId
}
const res = await apiRoute.xs_orderTableList(params)
if (res.code === 1) {
this.orderList = this.processOrderData(res.data?.data || [])
this.$emit('loaded', this.orderList)
} else {
console.error('获取订单列表失败:', res.msg)
this.orderList = []
this.$emit('error', { type: 'load', message: res.msg })
}
} catch (error) {
console.error('获取订单列表异常:', error)
uni.showToast({ title: '加载失败', icon: 'none' })
this.$emit('error', { type: 'load', error })
} finally {
this.loading = false
}
},
/**
* 处理订单数据格式
*/
processOrderData(orders) {
if (!Array.isArray(orders)) return []
return orders.map(order => ({
id: order.id,
student_id: order.student_id,
order_no: order.payment_id || `ORD${order.id}`,
product_name: order.course_id_name || '课程',
total_amount: order.order_amount || '0.00',
paid_amount: order.order_status === 'paid' ? order.order_amount : '0.00',
unpaid_amount: order.order_status === 'paid' ? '0.00' : order.order_amount,
payment_method: this.formatPaymentType(order.payment_type),
salesperson_name: order.staff_id_name || '未指定',
course_count: order.total_hours || 0,
status: this.mapOrderStatus(order.order_status),
create_time: order.created_at,
contract_id: order.contract_id,
contract_sign_id: order.contract_sign_id,
remark: order.remark || '',
_raw: order
}))
},
/**
* 格式化支付类型
*/
formatPaymentType(paymentType) {
if (!paymentType) return '未知'
const paymentItem = this.paymentTypeDict.find(item => item.value === paymentType)
if (paymentItem) return paymentItem.name
const fallbackMap = {
'cash': '现金支付',
'scan_code': '扫码支付',
'subscription': '订阅支付',
'wxpay_online': '微信在线代付',
'client_wxpay': '客户自行付款',
'deposit': '定金'
}
return fallbackMap[paymentType] || paymentType || '未知'
},
/**
* 映射订单状态
*/
mapOrderStatus(orderStatus) {
const statusMap = {
'pending': 'pending',
'paid': 'paid',
'signed': 'completed',
'completed': 'completed',
'transfer': 'partial'
}
return statusMap[orderStatus] || 'pending'
},
/**
* 订单点击处理
*/
handleOrderClick(order) {
this.currentOrder = order
if (order.status === 'pending') {
//
this.$emit('pay-order', order)
// ,
this.handleOrderPay(order)
} else {
//
this.$emit('view-detail', order)
// ,
this.viewOrderDetail(order)
}
},
//
getStatusClass(status) {
if (!status) return 'status-default'
/**
* 处理订单支付
*/
handleOrderPay(order) {
const paymentType = order._raw?.payment_type
switch(paymentType) {
case 'cash':
this.processCashPayment(order)
break
case 'scan_code':
this.processQRCodePayment(order)
break
case 'subscription':
this.processSubscriptionPayment(order)
break
case 'wxpay_online':
this.processWechatPayment(order)
break
default:
uni.showToast({ title: '不支持的支付方式', icon: 'none' })
}
},
/**
* 现金支付处理
*/
async processCashPayment(order) {
uni.showModal({
title: '现金支付确认',
content: `确认已收到现金支付 ¥${order.total_amount}`,
success: async (res) => {
if (res.confirm) {
await this.updateOrderPaymentStatus(order, 'paid', `CASH${Date.now()}`)
}
}
})
},
/**
* 二维码支付处理
*/
async processQRCodePayment(order) {
uni.showLoading({ title: '生成支付二维码...' })
try {
const res = await apiRoute.getOrderPayQrcode({
order_id: order._raw?.id || order.id
})
uni.hideLoading()
if (res.code === 1 && res.data) {
this.qrCodePaymentData = {
order: order,
qrcode: res.data.code_url,
qrcodeImage: res.data.qrcode_base64 || res.data.qrcode_url
}
this.showQRCodeModal = true
this.$refs.qrCodePopup.open()
} else {
uni.showToast({ title: res.msg || '获取支付二维码失败', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('获取支付二维码失败:', error)
uni.showToast({ title: '获取支付二维码失败', icon: 'none' })
}
},
/**
* 订阅支付处理
*/
processSubscriptionPayment(order) {
uni.showActionSheet({
itemList: ['确认分期支付方案', '取消支付'],
success: async (res) => {
if (res.tapIndex === 0) {
uni.showModal({
title: '分期支付确认',
content: `确认使用分期支付方式支付 ¥${order.total_amount}`,
success: async (modalRes) => {
if (modalRes.confirm) {
await this.updateOrderPaymentStatus(order, 'partial', `SUB${Date.now()}`)
}
}
})
}
}
})
},
/**
* 微信支付处理
*/
processWechatPayment(order) {
uni.showModal({
title: '微信支付',
content: `将调用微信支付 ¥${order.total_amount}`,
confirmText: '确认支付',
cancelText: '取消',
success: async (res) => {
if (res.confirm) {
uni.showLoading({ title: '正在调用微信支付...' })
setTimeout(async () => {
uni.hideLoading()
uni.showModal({
title: '支付结果',
content: '微信支付完成,请确认是否已收到款项?',
success: async (confirmRes) => {
if (confirmRes.confirm) {
await this.updateOrderPaymentStatus(order, 'paid', `WX${Date.now()}`)
}
}
})
}, 1500)
}
}
})
},
/**
* 更新订单支付状态
*/
async updateOrderPaymentStatus(order, status, paymentId = '') {
try {
const updateData = {
order_id: order._raw?.id || order.id,
order_status: status,
payment_id: paymentId
}
const result = await apiRoute.xs_orderTableUpdatePaymentStatus(updateData)
if (result.code === 1) {
const statusText = {
'paid': '支付成功',
'partial': '分期支付确认成功',
'cancelled': '支付已取消'
}
uni.showToast({
title: statusText[status] || '状态更新成功',
icon: 'success'
})
//
await this.refresh()
//
this.$emit('payment-success', order)
} else {
uni.showToast({ title: result.msg || '状态更新失败', icon: 'none' })
}
} catch (error) {
console.error('更新订单状态失败:', error)
uni.showToast({ title: '状态更新失败', icon: 'none' })
this.$emit('error', { type: 'payment', error })
}
},
/**
* 关闭二维码支付弹窗
*/
closeQRCodeModal() {
this.showQRCodeModal = false
this.qrCodePaymentData = null
this.$refs.qrCodePopup.close()
},
/**
* 确认二维码支付完成
*/
async confirmQRCodePayment() {
if (!this.qrCodePaymentData?.order) return
const order = this.qrCodePaymentData.order
uni.showModal({
title: '支付确认',
content: '请确认是否已完成扫码支付?',
success: async (res) => {
if (res.confirm) {
try {
await this.updateOrderPaymentStatus(order, 'paid', `QR${Date.now()}`)
this.closeQRCodeModal()
} catch (error) {
console.error('支付确认失败:', error)
}
}
}
})
},
/**
* 查看订单详情
*/
viewOrderDetail(order) {
const orderInfo = order._raw || order
const isOrderPaid = order.status === 'paid'
const detailText = `
订单号${order.order_no}
课程${order.product_name}
金额¥${order.total_amount}
已付¥${order.paid_amount}
未付¥${order.unpaid_amount}
支付方式${order.payment_method}
销售顾问${order.salesperson_name}
课时数${order.course_count}
状态${this.getStatusText(order.status)}
创建时间${this.formatTime(order.create_time)}
${orderInfo.paid_at ? '支付时间:' + this.formatTime(orderInfo.paid_at) : ''}
`.trim()
uni.showModal({
title: '订单详情',
content: detailText,
showCancel: isOrderPaid,
cancelText: isOrderPaid ? '知道了' : '',
confirmText: isOrderPaid ? '合同签署' : '知道了',
success: (res) => {
if (res.confirm && isOrderPaid) {
this.goToContractSign(order)
}
}
})
},
/**
* 跳转到合同签署页面
*/
goToContractSign(order) {
const studentId = order.student_id || this.studentId
const contractId = order.contract_id || order._raw?.contract_id
const contractSignId = order.contract_sign_id || order._raw?.contract_sign_id
if (!studentId) {
uni.showToast({ title: '缺少学生信息', icon: 'none' })
return
}
if (!contractId) {
this.getContractByOrder(order, studentId)
return
}
let url = `/pages-student/contracts/sign?contract_id=${contractId}&student_id=${studentId}&contract_name=${encodeURIComponent(order.product_name + '合同')}&user_role=staff`
if (contractSignId) {
url += `&contract_sign_id=${contractSignId}`
}
uni.navigateTo({ url })
},
/**
* 根据订单获取合同信息
*/
async getContractByOrder(order, studentId) {
try {
uni.showLoading({ title: '获取合同信息...' })
const res = await apiRoute.getContractByOrder({
order_id: order._raw?.id || order.id,
student_id: studentId
})
uni.hideLoading()
if (res.code === 1 && res.data) {
const contractInfo = res.data
uni.navigateTo({
url: `/pages-student/contracts/sign?contract_id=${contractInfo.contract_id}&student_id=${studentId}&contract_name=${encodeURIComponent(contractInfo.contract_name || order.product_name + '合同')}&user_role=staff`
})
} else {
uni.showToast({ title: res.msg || '未找到相关合同', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('获取合同信息失败:', error)
uni.showToast({ title: '获取合同信息失败', icon: 'none' })
}
},
/**
* 新增订单
*/
openAddOrder() {
if (!this.studentId) {
uni.showToast({ title: '缺少学员信息', icon: 'none' })
return
}
this.showOrderForm = true
this.$refs.orderFormPopup.open()
},
/**
* 关闭新增订单弹窗
*/
closeOrderForm() {
this.showOrderForm = false
this.$refs.orderFormPopup.close()
},
/**
* 订单创建成功回调
*/
async handleOrderCreated() {
this.closeOrderForm()
uni.showToast({ title: '订单创建成功', icon: 'success' })
//
await this.refresh()
//
this.$emit('order-created')
},
/**
* 刷新订单列表(对外暴露方法)
*/
async refresh() {
await this.loadOrderList()
this.$emit('refreshed')
},
/**
* 获取当前订单列表(对外暴露方法)
*/
getOrderList() {
return this.orderList
},
/**
* 获取状态样式类
*/
getStatusClass(status) {
const statusMap = {
'pending': 'status-pending',
'paid': 'status-paid',
@ -156,10 +766,10 @@ export default {
return statusMap[status] || 'status-default'
},
//
/**
* 获取状态文本
*/
getStatusText(status) {
if (!status) return '未知状态'
const statusMap = {
'pending': '待支付',
'paid': '已支付',
@ -171,7 +781,9 @@ export default {
return statusMap[status] || '未知状态'
},
//
/**
* 格式化时间
*/
formatTime(timeStr) {
if (!timeStr) return ''
try {
@ -190,6 +802,24 @@ export default {
padding: 0;
}
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
}
.loading-icon {
font-size: 60rpx;
margin-bottom: 20rpx;
}
.loading-text {
font-size: 28rpx;
color: #999999;
}
.order-list {
display: flex;
flex-direction: column;
@ -211,19 +841,6 @@ export default {
&.pending-payment {
border-color: #FFC107;
background: rgba(255, 193, 7, 0.05);
&::after {
position: absolute;
bottom: 16rpx;
right: 16rpx;
background: #FFC107;
color: #000000;
font-size: 24rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-weight: 500;
z-index: 10;
}
}
}

207
uniapp/components/order-list-card/qrcode-payment-dialog.vue

@ -0,0 +1,207 @@
<!--二维码支付弹窗组件-->
<template>
<view class="qrcode-payment-modal">
<!-- 弹窗头部 -->
<view class="modal-header">
<text class="modal-title">扫码支付</text>
<view class="close-btn" @click="handleClose">
<text></text>
</view>
</view>
<!-- 订单信息 -->
<view class="order-info">
<view class="info-row">
<text class="label">订单号</text>
<text class="value">{{ paymentData.order.order_no }}</text>
</view>
<view class="info-row">
<text class="label">支付金额</text>
<text class="amount">¥{{ paymentData.order.total_amount }}</text>
</view>
</view>
<!-- 二维码区域 -->
<view class="qrcode-container">
<image
v-if="paymentData.qrcodeImage"
:src="paymentData.qrcodeImage"
class="qrcode-image"
mode="aspectFit"
/>
<text v-else class="qrcode-placeholder">二维码加载中...</text>
<text class="qrcode-tip">请使用微信扫码完成支付</text>
</view>
<!-- 操作按钮 -->
<view class="modal-buttons">
<view class="btn secondary" @click="handleClose">取消支付</view>
<view class="btn primary" @click="handleConfirm">确认已支付</view>
</view>
</view>
</template>
<script>
export default {
name: 'QRCodePaymentDialog',
props: {
paymentData: {
type: Object,
required: true
}
},
methods: {
handleClose() {
this.$emit('close')
},
handleConfirm() {
this.$emit('confirm')
}
}
}
</script>
<style lang="scss" scoped>
.qrcode-payment-modal {
background: #2A2A2A;
border-radius: 24rpx;
padding: 48rpx;
width: 600rpx;
max-width: 90vw;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40rpx;
}
.modal-title {
font-size: 36rpx;
font-weight: 600;
color: #ffffff;
}
.close-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
font-size: 32rpx;
color: #ffffff;
transition: all 0.3s ease;
&:active {
background: rgba(255, 255, 255, 0.2);
}
}
.order-info {
background: rgba(255, 255, 255, 0.05);
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 40rpx;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
}
.label {
font-size: 28rpx;
color: #999999;
}
.value {
font-size: 28rpx;
color: #ffffff;
}
.amount {
font-size: 40rpx;
font-weight: 600;
color: #FFC107;
}
.qrcode-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40rpx;
background: #ffffff;
border-radius: 16rpx;
margin-bottom: 40rpx;
}
.qrcode-image {
width: 400rpx;
height: 400rpx;
margin-bottom: 24rpx;
}
.qrcode-placeholder {
width: 400rpx;
height: 400rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #999999;
margin-bottom: 24rpx;
}
.qrcode-tip {
font-size: 26rpx;
color: #666666;
text-align: center;
}
.modal-buttons {
display: flex;
gap: 24rpx;
}
.btn {
flex: 1;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 44rpx;
font-size: 30rpx;
font-weight: 500;
transition: all 0.3s ease;
&.secondary {
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
&:active {
background: rgba(255, 255, 255, 0.15);
}
}
&.primary {
background: #29D3B4;
color: #ffffff;
&:active {
background: #1fb396;
}
}
}
</style>

254
uniapp/components/schedule/ScheduleDetail.vue

@ -1,14 +1,19 @@
<template>
<fui-modal :show="visible" width="700" @cancel="closePopup" :buttons="[{text: '关闭', type: 'default'}]" :showClose="true" @close="closePopup" @click="handleModalClick">
<!-- 自定义关闭按钮 -->
<template #header>
<!-- 组件根容器 - Vue2要求只有一个根元素 -->
<view>
<!-- 自定义弹窗遮罩 -->
<view class="modal-mask" v-if="visible" @click="closePopup">
<!-- 弹窗内容 -->
<view class="modal-container" @click.stop>
<!-- 自定义头部 -->
<view class="custom-header">
<text class="modal-title">课程安排详情</text>
<view class="close-btn" @click="closePopup">
<text class="close-icon"></text>
</view>
</view>
</template>
<!-- 弹窗主体内容 -->
<view class="schedule-detail" v-if="scheduleInfo">
<!-- 课程基本信息 -->
<view class="section basic-info">
@ -188,11 +193,13 @@
</view>
<!-- 加载状态 -->
<view class="loading" v-if="loading && !scheduleInfo">
<fui-loading></fui-loading>
<text class="loading-text">加载中...</text>
</view>
<!-- 错误信息 -->
<view class="error-message" v-if="error && !scheduleInfo">
<text>{{ errorMessage }}</text>
<view class="retry-btn" @click="fetchScheduleDetail">
@ -200,8 +207,17 @@
</view>
</view>
<!-- 底部关闭按钮 -->
<view class="modal-footer">
<view class="footer-btn close-footer-btn" @click="closePopup">
<text class="btn-text">关闭</text>
</view>
</view>
</view>
</view>
<!-- 学员点名底部弹窗 -->
<fui-modal :show="showAttendanceModal" title="学员点名" @cancel="closeAttendanceModal" :buttons="[]">
<fui-modal :show="showAttendanceModal" title="学员点名" @cancel="closeAttendanceModal" :buttons="[]" :zIndex="10001">
<view class="attendance-modal" v-if="selectedStudent">
<view class="student-info">
<view class="student-avatar-large">
@ -226,7 +242,7 @@
</fui-modal>
<!-- 升级确认弹窗 -->
<fui-modal :show="showUpgradeConfirm" title="升级确认" @cancel="cancelUpgrade" :buttons="[]" :zIndex="10000">
<fui-modal :show="showUpgradeConfirm" title="升级确认" @cancel="cancelUpgrade" :buttons="[]" :zIndex="10002">
<view class="upgrade-confirm-modal" v-if="upgradeStudent">
<view class="confirm-content">
<view class="upgrade-icon"></view>
@ -250,7 +266,7 @@
</fui-modal>
<!-- 删除课程安排确认弹窗 -->
<fui-modal :show="showDeleteSchedulesModal" title="删除确认" @cancel="closeDeleteSchedulesModal" :buttons="[]" :zIndex="10001">
<fui-modal :show="showDeleteSchedulesModal" title="删除确认" @cancel="closeDeleteSchedulesModal" :buttons="[]" :zIndex="10003">
<view class="delete-confirm-modal" v-if="studentToDelete">
<view class="confirm-content">
<view class="error-icon"></view>
@ -275,7 +291,7 @@
</view>
</view>
</fui-modal>
</fui-modal>
</view>
</template>
<script>
@ -296,12 +312,20 @@
computed: {
//
formalStudents() {
if (!this.scheduleInfo || !this.scheduleInfo.students) return [];
return this.scheduleInfo.students.filter(student => student.schedule_type === 1 || student.schedule_type === null);
if (!this.scheduleInfo) return [];
const students = this.scheduleInfo.formal_students;
if (!students || !Array.isArray(students)) {
return [];
}
return students;
},
waitingStudents() {
if (!this.scheduleInfo || !this.scheduleInfo.students) return [];
return this.scheduleInfo.students.filter(student => student.schedule_type === 2);
if (!this.scheduleInfo) return [];
const students = this.scheduleInfo.waiting_students;
if (!students || !Array.isArray(students)) {
return [];
}
return students;
},
statusClass() {
const statusMap = {
@ -369,24 +393,12 @@
if (newVal && this.scheduleId) {
this.fetchScheduleDetail();
}
},
scheduleId(newVal, oldVal) {
// scheduleId
if (newVal && this.visible && newVal !== oldVal) {
this.fetchScheduleDetail();
}
}
// : scheduleIdwatchvisiblewatch
// scheduleIdvisible=truevisible
},
methods: {
//
handleModalClick(e) {
//
if (e.index === 0) {
this.closePopup();
}
},
// 使API - admin
// (使API - admin)
async fetchScheduleDetail() {
if (!this.scheduleId) {
this.error = true;
@ -410,8 +422,33 @@
// 使APIadmin
if (data.schedule_info) {
// schedule_infoformal_studentswaiting_students
const allStudents = [...(data.formal_students || []), ...(data.waiting_students || [])];
//
const processStudents = (students) => {
return (students || []).map(student => ({
...student,
status_text: this.getStatusText(student.status || 0),
//
course_progress: student.course_progress || {
total: student.totalHours || 0,
used: student.usedHours || 0,
remaining: student.remainingHours || 0,
percentage: student.totalHours > 0 ? Math.round((student.usedHours / student.totalHours) * 100) : 0
},
//
needsRenewal: student.needsRenewal || false,
isTrialStudent: student.isTrialStudent || student.person_type !== 'student',
//
courseStatus: student.courseStatus || (student.person_type === 'student' ? '正式课' : '体验课'),
courseType: student.schedule_type === 2 ? 'waiting' : 'formal',
//
age: student.age || 0,
//
trialClassCount: student.trialClassCount || 0,
//
remainingHours: student.remainingHours || student.course_progress?.remaining || 0,
expiryDate: student.expiryDate || ''
}));
};
this.scheduleInfo = {
// schedule_info
@ -433,8 +470,20 @@
time_info: data.schedule_info.time_info || null,
//
course_duration: data.schedule_info.time_info?.duration || data.schedule_info.course_duration || 60,
//
students: allStudents.map(student => ({
//
formal_students: processStudents(data.formal_students),
waiting_students: processStudents(data.waiting_students),
//
available_capacity: data.schedule_info.available_capacity || 0,
enrolled_count: (data.formal_students?.length || 0) + (data.waiting_students?.length || 0),
remaining_capacity: data.schedule_info.remaining_capacity || 0
};
console.log('课程安排详情加载成功:', this.scheduleInfo);
} else {
//
const processStudents = (students) => {
return (students || []).map(student => ({
...student,
status_text: this.getStatusText(student.status || 0),
//
@ -446,7 +495,7 @@
},
//
needsRenewal: student.needsRenewal || false,
isTrialStudent: student.isTrialStudent || student.person_type !== 'student',
isTrialStudent: student.isTrialStudent || false,
//
courseStatus: student.courseStatus || (student.person_type === 'student' ? '正式课' : '体验课'),
courseType: student.schedule_type === 2 ? 'waiting' : 'formal',
@ -455,16 +504,16 @@
//
trialClassCount: student.trialClassCount || 0,
//
remainingHours: student.remainingHours || student.course_progress?.remaining || 0,
remainingHours: student.remainingHours || 0,
expiryDate: student.expiryDate || ''
})),
//
available_capacity: data.available_capacity || 0,
enrolled_count: allStudents.length,
remaining_capacity: data.remaining_capacity || 0
}));
};
} else {
//
// students
const allStudents = data.students || [];
const formalStudents = allStudents.filter(s => s.schedule_type === 1 || s.schedule_type === null);
const waitingStudents = allStudents.filter(s => s.schedule_type === 2);
this.scheduleInfo = {
//
id: data.id,
@ -485,34 +534,12 @@
time_info: data.time_info || null,
//
course_duration: data.time_info?.duration || data.course_duration || 60,
//
students: (data.students || []).map(student => ({
...student,
status_text: this.getStatusText(student.status || 0),
//
course_progress: student.course_progress || {
total: student.totalHours || 0,
used: student.usedHours || 0,
remaining: student.remainingHours || 0,
percentage: student.totalHours > 0 ? Math.round((student.usedHours / student.totalHours) * 100) : 0
},
//
needsRenewal: student.needsRenewal || false,
isTrialStudent: student.isTrialStudent || false,
//
courseStatus: student.courseStatus || (student.person_type === 'student' ? '正式课' : '体验课'),
courseType: student.schedule_type === 2 ? 'waiting' : 'formal',
//
age: student.age || 0,
//
trialClassCount: student.trialClassCount || 0,
//
remainingHours: student.remainingHours || 0,
expiryDate: student.expiryDate || ''
})),
//
formal_students: processStudents(formalStudents),
waiting_students: processStudents(waitingStudents),
//
available_capacity: data.available_capacity || 0,
enrolled_count: data.enrolled_count || 0,
enrolled_count: allStudents.length,
remaining_capacity: data.remaining_capacity || 0
};
}
@ -562,7 +589,11 @@
//
handleStudentClick(student, index) {
console.log('点击了学员:', student)
console.log('=== 学员点击事件触发 ===');
console.log('学员信息:', student);
console.log('学员索引:', index);
console.log('学员状态:', student.status);
// 0
if (student.status !== 0) {
uni.showToast({
@ -575,13 +606,18 @@
//
if (student.schedule_type === 2) {
console.log('等待位学员,弹出升级确认');
// -
this.handleWaitingStudentClick(student, index);
} else {
console.log('正式学员,弹出签到弹窗');
console.log('设置 selectedStudent:', student);
console.log('设置 selectedStudentIndex:', index);
// - /
this.selectedStudent = student;
this.selectedStudentIndex = index;
this.showAttendanceModal = true;
console.log('showAttendanceModal 已设置为:', this.showAttendanceModal);
}
},
@ -796,8 +832,14 @@
this.selectedStudent.statusClass = this.getStudentStatusClass(actionMap[action].status);
// scheduleInfo
if (this.scheduleInfo && this.scheduleInfo.students && this.selectedStudentIndex >= 0) {
this.$set(this.scheduleInfo.students, this.selectedStudentIndex, this.selectedStudent);
if (this.scheduleInfo) {
//
const isFormal = this.selectedStudent.schedule_type === 1 || this.selectedStudent.schedule_type === null;
const targetArray = isFormal ? this.scheduleInfo.formal_students : this.scheduleInfo.waiting_students;
if (targetArray && this.selectedStudentIndex >= 0) {
this.$set(targetArray, this.selectedStudentIndex, this.selectedStudent);
}
}
//
@ -949,6 +991,38 @@
</script>
<style lang="scss" scoped>
/* 自定义弹窗样式 */
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
padding: 20rpx;
}
.modal-container {
width: 100%;
max-width: 700rpx;
max-height: 90vh;
background-color: #1a1a1a;
border-radius: 16rpx;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.5);
}
/* 移除 fui-modal 的默认 padding */
::v-deep .fui-modal__body {
padding: 0 !important;
}
/* 自定义头部样式 */
.custom-header {
display: flex;
@ -957,6 +1031,7 @@
padding: 20rpx 30rpx;
background: #2a2a2a;
border-bottom: 1px solid #3a3a3a;
flex-shrink: 0;
}
.modal-title {
@ -994,7 +1069,7 @@
.schedule-detail {
padding: 20rpx;
max-height: 80vh;
flex: 1;
overflow-y: auto;
position: relative;
}
@ -1724,4 +1799,43 @@
box-shadow: 0 2rpx 8rpx rgba(239, 68, 68, 0.4);
}
}
/* 弹窗底部样式 */
.modal-footer {
padding: 20rpx 30rpx;
background: #2a2a2a;
border-top: 1px solid #3a3a3a;
flex-shrink: 0;
display: flex;
justify-content: center;
}
.footer-btn {
padding: 16rpx 60rpx;
border-radius: 8rpx;
cursor: pointer;
transition: all 0.3s ease;
.btn-text {
font-size: 28rpx;
font-weight: 600;
}
&:active {
transform: scale(0.98);
}
}
.close-footer-btn {
background: #4a4a4a;
border: 1px solid #666;
.btn-text {
color: #ccc;
}
&:hover {
background: #5a5a5a;
}
}
</style>

47
uniapp/components/student-edit-popup/student-edit-popup.less

@ -82,6 +82,53 @@
.form-input {
flex: 1;
// 头像上传样式
.avatar-upload {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
overflow: hidden;
border: 2rpx solid #e9ecef;
background: #f8f9fa;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
&:active {
background: #e9ecef;
transform: scale(0.95);
}
.avatar-preview {
width: 100%;
height: 100%;
border-radius: 50%;
}
.avatar-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
.avatar-icon {
font-size: 48rpx;
color: #29d3b4;
margin-bottom: 8rpx;
font-weight: 300;
}
.avatar-text {
font-size: 22rpx;
color: #999;
}
}
}
input {
width: 100%;
height: 80rpx;

66
uniapp/components/student-edit-popup/student-edit-popup.vue

@ -14,6 +14,19 @@
<view class="form-group">
<view class="form-group-title">基本信息</view>
<view class="form-item">
<view class="form-label">头像</view>
<view class="form-input">
<view class="avatar-upload" @click="chooseAvatar">
<image v-if="studentData.headimg" :src="studentData.headimg" class="avatar-preview" mode="aspectFill"></image>
<view v-else class="avatar-placeholder">
<text class="avatar-icon">+</text>
<text class="avatar-text">上传头像</text>
</view>
</view>
</view>
</view>
<view class="form-item">
<view class="form-label required">姓名</view>
<view class="form-input">
@ -151,6 +164,7 @@
<script>
import apiRoute from '@/api/apiRoute.js'
import { uploadFile } from '@/common/util.js'
export default {
name: 'StudentEditPopup',
@ -183,7 +197,8 @@ export default {
consultant_id: null,
coach_id: null,
trial_class_count: 2, // |2
actionsExpanded: false //
actionsExpanded: false, //
headimg: '' // URL
},
//
@ -258,7 +273,8 @@ export default {
consultant_id: student.consultant_id,
coach_id: student.coach_id,
trial_class_count: student.trial_class_count,
actionsExpanded: student.actionsExpanded || false
actionsExpanded: student.actionsExpanded || false,
headimg: student.headimg || '' // URL
}
//
this.parseExistingTags()
@ -291,7 +307,8 @@ export default {
consultant_id: null,
coach_id: null,
trial_class_count: 2,
actionsExpanded: false
actionsExpanded: false,
headimg: '' // URL
}
//
this.selectedTagIds = []
@ -378,7 +395,8 @@ export default {
member_label: this.studentData.member_label,
consultant_id: this.studentData.consultant_id,
coach_id: this.studentData.coach_id,
trial_class_count: this.studentData.trial_class_count
trial_class_count: this.studentData.trial_class_count,
headimg: this.studentData.headimg // URL
}
if (this.isEditing) {
@ -476,6 +494,46 @@ export default {
this.selectedTagIds = []
this.selectedTagNames = []
}
},
//
chooseAvatar() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePath = res.tempFilePaths[0]
//
uni.showLoading({
title: '上传中...',
mask: true
})
// uploadFile
uploadFile(
tempFilePath,
(fileData) => {
// ,fileDataurlextnamename
this.studentData.headimg = fileData.url
uni.hideLoading()
uni.showToast({
title: '头像上传成功',
icon: 'success'
})
},
(error) => {
//
uni.hideLoading()
console.error('头像上传失败:', error)
}
)
},
fail: (err) => {
console.error('选择图片失败:', err)
}
})
}
}
}

2
uniapp/components/student-info-card/student-info-card.vue

@ -251,7 +251,7 @@ export default {
.info-label {
color: #999;
font-size: 22rpx;
width: 150rpx;
width: 165rpx;
flex-shrink: 0;
}

48
uniapp/pages-coach/coach/schedule/schedule_table.vue

@ -1431,8 +1431,7 @@ export default {
},
// 使API
async viewScheduleDetail(scheduleId) {
try {
viewScheduleDetail(scheduleId) {
if (!scheduleId) {
uni.showToast({
title: '课程ID不能为空',
@ -1441,51 +1440,10 @@ export default {
return;
}
//
// propsScheduleDetail
// ScheduleDetailwatchapi.getCourseArrangementDetail()
this.selectedScheduleId = scheduleId;
this.showScheduleDetail = true;
// 使APIadmin
console.log('调用统一API获取课程安排详情:', scheduleId);
// APIScheduleDetailfetchScheduleDetail
// ScheduleDetailapi.getCourseArrangementDetail()admin
// 使
// API
try {
const testResponse = await api.getCourseArrangementDetail({
schedule_id: scheduleId
});
if (testResponse.code !== 1) {
console.warn('API预检查警告:', testResponse.msg);
}
} catch (preCheckError) {
console.warn('API预检查失败,将在ScheduleDetail组件中处理:', preCheckError);
}
} catch (error) {
console.error('获取课程安排详情失败:', error);
let errorMessage = '获取课程详情失败';
if (error.message) {
if (error.message.includes('timeout')) {
errorMessage = '请求超时,请重试';
} else if (error.message.includes('Network')) {
errorMessage = '网络连接失败';
}
}
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 3000
});
// 使ScheduleDetail
this.selectedScheduleId = scheduleId;
this.showScheduleDetail = true;
}
},
//

1
uniapp/pages-market/clue/class_arrangement_detail.vue

@ -29,6 +29,7 @@
<view class="student-info">
<view class="student-name">{{ stu.name }}</view>
<view class="student-age">年龄{{ stu.age || '未知' }}</view>
<view class="course-status">课程类型{{ stu.person_type == 'customer_resource' ? '体验课' : '正式课'}}</view>
<view class="course-status">课程状态{{ stu.courseStatus }}</view>
<view class="course-status">上课情况{{ stu.course_progress.used }}/{{ stu.course_progress.total }}</view>
<view class="expiry-date" v-if="stu.student_course_info">到期时间{{ stu.student_course_info.end_date || '未设置' }}</view>

718
uniapp/pages-market/clue/clue_info.vue

@ -138,8 +138,12 @@
<StudyPlanCard v-if="currentPopup === 'study_plan'" :plan-list="studyPlanList" @edit="openEditStudyPlan" />
<!-- 订单列表弹窗 -->
<OrderListCard v-if="currentPopup === 'order_list'" :order-list="orderList" @add-order="openAddOrderDialog"
@pay-order="handlePayOrder" @view-detail="viewOrderDetail" />
<OrderListCard
v-if="currentPopup === 'order_list'"
:student-id="currentStudent && currentStudent.id"
:resource-id="clientInfo.resource_id"
@payment-success="handlePaymentSuccess"
/>
<!-- 服务列表弹窗 -->
<ServiceListCard v-if="currentPopup === 'service_list'" :service-list="serviceList" />
@ -170,57 +174,11 @@
@confirm="handleStudentEditConfirm" />
<StudyPlanPopup ref="studyPlanPopup" :student-id="currentStudent && currentStudent.id"
@confirm="handleStudyPlanConfirm" />
<!-- 新增订单弹窗 -->
<uni-popup ref="orderFormPopup" type="bottom">
<OrderFormPopup :visible="showOrderForm" :student-info="currentStudent"
:resource-id="clientInfo.resource_id" @cancel="closeOrderForm" @confirm="handleOrderFormConfirm" />
</uni-popup>
<!-- 二维码支付弹窗 -->
<uni-popup ref="qrCodePopup" type="center" @close="closeQRCodeModal">
<view class="qrcode-payment-modal" v-if="qrCodePaymentData">
<!-- 弹窗头部 -->
<view class="modal-header">
<text class="modal-title">扫码支付</text>
<view class="close-btn" @click="closeQRCodeModal">
<text></text>
</view>
</view>
<!-- 订单信息 -->
<view class="order-info">
<view class="info-row">
<text class="label">订单号</text>
<text class="value">{{ qrCodePaymentData.order.order_no }}</text>
</view>
<view class="info-row">
<text class="label">支付金额</text>
<text class="amount">¥{{ qrCodePaymentData.order.total_amount }}</text>
</view>
</view>
<!-- 二维码区域 -->
<view class="qrcode-container">
<image v-if="qrCodePaymentData.qrcodeImage" :src="qrCodePaymentData.qrcodeImage"
class="qrcode-image" mode="aspectFit" />
<text v-else class="qrcode-placeholder">二维码加载中...</text>
<text class="qrcode-tip">请使用微信扫码完成支付</text>
</view>
<!-- 操作按钮 -->
<view class="modal-buttons">
<view class="btn secondary" @click="closeQRCodeModal">取消支付</view>
<view class="btn primary" @click="confirmQRCodePayment">发送二维码给客户</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js'
import dictUtilSimple from '@/common/dictUtilSimple.js'
//
import ClientInfoCard from '@/components/client-info-card/client-info-card.vue'
import StudentInfoCard from '@/components/student-info-card/student-info-card.vue'
@ -234,7 +192,6 @@
import CourseInfoCard from '@/components/course-info-card/index.vue'
import OrderListCard from '@/components/order-list-card/index.vue'
import ServiceListCard from '@/components/service-list-card/index.vue'
import OrderFormPopup from '@/components/order-form-popup/index.vue'
//
import StudentEditPopup from '@/components/student-edit-popup/student-edit-popup.vue'
import FitnessRecordPopup from '@/components/fitness-record-popup/fitness-record-popup.vue'
@ -253,7 +210,6 @@
CourseInfoCard,
OrderListCard,
ServiceListCard,
OrderFormPopup,
StudentEditPopup,
FitnessRecordPopup,
StudyPlanPopup
@ -281,19 +237,8 @@
//
currentPopup: null,
studyPlanList: [],
orderList: [],
serviceList: [],
//
showOrderForm: false,
//
showQRCodeModal: false,
qrCodePaymentData: null,
//
paymentTypeDict: [], //
//
remark_content: '',
currentRecord: null,
@ -412,82 +357,8 @@
},
methods: {
/**
* 加载字典数据
*/
async loadDictData() {
try {
console.log('开始加载支付方式字典数据')
const dictResult = await dictUtilSimple.getBatchDict(['payment_type'])
if (dictResult.payment_type && Array.isArray(dictResult.payment_type)) {
this.paymentTypeDict = dictResult.payment_type
console.log('支付方式字典加载成功:', this.paymentTypeDict)
} else {
console.warn('支付方式字典数据格式不正确:', dictResult.payment_type)
// 使
this.paymentTypeDict = [{
name: '现金支付',
value: 'cash'
},
{
name: '扫码支付',
value: 'scan_code'
},
{
name: '订阅支付',
value: 'subscription'
},
{
name: '微信在线代付',
value: 'wxpay_online'
},
{
name: '客户自行付款',
value: 'client_wxpay'
},
{
name: '定金',
value: 'deposit'
}
]
}
} catch (error) {
console.error('加载支付方式字典失败:', error)
// 使
this.paymentTypeDict = [{
name: '现金支付',
value: 'cash'
},
{
name: '扫码支付',
value: 'scan_code'
},
{
name: '订阅支付',
value: 'subscription'
},
{
name: '微信在线代付',
value: 'wxpay_online'
},
{
name: '客户自行付款',
value: 'client_wxpay'
},
{
name: '定金',
value: 'deposit'
}
]
}
},
async init() {
try {
//
await this.loadDictData()
await this.getInfo()
await Promise.all([
this.getUserInfo(),
@ -673,7 +544,7 @@
this.currentPopup = 'study_plan'
break
case 'order_list':
await this.getOrderList(student.id)
// ,
this.currentPopup = 'order_list'
break
case 'service_list':
@ -688,7 +559,6 @@
this.currentPopup = null
//
this.studyPlanList = []
this.orderList = []
this.serviceList = []
this.courseInfo = []
this.fitnessRecords = []
@ -1295,574 +1165,12 @@
}
},
//
async getOrderList(studentId = null) {
if (!this.clientInfo?.resource_id) return
try {
const targetStudentId = studentId || this.currentStudent?.id
// 使student_id
const params = {}
if (targetStudentId) {
params.student_id = targetStudentId
} else if (this.clientInfo.resource_id) {
params.resource_id = this.clientInfo.resource_id
} else {
console.warn('缺少查询参数')
this.orderList = []
return
}
const res = await apiRoute.xs_orderTableList(params)
if (res.code === 1) {
// API
this.orderList = this.processOrderData(res.data?.data || [])
} else {
console.error('获取订单列表失败:', res.msg)
this.orderList = []
}
} catch (error) {
console.error('获取订单列表异常:', error)
this.orderList = []
}
},
//
processOrderData(orders) {
if (!Array.isArray(orders)) return []
return orders.map(order => ({
id: order.id,
student_id: order.student_id,
order_no: order.payment_id || `ORD${order.id}`, // 使payment_id
product_name: order.course_id_name || '课程',
total_amount: order.order_amount || '0.00',
paid_amount: order.order_status === 'paid' ? order.order_amount : '0.00',
unpaid_amount: order.order_status === 'paid' ? '0.00' : order.order_amount,
payment_method: this.formatPaymentType(order.payment_type),
salesperson_name: order.staff_id_name || '未指定',
course_count: order.total_hours || 0, // 使
status: this.mapOrderStatus(order.order_status),
create_time: order.created_at,
//
contract_id: order.contract_id,
contract_sign_id: order.contract_sign_id,
//
_raw: order
}))
},
//
formatPaymentType(paymentType) {
if (!paymentType) return '未知'
//
const paymentItem = this.paymentTypeDict.find(item => item.value === paymentType)
if (paymentItem) {
return paymentItem.name
}
// 使
const fallbackMap = {
'cash': '现金支付',
'scan_code': '扫码支付',
'subscription': '订阅支付',
'wxpay_online': '微信在线代付',
'client_wxpay': '客户自行付款',
'deposit': '定金'
}
return fallbackMap[paymentType] || paymentType || '未知'
},
//
mapOrderStatus(orderStatus) {
const statusMap = {
'pending': 'pending',
'paid': 'paid',
'signed': 'completed',
'completed': 'completed',
'transfer': 'partial'
}
return statusMap[orderStatus] || 'pending'
},
//
async getServiceList(studentId = null) {
if (!this.clientInfo?.resource_id) return
const targetStudentId = studentId || this.currentStudent?.id
if (!targetStudentId) {
uni.showToast({
title: '请先选择学生',
icon: 'none'
})
return
}
try {
const response = await apiRoute.getStudentServiceList({
student_id: targetStudentId
})
if (response.code === 1) {
this.serviceList = response.data || []
} else {
console.error('获取服务记录失败:', response.msg)
uni.showToast({
title: response.msg || '获取服务记录失败',
icon: 'none'
})
this.serviceList = []
}
} catch (error) {
console.error('获取服务记录异常:', error)
uni.showToast({
title: '网络请求失败',
icon: 'none'
})
this.serviceList = []
}
},
//
openAddOrderDialog() {
if (!this.currentStudent) {
uni.showToast({
title: '请先选择学生',
icon: 'none'
})
return
}
//
this.closePopup()
this.showOrderForm = true
this.$refs.orderFormPopup.open()
},
//
closeOrderForm() {
this.showOrderForm = false
this.$refs.orderFormPopup.close()
},
//
async handleOrderFormConfirm() {
try {
//
this.closeOrderForm()
//
await this.getOrderList()
uni.showToast({
title: '订单创建成功',
icon: 'success'
})
} catch (error) {
console.error('处理订单确认失败:', error)
}
},
//
handlePayOrder(order) {
//
this.closePopup()
//
this.processPayment(order)
},
//
async processPayment(order) {
const paymentType = order._raw?.payment_type || order.payment_type
console.log('paymentType', paymentType)
try {
switch (paymentType) {
case 'cash':
// -
await this.confirmCashPayment(order)
break
case 'scan_code':
// -
this.showQRCodePayment(order)
break
case 'subscription':
// -
this.showSubscriptionPayment(order)
break
case 'wxpay_online':
// 线 -
this.showWechatPayment(order)
break
default:
uni.showToast({
title: '不支持的支付方式',
icon: 'none'
})
}
} catch (error) {
console.error('支付处理失败:', error)
uni.showToast({
title: '支付处理失败',
icon: 'none'
})
}
},
//
async confirmCashPayment(order) {
uni.showModal({
title: '现金支付确认',
content: `确认已收到现金支付 ¥${order.total_amount}`,
success: async (res) => {
if (res.confirm) {
try {
// API
const updateData = {
order_id: order._raw?.id || order.id,
order_status: 'paid',
payment_id: `CASH${Date.now()}` //
}
const result = await apiRoute.xs_orderTableUpdatePaymentStatus(updateData)
if (result.code === 1) {
uni.showToast({
title: '支付确认成功',
icon: 'success'
})
//
await this.getOrderList()
} else {
uni.showToast({
title: result.msg || '支付确认失败',
icon: 'none'
})
}
} catch (error) {
console.error('现金支付确认失败:', error)
uni.showToast({
title: '支付确认失败',
icon: 'none'
})
}
}
}
})
},
//
async showQRCodePayment(order) {
console.log('扫码支付:', order)
try {
uni.showLoading({
title: '生成支付二维码...'
})
//
const res = await apiRoute.getOrderPayQrcode({
order_id: order._raw?.id || order.id
})
uni.hideLoading()
if (res.code === 1 && res.data) {
//
this.openQRCodeModal({
order: order,
qrcode: res.data.code_url,
// 使base64访localhost
qrcodeImage: res.data.qrcode_base64 || res.data.qrcode_url
})
} else {
uni.showToast({
title: res.msg || '获取支付二维码失败',
icon: 'none'
})
}
} catch (error) {
uni.hideLoading()
console.error('获取支付二维码失败:', error)
uni.showToast({
title: '获取支付二维码失败',
icon: 'none'
})
}
},
//
openQRCodeModal(paymentData) {
this.qrCodePaymentData = paymentData
this.showQRCodeModal = true
this.$refs.qrCodePopup.open()
},
//
closeQRCodeModal() {
this.showQRCodeModal = false
this.qrCodePaymentData = null
this.$refs.qrCodePopup.close()
},
//
async confirmQRCodePayment() {
if (!this.qrCodePaymentData?.order) return
const order = this.qrCodePaymentData.order
uni.showModal({
title: '支付确认',
content: '请确认是否已完成扫码支付?',
success: async (res) => {
if (res.confirm) {
try {
//
await this.updateOrderStatus(order, 'paid', `QR${Date.now()}`)
this.closeQRCodeModal()
} catch (error) {
console.error('支付确认失败:', error)
uni.showToast({
title: '支付确认失败',
icon: 'none'
})
}
}
}
})
},
//
showSubscriptionPayment(order) {
uni.showActionSheet({
itemList: ['确认分期支付方案', '取消支付'],
success: async (res) => {
if (res.tapIndex === 0) {
uni.showModal({
title: '分期支付确认',
content: `确认使用分期支付方式支付 ¥${order.total_amount}`,
success: async (modalRes) => {
if (modalRes.confirm) {
//
await this.updateOrderStatus(order, 'partial',
`SUB${Date.now()}`)
}
}
})
}
}
})
},
//
showWechatPayment(order) {
uni.showModal({
title: '微信支付',
content: `将调用微信支付 ¥${order.total_amount}`,
confirmText: '确认支付',
cancelText: '取消',
success: async (res) => {
if (res.confirm) {
//
uni.showLoading({
title: '正在调用微信支付...'
})
//
setTimeout(async () => {
uni.hideLoading()
// API
uni.showModal({
title: '支付结果',
content: '微信支付完成,请确认是否已收到款项?',
success: async (confirmRes) => {
if (confirmRes.confirm) {
await this.updateOrderStatus(order, 'paid',
`WX${Date.now()}`)
}
}
})
}, 1500)
}
}
})
},
//
async updateOrderStatus(order, status, paymentId = '') {
try {
const updateData = {
order_id: order._raw?.id || order.id,
order_status: status,
payment_id: paymentId
}
const result = await apiRoute.xs_orderTableUpdatePaymentStatus(updateData)
if (result.code === 1) {
const statusText = {
'paid': '支付成功',
'partial': '分期支付确认成功',
'cancelled': '支付已取消'
}
uni.showToast({
title: statusText[status] || '状态更新成功',
icon: 'success'
})
//
await this.getOrderList()
} else {
uni.showToast({
title: result.msg || '状态更新失败',
icon: 'none'
})
}
} catch (error) {
console.error('更新订单状态失败:', error)
uni.showToast({
title: '状态更新失败',
icon: 'none'
})
}
},
//
viewOrderDetail(order) {
const orderInfo = order._raw || order
const detailText = `
订单号${order.order_no}
课程${order.product_name}
金额¥${order.total_amount}
已付¥${order.paid_amount}
未付¥${order.unpaid_amount}
支付方式${order.payment_method}
销售顾问${order.salesperson_name}
课时数${order.course_count}
状态${this.getOrderStatusText(order.status)}
创建时间${this.formatOrderTime(order.create_time)}
${orderInfo.paid_at ? '支付时间:' + this.formatOrderTime(orderInfo.paid_at) : ''}
`.trim()
//
const isOrderPaid = order.status === 'paid'
const buttons = isOrderPaid ? ['知道了', '合同签署'] : ['知道了']
console.log('订单数据', order)
uni.showModal({
title: '订单详情',
content: detailText,
showCancel: isOrderPaid,
cancelText: isOrderPaid ? '知道了' : '',
confirmText: isOrderPaid ? '合同签署' : '知道了',
success: (res) => {
if (res.confirm && isOrderPaid) {
//
this.goToContractSign(order)
}
}
})
},
//
goToContractSign(order) {
//
const studentId = order.student_id || this.currentStudent?.id
const contractId = order.contract_id || order._raw?.contract_id
const contractSignId = order.contract_sign_id || order._raw?.contract_sign_id
if (!studentId) {
uni.showToast({
title: '缺少学生信息',
icon: 'none'
})
return
}
if (!contractId) {
// ID
this.getContractByOrder(order, studentId)
return
}
//
let url =
`/pages-student/contracts/sign?contract_id=${contractId}&student_id=${studentId}&contract_name=${encodeURIComponent(order.product_name + '合同')}&user_role=staff`
// ID
if (contractSignId) {
url += `&contract_sign_id=${contractSignId}`
}
//
uni.navigateTo({
url: url
})
},
//
async getContractByOrder(order, studentId) {
try {
uni.showLoading({
title: '获取合同信息...'
})
// API
const res = await apiRoute.getContractByOrder({
order_id: order._raw?.id || order.id,
student_id: studentId
})
uni.hideLoading()
if (res.code === 1 && res.data) {
const contractInfo = res.data
//
uni.navigateTo({
url: `/pages-student/contracts/sign?contract_id=${contractInfo.contract_id}&student_id=${studentId}&contract_name=${encodeURIComponent(contractInfo.contract_name || order.product_name + '合同')}&user_role=staff`
})
} else {
uni.showToast({
title: res.msg || '未找到相关合同',
icon: 'none'
})
}
} catch (error) {
uni.hideLoading()
console.error('获取合同信息失败:', error)
uni.showToast({
title: '获取合同信息失败',
icon: 'none'
})
}
},
//
getOrderStatusText(status) {
const statusMap = {
'pending': '待支付',
'paid': '已支付',
'partial': '部分支付',
'cancelled': '已取消',
'completed': '已完成',
'refunded': '已退款'
}
return statusMap[status] || '未知状态'
},
//
formatOrderTime(timeStr) {
if (!timeStr) return '未知'
try {
const date = new Date(timeStr)
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
} catch (e) {
return timeStr
}
/**
* 订单支付成功回调
*/
handlePaymentSuccess(order) {
console.log('订单支付成功:', order)
// ,
},
//

23
uniapp/pages-market/clue/edit_clues.vue

@ -684,8 +684,6 @@
//-()
async getInfo() {
try {
console.log('getInfo - 开始获取客户详情, resource_sharing_id:', this.resource_sharing_id);
if (!this.resource_sharing_id) {
console.error('getInfo - resource_sharing_id 为空,无法获取客户详情');
return;
@ -695,9 +693,7 @@
resource_sharing_id: this.resource_sharing_id
};
console.log('getInfo - 发起请求:', params);
let res = await apiRoute.xs_resourceSharingInfo(params); //-()
console.log('getInfo - 请求响应:', res);
if (res.code != 1) {
console.error('getInfo - 请求失败:', res.msg);
@ -710,8 +706,6 @@
let customerResource = res.data.customerResource || {}; //
let sixSpeed = res.data.customerResource.sixSpeed || {}; //
console.log('getInfo - 客户资源详情:', customerResource);
console.log('getInfo - 六要素详情:', sixSpeed);
//
this._resourceDetail = res.data;
@ -755,17 +749,18 @@
emotional_stickiness_score: sixSpeed.emotional_stickiness_score || '', //
};
console.log('getInfo - 表单数据设置完成:', this.formData);
console.log('getInfo - 表单数据设置完成:',sixSpeed.promised_visit_time);
//
if (sixSpeed.promised_visit_time) {
// if (sixSpeed.promised_visit_time) {
// console.log('getInfo - ',sixSpeed.promised_visit_time.includes(' '));
//
if (sixSpeed.promised_visit_time.includes(' ')) {
this.formData.promised_visit_time = sixSpeed.promised_visit_time;
} else {
this.formData.promised_visit_time = this.$util.formatToDateTime(sixSpeed.promised_visit_time, 'Y-m-d');
}
}
// if (sixSpeed.promised_visit_time.includes(' ')) {
// this.formData.promised_visit_time = sixSpeed.promised_visit_time;
// } else {
// this.formData.promised_visit_time = this.$util.formatToDateTime(sixSpeed.promised_visit_time, 'Y-m-d');
// }
// }
//
if (sixSpeed.preferred_class_time) {

Loading…
Cancel
Save